使用 Python 解析北欧格式日期(首先是 DMY,然后是 YMD)的最佳方法

Best way to parse dates in Northern European format (first DMY, then YMD) using Python

我正在寻找一种方法来解析 未知格式 的日期,按优先顺序使用以下 "meta-formats":

  1. 日-月-年 (DMY)
  2. 年月日 (YMD)
  3. 可能是其他格式(但这并不重要)

这是挪威、丹麦、芬兰和荷兰几乎所有发票上实际使用的元格式,因此它应该是一个常见的用例。但是,似乎没有一个库能够处理它而不必定义大量可能的格式。

具体来说。我需要一个方法 (parse) 来满足以下条件: parse("01-02-03") == "datetime.datetime(2003, 2, 1, 0, 0)" parse("2003-02-01") == "datetime.datetime(2003, 2, 1, 0, 0)"

但它也应该适用于其他分隔符等。

关于如何在不定义大量格式列表的情况下完成此任务的任何建议?

编辑:由于瑞典有不同的偏好,我更喜欢一个可以概括的答案,以适用于 YMD 优于 DMY 的情况。

看看dateutil.parser.parse?

from dateutil.parser import parse

parse('01-02-03', dayfirst=True)  # datetime.datetime(2003, 2, 1, 0, 0)
parse('2003-02-01')  # datetime.datetime(2003, 2, 1, 0, 0)

当然你需要微调parse()的参数,因为它不会总是猜测它是YDM还是YMD格式,但这是一个好的开始。 Have a look at the documentation for more examples.

您尝试过使用 pandas 吗?恕我直言,这是导入日期的最好和最干净的方法,因为它在 99% 的时间里开箱即用,而大多数其他东西,如 dateutil 往往会失败。

import pandas as pd
pd.to_datetime('01-02-03', dayfirst=True)
pd.to_datetime('2003-02-01', dayfirst=True)

pandas 的另一个优点是它可以处理数组、列表和大多数其他类型,甚至支持使用 datetime-index 对数组(称为 DataFrame)进行字符串索引。

有关如何使用 pandas 获取 datetime.datetime 格式的更多信息:
只需将 .to_pydatetime() 附加到您的解析器即可。

pd.to_datetime('2003-02-01', dayfirst=True).to_pydatetime()
# Out[]: datetime.datetime(2003, 2, 1, 0, 0)

查看 python 中的 arrow 图书馆。您可以按照您喜欢的任何格式指定日期格式。例如:

arrow.get("01-02-03","DD-MM-YY")
# gives <Arrow [2003-02-01T00:00:00+00:00]>
arrow.get("01-02-03","YY-MM-DD")
# gives <Arrow [2001-02-03T00:00:00+00:00]>

正如 Scotty1 正确指出的那样,pandas.to_datetime 实际上适用于我描述的用例,但它并没有推广到 YMD 优于 DMY 的用例(这恰好是瑞典的偏好)。

我最终得到的东西在超过 95% 的案例中都有效,这比任何现有的日期解析库都可以开箱即用地匹配要好得多。这是我的解决方案:

def parse(string):
    dmy = ['%d{sep}%m{sep}%Y', '%d{sep}%m{sep}%y']
    ymd = ['%Y{sep}%m{sep}%d', '%y{sep}%m{sep}%d']
    seperators = ['', ' ', '-', '.', '/']
    formats = [f.format(sep=sep) for f in dmy + ymd for sep in seperators]
    additional = ['%d/%m %Y']
    return dateparser.parse(string, date_formats=formats + additional)

可以通过将 dmy + ymd 替换为 ymd + dmy 来实现对 "YMD preferred over DMY" 的支持。

为了帮助传达上述代码的行为,这里有一组全部通过的测试:

out = datetime.datetime(2003, 2, 1, 0, 0)

# straight forward DMY
assert out == extractors.extract_date('010203')
assert out == extractors.extract_date('01022003')
assert out == extractors.extract_date('01-02-03')
assert out == extractors.extract_date('01-02-2003')

# alternative delimiters
assert out == extractors.extract_date('01.02.03')
assert out == extractors.extract_date('01 02 03')
assert out == extractors.extract_date('01/02/03')
assert out == extractors.extract_date('01/02 2003')

# YMD (when the first cannot parse as a day, default to YMD)
assert out == extractors.extract_date('2003-02-01')
assert extractors.extract_date('98-02-01') == \
    datetime.datetime(1998, 2, 1, 0, 0)

# single digits
assert out == extractors.extract_date('1-2-2003')
assert out == extractors.extract_date('1/2 2003')
assert out == extractors.extract_date('2003-2-1')

# when there are not other possibilities (MDY, YDM)
assert extractors.extract_date('12-31-98') == \
    datetime.datetime(1998, 12, 31, 0, 0)
assert extractors.extract_date('98-31-12') == \
    datetime.datetime(1998, 12, 31, 0, 0)

我尝试了 pandas,当我看到它正在下载 (11.6MB) 时我感到很困惑,当它开始下载时我也很惊讶 numpy (12.1MB)。

但是作为一个欧洲人,我不需要 dateutil 的默认 "month-first" 行为,所以我现在使用这个:

import re
sloppy_iso8601 = re.compile('^[12][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?.*$')
import dateutil.parser

def parse_date(value, dayfirst=True, yearfirst=False, **kwargs):
    if sloppy_iso8601.match(value) is not None:
        dayfirst = False
        yearfirst = True
    return dateutil.parser.parse(value, dayfirst=dayfirst, yearfirst=yearfirst, **kwargs)

其行为符合 OP(和我自己)的预期。

>>> parse = parse_date
>>> parse("01-02-03")
datetime.datetime(2003, 2, 1, 0, 0)
>>> parse("2003-02-01")
datetime.datetime(2003, 2, 1, 0, 0)
>>>