在python中解析日期而不使用默认值

时间:2011-12-08 17:13:04

标签: python python-dateutil

我正在使用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 个答案:

答案 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日期格式中,它将是唯一没有前缀的字母数字字符)