Получить формат в dateutil.parse - PullRequest
0 голосов
/ 22 декабря 2018

Есть ли способ получить «формат» после разбора даты в dateutil.Например, что-то вроде:

>>> x = parse("2014-01-01 00:12:12")
datetime.datetime(2014, 1, 1, 0, 12, 12)

x.get_original_string_format()
YYYY-MM-DD HH:MM:SS # %Y-%m-%d %H:%M:%S

# Or, passing the date-string directly
get_original_string_format("2014-01-01 00:12:12")
YYYY-MM-DD HH:MM:SS # %Y-%m-%d %H:%M:%S

Обновление: Я хотел бы добавить вознаграждение к этому вопросу, чтобы посмотреть, может ли кто-нибудь добавить ответ, который будет эквивалентенполучение формата строки переданной общей строки даты.Он может использовать dateutil, если хотите, но это не обязательно.Надеюсь, мы найдем здесь креативные решения.

Ответы [ 6 ]

0 голосов
/ 04 января 2019

Вы можете обернуть функцию для сохранения аргументов вместе с результатом в любое время при вызове свернутой версии:

from dateutil.parser import parse
from functools import wraps

def parse_wrapper(function):
    @wraps(function)
    def wrapper(*args):
        return {'datetime': function(*args), 'args': args}
    return wrapper

wrapped_parse = parse_wrapper(parse)
x = wrapped_parse("2014-01-01 00:12:12")
# {'datetime': datetime.datetime(2014, 1, 1, 0, 12, 12),
#  'args': ('2014-01-01 00:12:12',)}
0 голосов
/ 31 декабря 2018

Идея:

  1. Проверка введенной пользователем строки даты и создание возможного формата даты, установленного
  2. Цикл по набору форматов, используйте datetime.strptime для анализа строки даты с индивидуально возможнымформат даты.
  3. Отформатируйте дату из шага 2 с помощью datetime.strftime, если результат равен строке даты происхождения, тогда этот формат является возможным форматом даты.

Реализация алгоритма

from datetime import datetime
import itertools
import re

FORMAT_CODES = (
    r'%a', r'%A', r'%w', r'%d', r'%b', r'%B', r'%m', r'%y', r'%Y',
    r'%H', r'%I', r'%p', r'%M', r'%S', r'%f', r'%z', r'%Z', r'%j',
    r'%U', r'%W',
)

TWO_LETTERS_FORMATS = (
    r'%p',
)

THREE_LETTERS_FORMATS = (
    r'%a', r'%b'
)

LONG_LETTERS_FORMATS = (
    r'%A', r'%B', r'%z', r'%Z',
)

SINGLE_DIGITS_FORMATS = (
    r'w',
)

TWO_DIGITS_FORMATS = (
    r'%d', r'%m', r'%y', r'%H', r'%I', r'%M', r'%S', r'%U', r'%W',
)

THREE_DIGITS_FORMATS = (
    r'%j',
)

FOUR_DIGITS_FORMATS = (
    r'%Y',
)

LONG_DIGITS_FORMATS = (
    r'%f',
)

# Non format code symbols
SYMBOLS = (
    '-',
    ':',
    '+',
    'Z',
    ',',
    ' ',
)


