16

我正在使用 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

(此代码段仅查看日期,因为这是我的应用程序所关心的全部内容,但可以扩展类似的逻辑以包含时间组件。)

我想知道(希望)有更好的方法来做到这一点。至少可以说,两次解析相同的字符串以查看它是否填充不同的默认值似乎是对资源的严重浪费。

这是预期行为的一组测试(使用鼻子测试生成器):

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

4 回答 4

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)
于 2011-12-08T17:29:54.733 回答
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
于 2013-08-14T21:29:47.237 回答
0

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

psinvert()只是在指定复杂的日期模式时切换其存在%,否则会变得一团糟。所以这里只有文字T字符需要%前缀(在标准的python日期格式中,它是唯一没有前缀的字母数字字符)

于 2013-08-14T20:40:03.657 回答
0

我在使用 dateutil 时遇到了完全相同的问题,我编写了这个函数,并认为我会为了后代而发布它。_parse基本上使用像@ILYA Khlopotov 这样的底层方法建议:

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实例。

于 2013-08-14T20:34:56.043 回答