我正在使用python的dateutil.parser
工具来解析我从第三方供稿中获得的一些日期。它允许指定一个默认日期,它本身默认为今天,用于填充已解析日期的缺失元素。虽然这通常很有用,但我的用例没有合理的默认值,我宁愿将部分日期视为我根本没有得到日期(因为它几乎总是意味着我的数据乱码)。我写了以下工作:
from dateutil import parser
import datetime
def parse_no_default(dt_str):
dt = parser.parse(dt_str, default=datetime.datetime(1900, 1, 1)).date()
dt2 = parser.parse(dt_str, default=datetime.datetime(1901, 2, 2)).date()
if dt == dt2:
return dt
else:
return None
(这个片段只查看日期,因为我只关心我的应用程序,但类似的逻辑可以扩展到包含时间组件。)
我想知道(希望)有更好的方法来做到这一点。解析相同的字符串只是为了看它是否填写了不同的默认值,这似乎是浪费资源,至少可以说。
这是针对预期行为的一组测试(使用nosetest生成器):
import nose.tools
import lib.tools.date
def check_parse_no_default(sample, expected):
actual = lib.tools.date.parse_no_default(sample)
nose.tools.eq_(actual, expected)
def test_parse_no_default():
cases = (
('2011-10-12', datetime.date(2011, 10, 12)),
('2011-10', None),
('2011', None),
('10-12', None),
('2011-10-12T11:45:30', datetime.date(2011, 10, 12)),
('10-12 11:45', None),
('', None),
)
for sample, expected in cases:
yield check_parse_no_default, sample, expected
答案 0 :(得分:8)
根据您的域名,以下解决方案可能有效:
DEFAULT_DATE = datetime.datetime(datetime.MINYEAR, 1, 1)
def parse_no_default(dt_str):
dt = parser.parse(dt_str, default=DEFAULT_DATE).date()
if dt != DEFAULT_DATE:
return dt
else:
return None
另一种方法是使用猴子补丁解析器类(这非常hackiesh,所以如果你有其他选择,我不推荐它):
import dateutil.parser as parser
def parse(self, timestr, default=None,
ignoretz=False, tzinfos=None,
**kwargs):
return self._parse(timestr, **kwargs)
parser.parser.parse = parse
您可以按如下方式使用它:
>>> ddd = parser.parser().parse('2011-01-02', None)
>>> ddd
_result(year=2011, month=01, day=02)
>>> ddd = parser.parser().parse('2011', None)
>>> ddd
_result(year=2011)
通过检查结果(ddd)中可用的成员,您可以确定何时返回None。 当所有字段都可用时,您可以将ddd转换为datetime对象:
# ddd might have following fields:
# "year", "month", "day", "weekday",
# "hour", "minute", "second", "microsecond",
# "tzname", "tzoffset"
datetime.datetime(ddd.year, ddd.month, ddd.day)
答案 1 :(得分:3)
这可能是一个“黑客”,但看起来像dateutil看到你输入的默认属性中的极少数属性。你可以提供一个“假的”日期时间,以所需的方式爆炸。
>>> import datetime
>>> import dateutil.parser
>>> class NoDefaultDate(object):
... def replace(self, **fields):
... if any(f not in fields for f in ('year', 'month', 'day')):
... return None
... return datetime.datetime(2000, 1, 1).replace(**fields)
>>> def wrap_parse(v):
... _actual = dateutil.parser.parse(v, default=NoDefaultDate())
... return _actual.date() if _actual is not None else None
>>> cases = (
... ('2011-10-12', datetime.date(2011, 10, 12)),
... ('2011-10', None),
... ('2011', None),
... ('10-12', None),
... ('2011-10-12T11:45:30', datetime.date(2011, 10, 12)),
... ('10-12 11:45', None),
... ('', None),
... )
>>> all(wrap_parse(test) == expected for test, expected in cases)
True
答案 2 :(得分:0)
我遇到了与dateutil完全相同的问题,我编写了这个函数,并认为我会为了后人的缘故发布它。基本上使用像@ILYA Khlopotov这样的基础_parse
方法建议:
from dateutil.parser import parser
import datetime
from StringIO import StringIO
_CURRENT_YEAR = datetime.datetime.now().year
def is_good_date(date):
try:
parsed_date = parser._parse(parser(), StringIO(date))
except:
return None
if not parsed_date: return None
if not parsed_date.year: return None
if parsed_date.year < 1890 or parsed_date.year > _CURRENT_YEAR: return None
if not parsed_date.month: return None
if parsed_date.month < 1 or parsed_date.month > 12: return None
if not parsed_date.day: return None
if parsed_date.day < 1 or parsed_date.day > 31: return None
return parsed_date
返回的对象不是datetime
实例,但它具有.year
,.month
和.day
属性,足以满足我的需求。我想您可以轻松地将其转换为datetime
实例。
答案 3 :(得分:0)
simple-date为你做了这个(它在内部尝试多种格式,但没有你想象的那么多,因为它使用的模式扩展了python的日期模式和可选部分,比如regexps)。
见https://github.com/andrewcooke/simple-date - 但只有python 3.2及以上(抱歉)。
它比你想要的更宽松:
>>> for date in ('2011-10-12', '2011-10', '2011', '10-12', '2011-10-12T11:45:30', '10-12 11:45', ''):
... print(date)
... try: print(SimpleDate(date).naive.datetime)
... except: print('nope')
...
2011-10-12
2011-10-12 00:00:00
2011-10
2011-10-01 00:00:00
2011
2011-01-01 00:00:00
10-12
nope
2011-10-12T11:45:30
2011-10-12 11:45:30
10-12 11:45
nope
nope
但您可以指定自己的格式。例如:
>>> from simpledate import SimpleDateParser, invert
>>> parser = SimpleDateParser(invert('Y-m-d(%T| )?(H:M(:S)?)?'))
>>> for date in ('2011-10-12', '2011-10', '2011', '10-12', '2011-10-12T11:45:30', '10-12 11:45', ''):
... print(date)
... try: print(SimpleDate(date, date_parser=parser).naive.datetime)
... except: print('nope')
...
2011-10-12
2011-10-12 00:00:00
2011-10
nope
2011
nope
10-12
nope
2011-10-12T11:45:30
2011-10-12 11:45:30
10-12 11:45
nope
nope
ps invert()
只是切换%
的存在,否则在指定复杂的日期模式时会变得非常混乱。所以这里只有文字T
字符需要一个%
前缀(在标准的python日期格式中,它将是唯一没有前缀的字母数字字符)