if __name__ == '__main__':
    date_str = input('Please input a date: ')

    # Split with non format code symbols
    pattern = r'[^{}]+'.format(''.join(SYMBOLS))
    components = re.findall(pattern, date_str)

    # Create a format placeholder, eg. '{}-{}-{} {}:{}:{}+{}'
    placeholder = re.sub(pattern, '{}', date_str)

    formats = []
    for comp in components:
        if re.match(r'^\d{1}$', comp):
            formats.append(SINGLE_DIGITS_FORMATS)
        elif re.match(r'^\d{2}$', comp):
            formats.append(TWO_DIGITS_FORMATS)
        elif re.match(r'^\d{3}$', comp):
            formats.append(THREE_DIGITS_FORMATS)
        elif re.match(r'^\d{4}$', comp):
            formats.append(FOUR_DIGITS_FORMATS)
        elif re.match(r'^\d{5,}$', comp):
            formats.append(LONG_DIGITS_FORMATS)
        elif re.match(r'^[a-zA-Z]{2}$', comp):
            formats.append(TWO_LETTERS_FORMATS)
        elif re.match(r'^[a-zA-Z]{3}$', comp):
            formats.append(THREE_LETTERS_FORMATS)
        elif re.match(r'^[a-zA-Z]{4,}$', comp):
            formats.append(LONG_LETTERS_FORMATS)
        else:
            formats.append(FORMAT_CODES)

    # Create a possible format set
    possible_set = itertools.product(*formats)

    found = 0
    for possible_format in possible_set:
        # Create a format with possible format combination
        dt_format = placeholder.format(*possible_format)
        try:
            dt = datetime.strptime(date_str, dt_format)
            # Use the format to parse the date, and format the 
            # date back to string and compare with the origin one
            if dt.strftime(dt_format) == date_str:
                print('Possible result: {}'.format(dt_format))
                found += 1
        except Exception:
            continue

    if found == 0:
        print('No pattern found')

Использование:

$ python3 reverse.py
Please input a date: 2018-12-31 10:26 PM
Possible result: %Y-%d-%M %I:%S %p
Possible result: %Y-%d-%S %I:%M %p
Possible result: %Y-%m-%d %I:%M %p
Possible result: %Y-%m-%d %I:%S %p
Possible result: %Y-%m-%M %I:%d %p
Possible result: %Y-%m-%M %I:%S %p
Possible result: %Y-%m-%S %I:%d %p
Possible result: %Y-%m-%S %I:%M %p
Possible result: %Y-%H-%d %m:%M %p
Possible result: %Y-%H-%d %m:%S %p
Possible result: %Y-%H-%d %M:%S %p
Possible result: %Y-%H-%d %S:%M %p
Possible result: %Y-%H-%M %d:%S %p
Possible result: %Y-%H-%M %m:%d %p
Possible result: %Y-%H-%M %m:%S %p
Possible result: %Y-%H-%M %S:%d %p
Possible result: %Y-%H-%S %d:%M %p
Possible result: %Y-%H-%S %m:%d %p
Possible result: %Y-%H-%S %m:%M %p
Possible result: %Y-%H-%S %M:%d %p
Possible result: %Y-%I-%d %m:%M %p
Possible result: %Y-%I-%d %m:%S %p
Possible result: %Y-%I-%d %M:%S %p
Possible result: %Y-%I-%d %S:%M %p
Possible result: %Y-%I-%M %d:%S %p
Possible result: %Y-%I-%M %m:%d %p
Possible result: %Y-%I-%M %m:%S %p
Possible result: %Y-%I-%M %S:%d %p
Possible result: %Y-%I-%S %d:%M %p
Possible result: %Y-%I-%S %m:%d %p
Possible result: %Y-%I-%S %m:%M %p
Possible result: %Y-%I-%S %M:%d %p
Possible result: %Y-%M-%d %I:%S %p
Possible result: %Y-%M-%S %I:%d %p
Possible result: %Y-%S-%d %I:%M %p
Possible result: %Y-%S-%M %I:%d %p
0 голосов
/ 31 декабря 2018

Моя идея состояла в том, чтобы:

  1. Создать объект, у которого есть список описателей-кандидатов, который, как вы думаете, может быть в шаблоне даты (чем больше вы добавляете, тем больше возможностейвы получите другой конец)
  2. Анализ строки даты
  3. Создание списка возможных спецификаторов для каждого элемента в строке на основе даты и списка кандидатов, которые вы предоставили.
  4. Рекомбинируйте их, чтобы получить список «возможных».

Если у вас есть только один кандидат, вы можете быть уверены, что это правильный формат.Но вы часто получаете много возможностей (особенно с датами, месяцами, минутами и часами в диапазоне 0-10).

Пример класса:

import re
from itertools import product
from dateutil.parser import parse
from collections import defaultdict, Counter

COMMON_SPECIFIERS = [
    '%a', '%A', '%d', '%b', '%B', '%m',
    '%Y', '%H', '%p', '%M', '%S', '%Z',
]


