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

使用Python以北欧格式(先是DMY,然后是YMD)解析日期的最佳方法,python,date-parsing,Python,Date Parsing,我正在寻找一种按照优先顺序使用以下“元格式”解析未知格式日期的方法: 年月日(DMY) 年-月-日(YMD) 潜在的其他格式(但这并不重要) 这是挪威、丹麦、芬兰和荷兰几乎所有发票上的实际元格式,因此它应该是一个常见的用例。然而,似乎没有任何一个库能够处理它,而不必定义大量可能的格式列表 具体来说。我需要一个方法(parse)来满足以下要求: 解析(“01-02-03”)=“datetime.datetime(2003,2,1,0,0)” 解析(“2003-02-01”)=“datetime.

我正在寻找一种按照优先顺序使用以下“元格式”解析未知格式日期的方法:

  • 年月日(DMY)
  • 年-月-日(YMD)
  • 潜在的其他格式(但这并不重要)
  • 这是挪威、丹麦、芬兰和荷兰几乎所有发票上的实际元格式,因此它应该是一个常见的用例。然而,似乎没有任何一个库能够处理它,而不必定义大量可能的格式列表

    具体来说。我需要一个方法(
    parse
    )来满足以下要求:
    
    解析(“01-02-03”)=“datetime.datetime(2003,2,1,0,0)”
    解析(“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格式,但这是一个好的开始

    你试过使用熊猫吗?Imho这是导入日期的最佳和最干净的方法,因为它在99%的情况下都是开箱即用的,而像dateutil这样的大多数其他方法往往会失败

    import pandas as pd
    pd.to_datetime('01-02-03', dayfirst=True)
    pd.to_datetime('2003-02-01', dayfirst=True)
    
    pandas的另一个优点是,它可以处理数组、列表和大多数其他类型,甚至支持使用datetime索引对数组(称为DataFrames)进行字符串索引

    有关如何使用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]>
    
    arrow.get(“01-02-03”,“DD-MM-YY”)
    #给予
    箭头。get(“01-02-03”,“YY-MM-DD”)
    #给予
    
    正如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优先于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
    的“月优先”行为,因此我现在使用以下方法:

    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(和我自己)所期望的一样


    如果没有为每种情况指定参数,这并不能在所有情况下都起作用<代码>解析('2003-02-01',dayfirst=True)生成
    datetime.datetime(2003,1,2,0,0)
    ,这是不正确的。在第一种情况下,省略
    dayfirst=True
    会产生一个错误的日期。是的,但是再次-确定用户键入日期时的含义并不容易。例如,考虑<代码> 01 / 02 / 03 < /代码> -你如何知道哪一个是一天、一个月和一年?在美国可能是2003年第1个月第2天,在欧洲可能是2003年第2个月第1天。如果没有额外的提示,猜测是不可能的。他表示,他主要使用挪威、丹麦、芬兰和荷兰使用的格式,通常是
    dayfirst=True
    或ISO date。如果有使用美国日期的特殊情况,在没有指定其他信息的情况下,无法解析它,但这应被视为特殊情况。困难在于,即使使用最简单的情况-可能的日期格式字符串列表,你一个接一个地尝试,仍然有可能得到一个有效的结果。例如,无法猜测哪个月是从
    01/02/2003
    开始的,但可以猜测
    01/13/2003
    开始的月份。如果您正在解析用户输入,您可以通过查看区域设置来帮助自己。如果是一个网站,您可以查看请求的来源国(或查看
    Accept Language
    headers)。对于发票,您可以使用发票元数据-发票颁发者的国家/地区。如果您能够提出使用本地语言的解决方案,这是一个非常有效的解决方案。正如我提到的,我确实知道日期是北欧的。这个问题的作者似乎在寻找一个通用的“魔法”函数,它可以猜测格式,而不需要指定格式。哦,我明白了。也许可以使用已知格式的列表并解析每种格式的日期?是的,或者可以像下面的
    箭头那样链接它们。get('01-02-03',['DD-MM-YY','YY-MM-DD'])
    ,但是当尝试扩展到一般情况时,它很快就失控了。仅对于DMY,一个简单的最小值看起来像这样
    'DDMMYY','DD-MM-YY','DD/MM/YY','DD.MM.YY','DDMMYYYY','DD-MM-YYYY','DD.MM.YYYY'
    (当然可以通过编程方式构建)。如果没有通用的日期分析器,这是一个很好的解决方案。谢谢事实证明,
    arrow
    不是按顺序尝试多种格式的合适库,但是像
    dateparser
    这样的库可以很好地实现这一点。下面是一个示例,说明了
    arrow
    失败的原因:
    arrow.get('2003-02-01',['DD-MM-YY','yyyyy-MM-DD'])
    返回
    这是以一般方式解决问题。谢谢斯科蒂。但让我告诉你一件事
    >>> 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)
    >>>