在不使用默认值的情况下解析python中的日期

在不使用默认值的情况下解析python中的日期,python,python-dateutil,Python,Python Dateutil,我正在使用python的dateutil.parser工具解析从第三方提要获取的一些日期。它允许指定一个默认日期,它本身默认为今天,用于填充解析日期中缺少的元素。虽然这通常是有帮助的,但我的用例没有一个合理的默认值,我更愿意将部分日期视为我根本没有得到日期(因为它几乎总是意味着我得到了乱码数据)。我写了以下工作: from dateutil import parser import datetime def parse_no_default(dt_str): dt = parser.par

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

根据您的域,以下解决方案可能有效:

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
另一种方法是使用monkey patch parser类(这是一种非常缺乏技巧的方法,所以如果您有其他选择,我不推荐):

您可以按如下方式使用它:

>>> 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)

我在dateutil中遇到了完全相同的问题,我编写了这个函数,并认为为了子孙后代,我会发布它。基本上使用基础的
\u 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
来自dateutil.parser导入解析器
导入日期时间
从StringIO导入StringIO
_当前年份=datetime.datetime.now().YEAR
def有效日期(日期):
尝试:
parsed_date=parser._parse(parser(),StringIO(date))
除:
一无所获
如果未解析,则返回日期:无
如果未分析_date.year:返回None
如果解析的日期年<1890或解析的日期年>当前年:返回无
如果未分析_date.month:返回无
如果解析的日期月<1或解析的日期月>12:返回无
如果未分析_date.day:返回无
如果已解析的_date.day<1或已解析的_date.day>31:返回无
返回解析日期

返回的对象不是
datetime
实例,但它具有
.year
.month
.day
属性,这些属性足以满足我的需要。我想您可以很容易地将它转换为一个
datetime
实例。

simple date可以为您实现这一点(它在内部尝试了多种格式,但没有您想象的那么多,因为它使用的模式通过可选部分(如regexp)扩展了python的日期模式)

请参阅-但仅限于Python3.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日期格式中,它将是唯一没有前缀的字母数字字符)

这可能是一个“黑客”,但看起来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

这只解决了空字符串的情况。当我有一个部分日期时,它仍然默认未指定的字段,但得到的最终日期与默认日期不同。我在问题中添加了一些单元测试,以说明需求以及本示例失败的地方。不过谢谢你看一看!请小心,显然在第一个示例中,您将日期对象与日期时间对象进行比较。它总是不平等的。好的,干净的黑客,即使是黑客+1同时读取
replace
函数的kwargs,我可以找出在传递的字符串中指定了哪些日期元素。只有一年,或者一年/一个月等等。这正是我所需要的。这看起来不错,但目前对我不起作用。我像这样修改了函数,似乎解决了它:
def wrap\u parse(v):try:\u actual=。。。除AttributeError外:_actual=None
>>> 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