class FormatFinder:
    def __init__(self,
                 valid_specifiers=COMMON_SPECIFIERS,
                 date_element=r'([\w]+)',
                 delimiter_element=r'([\W]+)',
                 ignore_case=False):
        self.specifiers = valid_specifiers
        joined = (r'' + date_element + r"|" + delimiter_element)
        self.pattern = re.compile(joined)
        self.ignore_case = ignore_case

    def find_candidate_patterns(self, date_string):
        date = parse(date_string)
        tokens = self.pattern.findall(date_string)

        candidate_specifiers = defaultdict(list)

        for specifier in self.specifiers:
            token = date.strftime(specifier)
            candidate_specifiers[token].append(specifier)
            if self.ignore_case:
                candidate_specifiers[token.
                                     upper()] = candidate_specifiers[token]
                candidate_specifiers[token.
                                     lower()] = candidate_specifiers[token]

        options_for_each_element = []
        for (token, delimiter) in tokens:
            if token:
                if token not in candidate_specifiers:
                    options_for_each_element.append(
                        [token])  # just use this verbatim?
                else:
                    options_for_each_element.append(
                        candidate_specifiers[token])
            else:
                options_for_each_element.append([delimiter])

        for parts in product(*options_for_each_element):
            counts = Counter(parts)
            max_count = max(counts[specifier] for specifier in self.specifiers)
            if max_count > 1:
                # this is a candidate with the same item used more than once
                continue
            yield "".join(parts)

И некоторые примеры тестов:

def test_it_returns_value_from_question_1():
    s = "2014-01-01 00:12:12"
    candidates = FormatFinder().find_candidate_patterns(s)
    sut = FormatFinder()
    candidates = sut.find_candidate_patterns(s)
    assert "%Y-%m-%d %H:%M:%S" in candidates


def test_it_returns_value_from_question_2():
    s = 'Jan. 04, 2017'
    sut = FormatFinder()
    candidates = sut.find_candidate_patterns(s)
    candidates = list(candidates)
    assert "%b. %d, %Y" in candidates
    assert len(candidates) == 1


def test_it_can_ignore_case():
    # NB: apparently the 'AM/PM' is meant to be capitalised in my locale! 
    # News to me!
    s = "JANUARY 12, 2018 02:12 am"
    sut = FormatFinder(ignore_case=True)
    candidates = sut.find_candidate_patterns(s)
    assert "%B %d, %Y %H:%M %p" in candidates


def test_it_returns_parts_that_have_no_date_component_verbatim():
    # In this string, the 'at' is considered as a 'date' element, 
    # but there is no specifier that produces a candidate for it
    s = "January 12, 2018 at 02:12 AM"
    sut = FormatFinder()
    candidates = sut.find_candidate_patterns(s)
    assert "%B %d, %Y at %H:%M %p" in candidates

Чтобы сделать его более понятным, вот несколько примеров использования этого кода в оболочке iPython:

In [2]: ff = FormatFinder()

In [3]: list(ff.find_candidate_patterns("2014-01-01 00:12:12"))
Out[3]:
['%Y-%d-%m %H:%M:%S',
 '%Y-%d-%m %H:%S:%M',
 '%Y-%m-%d %H:%M:%S',
 '%Y-%m-%d %H:%S:%M']

In [4]: list(ff.find_candidate_patterns("Jan. 04, 2017"))
Out[4]: ['%b. %d, %Y']

In [5]: list(ff.find_candidate_patterns("January 12, 2018 at 02:12 AM"))
Out[5]: ['%B %d, %Y at %H:%M %p', '%B %M, %Y at %H:%d %p']

In [6]: ff_without_case = FormatFinder(ignore_case=True)

In [7]: list(ff_without_case.find_candidate_patterns("JANUARY 12, 2018 02:12 am"))
Out[7]: ['%B %d, %Y %H:%M %p', '%B %M, %Y %H:%d %p']
0 голосов
/ 29 декабря 2018

Моя идея состояла в том, чтобы создать класс, похожий на этот, может быть неточным

