【问题标题】:customize dateutil.parser century inference logic自定义 dateutil.parser 世纪推理逻辑
【发布时间】:2016-07-25 20:41:37
【问题描述】:

我正在处理具有 2 位数年份的旧文本文件,其中 dateutil.parser 中的默认世纪逻辑似乎无法正常工作。例如,珍珠港袭击不在dparser.parse("12/7/41")(返回 2041-12-7)。

回到 1900 年代的内置世纪“门槛”似乎发生在 66 岁:

import dateutil.parser as dparser
print(dparser.parse("12/31/65")) # goes forward to 2065-12-31 00:00:00
print(dparser.parse("1/1/66")) # goes back to 1966-01-01 00:00:00

出于我的目的,我想将此“阈值”设置为 17,以便:

  • "12/31/16" 解析为 2016-12-31 (yyyy-mm-dd)
  • "1/1/17" 解析为 1917-01-01

但我想继续使用这个模块,因为它的模糊匹配似乎运作良好。

documentation 没有识别用于执行此操作的参数...是否有我忽略的参数?

【问题讨论】:

    标签: python python-dateutil


    【解决方案1】:

    这不是特别好的文档,但您实际上可以使用dateutil.parser 覆盖它。第二个参数是parserinfo 对象,您将关注的方法是convertyeardefault implementation 是导致您出现问题的原因。你可以看到它是根据当前年份来解释世纪的,正负五十年。这就是为什么你会在 1966 年看到过渡。明年将是 1967 年。:)

    由于您个人使用它并且可能有非常特定的需求,因此您不必是超级通用的。如果它适合你,你可以做这样简单的事情:

    from dateutil.parser import parse, parserinfo
    
    class MyParserInfo(parserinfo):
        def convertyear(self, year, *args, **kwargs):
            if year < 100:
                year += 1900
            return year
    
    parse('1/21/47', MyParserInfo())
    # datetime.datetime(1947, 1, 21, 0, 0)
    

    【讨论】:

    • this bug report。推荐的做法是继承和覆盖convertyear
    • 嘿,酷。英雄所见略同! (我不知道那份报告。)
    • 可能值得注意的是,在即将发布的 2.6.0 中,century_specified 标志也将传递给 convertyear,以区分 0099-04-2099-04-20。这个带有**kwargs 的实现应该涵盖了这一点。
    • @C8H10N4O2 看起来更改实际上是在 2.5.0 中。将**kwargs 替换为*args*args, **kwargs,你应该会很好。
    • 另外,请注意,如果您有可能获得像 0095057 (公元一世纪)这样的年份,您应该明确地将签名更改为 year, century_specified=False 并进行相应处理.
    【解决方案2】:

    如果提取的年份超过指定阈值(在您的情况下为 2016 年),您还可以对提取的日期进行后处理手动更改世纪:

    import dateutil.parser as dparser
    
    THRESHOLD = 2016
    
    date_strings = ["12/31/65", "1/1/66", "12/31/16", "1/1/17"]
    for date_string in date_strings:
        dt = dparser.parse(date_string)
        if dt.year > THRESHOLD:
            dt = dt.replace(year=dt.year - 100)
        print(dt)
    

    打印:

    1965-12-31 00:00:00
    1966-01-01 00:00:00
    2016-12-31 00:00:00
    1917-01-01 00:00:00
    

    【讨论】:

    • 谢谢——对于我的用例,它有混合类型,我不能把每个日期都停靠在阈值之上,因为有时世纪是明确的。考虑:print(dparser.parse("The Soviets tested their first A-bomb on 8/29/49", fuzzy = True)); print(dparser.parse("Scientists promise flying atomic cars by the year 2020", fuzzy = True))
    • 它似乎是可配置的,但以一种模糊的方式。文档几乎不清楚。我不得不在源代码中四处寻找一种方法(公平地说,它与文档链接)。
    • @Two-BitAlchemist 确实是一个不错的发现!谢谢!
    • @C8H10N4O2 打破这个解决方案的好例子。谢谢。
    【解决方案3】:

    除了编写自己的 parserinfo.convertyear 方法外,您还可以通过传递标准的 parserinfo 对象来自定义此方法,并更改 _century_year 设置 *):

    from dateutil.parser import parse, parserinfo
    info = parserinfo()
    info._century = 1900
    info._year  = 1965
    parse('12/31/65', parserinfo=info)
    => 1965-12-31 00:00:00
    

    _century 指定添加到任何解析年份的默认年份,即65 + 1900 = 1965

    _year 指定截止年份 +- 50。任何比 _years 至少晚 50 年的年份,即差异在哪里

    • &lt; _year将切换到下个世纪
    • &gt;= _year会切换到上个世纪

    把它想象成一个时间线:

    1900          1916          1965          2015
    +--- (...) ---+--- (...) ---+--- (...) ---+
    ^             ^             ^             ^
    _century      _year - 49    _year         _year + 50
    
    parsed years:
                  16,17,...             99,00,...15
    

    换句话说,年份 00, 01, ..., 99 映射到时间范围 _year - 49 .. _year + 50_year 设置为这 100 年期间的中间。使用这两个设置,您可以指定任何您喜欢的截断。

    *) 请注意,这两个变量未记录在案,但在撰写本文时最新的稳定版本 2.5.3 中用于 parserinfo.convertyear 的默认实现。恕我直言,默认实现非常聪明。

    【讨论】:

    • 我不建议依赖私有变量,因为它们不能保证在以后的版本中存在。对于这些变量,我想它们很快就会被删除,取而代之的是公共接口。
    • 我明白您的意思,但是您始终可以实现自己的parserinfo.convertyear,从而保留 dateutil 选择更改其行为时的行为。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 2019-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多