在Python中解析日期而不使用默认值
我正在使用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
4 个回答
simple-date这个工具可以帮你处理日期格式的问题(它会尝试多种格式,但没有你想象的那么多,因为它使用的模式是在Python的日期模式基础上加了一些可选部分,就像正则表达式一样)。
你可以查看这个链接: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
顺便说一下,invert()
这个函数只是用来切换%
的存在与否,因为在指定复杂的日期模式时,如果不处理好会变得非常麻烦。所以在这里,只有字面上的T
字符需要加%
前缀(在标准的Python日期格式中,它是唯一一个没有前缀的字母数字字符)。
这可能算是一种“技巧”,但看起来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
根据你的具体情况,下面这个解决方案可能会有效:
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
另一种方法是对解析器类进行“猴子补丁”(这种做法有点黑科技,所以如果你有其他选择,我不太推荐这样做):
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 转换成日期时间对象:
# ddd might have following fields:
# "year", "month", "day", "weekday",
# "hour", "minute", "second", "microsecond",
# "tzname", "tzoffset"
datetime.datetime(ddd.year, ddd.month, ddd.day)