from datetime import datetime
import re
class DateTime(object):
    dateFormat = {"%d": "dd", "%Y": "YYYY", "%a": "Day", "%A": "DAY", "%w": "ww", "%b": "Mon", "%B": "MON", "%m": "mm",
                  "%H": "HH", "%I": "II", "%p": "pp", "%M": "MM", "%S": "SS"}  # wil contain all format equivalent

    def __init__(self, date_str, format):
        self.dateobj = datetime.strptime(date_str, format)
        self.format = format

    def parse_format(self):
        output=None
        reg = re.compile("%[A-Z a-z]")
        fmts = None
        if self.format is not None:
            fmts = re.findall(reg, self.format)
        if fmts is not None:
            output = self.format
            for f in fmts:
                output = output.replace(f, DateTime.dateFormat[f])
        return output


nDate = DateTime("12 January, 2018", "%d %B, %Y")
print(nDate.parse_format())
0 голосов
/ 29 декабря 2018

Я не знаю, как можно вернуть проанализированный формат из dateutil (или любого другого анализатора меток времени Python, о котором я знаю).

Реализация собственной функции синтаксического анализа меток времени, которая возвращаетформатирование вместе с объектом datetime довольно тривиально, используя datetime.strptime(), но эффективное его использование в отношении широко полезного списка возможных форматов меток времени - нет.

В следующем примере используется список из чуть более 50 форматов, адаптированных из одного изЛучшие хиты из быстрого поиска форматов меток времени.Он даже не царапает поверхность большого разнообразия форматов, проанализированных dateutil.Он последовательно проверяет каждый формат до тех пор, пока не найдет совпадение или не исчерпает все форматы в списке (вероятно, гораздо менее эффективный, чем dateutil подход к поиску различных частей даты и времени независимо, как отмечено в ответе от @ alecxe ).

Кроме того, я включил несколько примеров форматов меток времени, которые включают имена часовых поясов (вместо смещений).Если вы запустите приведенный ниже пример функции для этих конкретных строк даты и времени, вы можете обнаружить, что она возвращает «Unable to parse format», даже если я включил соответствующие форматы, используя директиву %Z.Некоторое объяснение проблем с использованием %Z для обработки имен часовых поясов можно найти в выпуск 22377 на bugs.python.org (просто чтобы подчеркнуть еще один нетривиальный аспект реализации вашей собственной функции синтаксического анализа даты и времени).

При всех этих предостережениях, если вы имеете дело с управляемым набором потенциальных форматов, реализация чего-то простого, подобного приведенному ниже, может дать вам то, что вам нужно.

Пример функции, которая пытается соответствоватьстрока datetime со списком форматов и возвращает объект datetime вместе с соответствующим форматом:

from datetime import datetime

def parse_timestamp(datestring, formats):
    for f in formats:
        try:
            d = datetime.strptime(datestring, f)
        except:
            continue
        return (d, f)
    return (datestring, 'Unable to parse format')

Примеры форматов и строк даты и времени, адаптированных из меток времени, часовых поясов, диапазонов времени и форматов даты :

