Лучший способ разобрать даты в североевропейском формате (сначала DMY, затем YMD), используя Python - PullRequest
0 голосов
/ 09 мая 2018

Я ищу способ анализа дат неизвестных форматов , используя следующие «мета-форматы» в порядке предпочтения:

  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.

Ответы [ 5 ]

0 голосов
/ 25 мая 2018

Я попробовал pandas, и я был озадачен, когда увидел, что он загружается (11,6 МБ), и каково было мое удивление, когда он также начал загружать numpy (12,1 МБ).

Но, как европеец, мне не нужно поведение по умолчанию "месяц-месяц" 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)

, который ведет себя так, как ожидают ОП (и я).

>>> 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)
>>> 
0 голосов
/ 09 мая 2018

Как правильно заметил Скотти1, 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)

Поддержка "YMD предпочтительнее DMY" может быть достигнута путем замены dmy + ymd на 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)
0 голосов
/ 09 мая 2018

Вы пробовали использовать pandas? Имхо, это лучший и самый чистый способ импортировать даты, так как он работает «из коробки» в 99% случаев, в то время как большинство других вещей, таких как dateutil, не работают.

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

Еще одним преимуществом pandas является то, что он будет работать с массивами, списками и большинством других типов и даже поддерживает индексирование строк для массивов (называемых DataFrames) с datetime-index.

Еще немного информации о том, как получить формат datetime.datetime с пандами:
Просто добавьте .to_pydatetime() к вашему парсеру.

pd.to_datetime('2003-02-01', dayfirst=True).to_pydatetime()
# Out[]: datetime.datetime(2003, 2, 1, 0, 0)
0 голосов
/ 09 мая 2018

Проверьте arrow библиотеку в Python. Вы можете указать формат даты в любом формате, который вам нравится. Например:

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]>
0 голосов
/ 09 мая 2018

Посмотрите на 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, но это хорошее начало. Посмотрите на документацию для большего количества примеров .

...