2011-12-08 45 views
13

我使用python的dateutil.parser工具來解析我從第三方提要中獲取的某些日期。它允許指定缺省日期,該日期本身默認爲今天,用於填充解析日期的缺失元素。雖然這通常很有幫助,但我的用例沒有明智的默認設置,我寧願將部分日期視爲沒有得到日期(因爲它幾乎總是意味着我得到了亂碼數據)。我寫了下面的解決:在不使用默認情況下解析python中的日期

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 

回答

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

這隻能解決空字符串的情況。當我有部分日期時,它仍然默認沒有指定的字段,但會獲得與默認日期不同的最終日期。我已經在問題中添加了一些單元測試來說明需求以及本例失敗的地方。謝謝你看一看! –

+1

要小心,顯然在你的第一個例子中,你正在比較一個日期對象和日期時間對象。它總是不平等的。 –

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

0

簡單日期爲您完成此(它不嘗試多種格式,在內部,但你可能會認爲沒有那麼多,因爲它使用的圖案可選配件擴展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 

PS的invert()剛剛切換的%存在指定複雜的日期模式時,否則成爲一個真正的混亂。所以這裏隻字面T字符需要%前綴(在標準Python日期格式化將會是唯一的字母數字字符沒有前綴)

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 
+0

即使是黑客也不錯,乾淨的黑客! +1 – tzaman

+0

另外閱讀'替換'函數kwargs我可以找出哪些日期元素被指定在傳遞的字符串。只有一年或一年W /月等。正是我需要的。 – Winand

+0

這看起來不錯,但目前沒有用。我修改了這樣的功能,似乎修復它:'def wrap_parse(v):try:_actual = ...除了AttributeError:_actual = None' – user2205380