formats = ['%Y-%m-%dT%H:%M:%S*%f%z','%Y %b %d %H:%M:%S.%f %Z','%b %d %H:%M:%S %z %Y','%d/%b/%Y:%H:%M:%S %z','%b %d, %Y %I:%M:%S %p','%b %d %Y %H:%M:%S','%b %d %H:%M:%S %Y','%b %d %H:%M:%S %z','%b %d %H:%M:%S','%Y-%m-%dT%H:%M:%S%z','%Y-%m-%dT%H:%M:%S.%f%z','%Y-%m-%d %H:%M:%S %z','%Y-%m-%d %H:%M:%S%z','%Y-%m-%d %H:%M:%S,%f','%Y/%m/%d*%H:%M:%S','%Y %b %d %H:%M:%S.%f*%Z','%Y %b %d %H:%M:%S.%f','%Y-%m-%d %H:%M:%S,%f%z','%Y-%m-%d %H:%M:%S.%f','%Y-%m-%d %H:%M:%S.%f%z','%Y-%m-%dT%H:%M:%S.%f','%Y-%m-%dT%H:%M:%S','%Y-%m-%dT%H:%M:%S%Z','%Y-%m-%dT%H:%M:%S.%f','%Y-%m-%dT%H:%M:%S','%Y-%m-%d*%H:%M:%S:%f','%Y-%m-%d*%H:%M:%S','%y-%m-%d %H:%M:%S,%f %z','%y-%m-%d %H:%M:%S,%f','%y-%m-%d %H:%M:%S','%y/%m/%d %H:%M:%S','%y%m%d %H:%M:%S','%Y%m%d %H:%M:%S.%f','%m/%d/%y*%H:%M:%S','%m/%d/%Y*%H:%M:%S','%m/%d/%Y*%H:%M:%S*%f','%m/%d/%y %H:%M:%S %z','%m/%d/%Y %H:%M:%S %z','%H:%M:%S','%H:%M:%S.%f','%H:%M:%S,%f','%d/%b %H:%M:%S,%f','%d/%b/%Y:%H:%M:%S','%d/%b/%Y %H:%M:%S','%d-%b-%Y %H:%M:%S','%d-%b-%Y %H:%M:%S.%f','%d %b %Y %H:%M:%S','%d %b %Y %H:%M:%S*%f','%m%d_%H:%M:%S','%m%d_%H:%M:%S.%f','%m/%d/%Y %I:%M:%S %p:%f','%m/%d/%Y %H:%M:%S %p']

datestrings = ['2018-08-20T13:20:10*633+0000','2017 Mar 03 05:12:41.211 PDT','Jan 21 18:20:11 +0000 2017','19/Apr/2017:06:36:15 -0700','Dec 2, 2017 2:39:58 AM','Jun 09 2018 15:28:14','Apr 20 00:00:35 2010','Sep 28 19:00:00 +0000','Mar 16 08:12:04','2017-10-14T22:11:20+0000','2017-07-01T14:59:55.711+0000','2017-08-19 12:17:55 -0400','2017-08-19 12:17:55-0400','2017-06-26 02:31:29,573','2017/04/12*19:37:50','2018 Apr 13 22:08:13.211*PDT','2017 Mar 10 01:44:20.392','2017-03-10 14:30:12,655+0000','2018-02-27 15:35:20.311','2017-03-12 13:11:34.222-0700','2017-07-22T16:28:55.444','2017-09-08T03:13:10','2017-03-12T17:56:22-0700','2017-11-22T10:10:15.455','2017-02-11T18:31:44','2017-10-30*02:47:33:899','2017-07-04*13:23:55','11-02-11 16:47:35,985 +0000','10-06-26 02:31:29,573','10-04-19 12:00:17','06/01/22 04:11:05','150423 11:42:35','20150423 11:42:35.173','08/10/11*13:33:56','11/22/2017*05:13:11','05/09/2017*08:22:14*612','04/23/17 04:34:22 +0000','10/03/2017 07:29:46 -0700','11:42:35','11:42:35.173','11:42:35,173','23/Apr 11:42:35,173','23/Apr/2017:11:42:35','23/Apr/2017 11:42:35','23-Apr-2017 11:42:35','23-Apr-2017 11:42:35.883','23 Apr 2017 11:42:35','23 Apr 2017 10:32:35*311','0423_11:42:35','0423_11:42:35.883','8/5/2011 3:31:18 AM:234','9/28/2011 2:23:15 PM']

Пример использования:

print(parse_timestamp(datestrings[0], formats))
# OUTPUT
# (datetime.datetime(2018, 8, 20, 13, 20, 10, 633000, tzinfo=datetime.timezone.utc), '%Y-%m-%dT%H:%M:%S*%f%z')
0 голосов
/ 22 декабря 2018

Есть ли способ получить "формат" после анализа даты в dateutil?

Невозможно с dateutil.Проблема в том, что dateutil никогда не имеет формат в качестве промежуточного результата в любое время во время синтаксического анализа, так как он обнаруживает отдельные компоненты datetime по отдельности - взгляните на этот не совсем легко читаемый источниккод .

...