Как мне разобрать дату в формате ISO 8601?


643

Мне нужно разобрать строки RFC 3339, как "2008-09-03T20:56:35.450686Z"в datetimeтип Python .

Я нашел strptimeв стандартной библиотеке Python, но это не очень удобно.

Каков наилучший способ сделать это?




3
Для ясности: ISO 8601 является основным стандартом. RFC 3339 - это самопровозглашенный «профиль» ISO 8601, который делает некоторые неразумные изменения правил ISO 8601.
Базилик Бурк

3
Не пропустите приведенное ниже решение python3.7 + для инвертирования изоформата ()
Брэд М

2
Этот вопрос не следует закрывать как дупе к связанному посту. Так как этот просит проанализировать строку времени ISO 8601 (которая изначально не поддерживалась python до 3.7), а другой - отформатировать объект datetime в строку эпохи, используя устаревший метод.
18:15

Ответы:


461

Пакет python-dateutil может анализировать не только строки даты и времени RFC 3339, как в вопросе, но также и другие строки даты и времени ISO 8601, которые не соответствуют RFC 3339 (например, те, которые не имеют смещения UTC, или те, которые представляют только свидание).

>>> import dateutil.parser
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)

Обратите внимание, что dateutil.parser.isoparseпредположительно строже, чем более хакерскийdateutil.parser.parse , но оба они довольно просты и попытаются интерпретировать передаваемую вами строку. Если вы хотите исключить возможность каких-либо неправильных прочтений, вам нужно использовать что-то более строгое, чем любой из этих функции.

Имя Pypi python-dateutilне dateutil(спасибо code3monk3y ):

pip install python-dateutil

Если вы используете Python 3.7, взглянуть на этот ответ о datetime.datetime.fromisoformat.


75
Для ленивых, он установлен с помощью python-dateutilне dateutilтак: pip install python-dateutil.
cod3monk3y

29
Имейте в виду, что dateutil.parserон намеренно взломан: он пытается угадать формат и делает неизбежные предположения (настраиваемые только вручную) в неоднозначных случаях. Так что используйте его ТОЛЬКО, если вам нужно разобрать ввод неизвестного формата, и вы можете терпеть случайные неправильные чтения.
ivan_pozdeev

2
Согласовано. Например, передается «дата» 9999. Это будет возвращать то же самое, что и дата-время (9999, текущий месяц, текущий день). На мой взгляд, недопустимая дата.
Тимбо

1
@ivan_pozdeev, какой пакет вы бы порекомендовали для не угадывающего анализа?
bgusach

2
@ivan_pozdeev есть обновление модуля, который читает даты iso8601
theEpsilon

198

Новое в Python 3.7+


В datetimeстандартной библиотеке появилась функция инвертирования datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Вернуть datetimeсоответствующий a date_stringв одном из форматов date.isoformat()иdatetime.isoformat() .

В частности, эта функция поддерживает строки в формате (ах):

YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]

где *может соответствовать любой отдельный символ.

Внимание : это не поддерживает разбор произвольных строк ISO 8601 - оно предназначено только как обратная операция datetime.isoformat().

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

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')

6
Это странно. Поскольку a datetimeможет содержать a tzinfoи, следовательно, выводить часовой пояс, но datetime.fromisoformat()не анализирует tzinfo? похоже на ошибку ..
Хенди Ираван

20
Не пропустите эту заметку в документации, она не принимает все допустимые строки ISO 8601, только те, которые сгенерированы isoformat. Он не принимает пример в вопросе "2008-09-03T20:56:35.450686Z"из-за трейлинга Z, но он принимает "2008-09-03T20:56:35.450686".
Flimm

26
Для правильной поддержки Zвходной скрипт можно изменить с помощью date_string.replace("Z", "+00:00").
Jox

7
Обратите внимание, что за секунды он обрабатывает только 0, 3 или 6 десятичных знаков. Если входные данные имеют 1, 2, 4, 5, 7 или более десятичных знаков, синтаксический анализ завершится неудачно!
Felk

1
@JDOaktown В этом примере используется нативная библиотека даты и времени Python, а не парсер dateutil. Это на самом деле потерпит неудачу, если при таком подходе десятичные разряды не равны 0, 3 или 6.
abccd

174

Обратите внимание, что в Python 2.6+ и Py3K символ% f перехватывает микросекунды.

>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")

Смотрите проблему здесь


4
Примечание - если вы используете наивные даты и времени - я думаю, что вы вообще не получите TZ - Z может не совпадать ни с чем.
Дэнни Стейпл

24
Этот ответ (в его текущей отредактированной форме) основан на жестком кодировании определенного смещения UTC (а именно «Z», что означает +00: 00) в строку формата. Это плохая идея, потому что она не сможет проанализировать любое время и дату с другим смещением UTC и вызвать исключение. Смотрите мой ответ, который описывает, как strptimeна самом деле невозможно выполнить разбор RFC 3339 .
Марк Амери

1
в моем случае% f поймал микросекунды, а не Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') так что
сработало

Py3K означает Python 3000?!?
Робиньо

2
@Robino IIRC, «Python 3000» - это старое название для того, что сейчас известно как Python 3.
Бросок аккаунта

161

Несколько ответов здесь предлагают использовать datetime.datetime.strptimeдля анализа времени RFC 3339 или ISO 8601 с часовыми поясами, как показано в вопросе:

2008-09-03T20:56:35.450686Z

Это плохая идея.

Предполагая, что вы хотите поддерживать полный формат RFC 3339, включая поддержку смещений UTC, отличных от нуля, код, предлагаемый этими ответами, не работает. Действительно, это не может работать, потому что синтаксический анализ RFC 3339 с использованиемstrptime невозможен. Строки формата, используемые модулем datetime в Python, не могут описать синтаксис RFC 3339.

Проблема в смещениях UTC. RFC 3339 Интернет - формат даты / времени требует , чтобы каждая дата-время включает в себя UTC смещение, и что эти смещения могут быть либо Z(сокращенно «Зулу времени») или в +HH:MMили -HH:MMформате, как +05:00и -10:30.

Следовательно, все они являются действительными датами времени RFC 3339:

  • 2008-09-03T20:56:35.450686Z
  • 2008-09-03T20:56:35.450686+05:00
  • 2008-09-03T20:56:35.450686-10:30

Увы, строки формата используются strptimeи не strftimeимеют директив, соответствующих смещениям UTC в формате RFC 3339. Полный список директив, которые они поддерживают, можно найти по адресу https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , и единственная директива смещения UTC, включенная в список %z:

% г

Смещение UTC в форме + ЧЧММ или -ЧЧММ (пустая строка, если объект наивный).

Пример: (пусто), +0000, -0400, +1030

Это не соответствует формату смещения RFC 3339, и действительно, если мы попытаемся использовать %zв строке формата и проанализировать дату RFC 3339, у нас не получится:

>>> from datetime import datetime
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686Z' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'

(На самом деле, вышесказанное - это то, что вы увидите в Python 3. В Python 2 мы потерпим неудачу по еще более простой причине, заключающейся в том, что в Python 2 директива strptimeвообще не реализуется.%z )

Несколько ответов здесь, которые рекомендуют strptimeвсем обойти это, путем включения литерала Zв их строку формата, которая совпадает Zсо строкой даты и времени из примера автора вопроса (и отбрасывает ее, создаваяdatetime объект без часового пояса):

>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

Поскольку при этом отбрасывается информация о часовом поясе, которая была включена в исходную строку даты и времени, сомнительно, должны ли мы рассматривать даже этот результат как правильный. Но что более важно, потому что этот подход включает в себя жесткое кодирование определенного смещения UTC в строку формата , он будет подавлен в тот момент, когда попытается проанализировать дату / время RFC 3339 с другим смещением UTC:

>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%fZ")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%fZ'

Если вы не уверены, что вам нужно только поддерживать время RFC 3339 по времени Зулу, а не время с другими смещениями часового пояса, не используйте strptime. Вместо этого используйте один из многих других подходов, описанных в ответах.


79
Сногсшибательно, почему strptime не имеет директивы для информации о часовом поясе в формате ISO и почему его нельзя проанализировать. Невероятный.
Чаба Тот

2
@CsabaToth Полностью согласен - если у меня будет время, чтобы убить, возможно, я попытаюсь добавить его в язык. Или вы могли бы сделать это, если бы вы были так склонны - я вижу, у вас есть некоторый опыт C, в отличие от меня.
Марк Эмери

1
@ CsabaToth - Почему невероятно? Это работает достаточно хорошо для большинства людей, или они нашли достаточно легкий обходной путь. Если вам нужна функция, это с открытым исходным кодом, и вы можете добавить ее. Или заплатите кому-нибудь, чтобы сделать это для вас. Почему кто-то должен добровольно посвятить свое свободное время решению ваших конкретных проблем? Пусть источник будет с вами.
Питер М. - выступает за Монику

2
@PeterMasiar Невероятно, потому что обычно обнаруживается, что вещи в Python реализованы вдумчиво и полностью. Мы были испорчены этим вниманием к деталям, и поэтому, когда мы натыкаемся на что-то на языке, который «не пифоничен», мы выбрасываем наши игрушки из коляски, как я собираюсь сделать это прямо сейчас. Whaaaaaaaaaa Whaa wahaaaaa :-(
Робиньо

2
strptime()в Python 3.7 теперь поддерживает все, что описано как невозможное в этом ответе (буквально 'Z' и ':' в смещении часового пояса). К сожалению, есть еще один угловой случай, который делает RFC 3339 принципиально несовместимым с ISO 8601, а именно первый допускает отрицательное нулевое смещение часового пояса -00: 00, а последний нет.
СергейКолесников

75

Попробуйте модуль iso8601 ; это делает именно это.

Есть несколько других вариантов , упомянутых на WorkingWithTime странице на python.org вики.


Просто какiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Пакман

3
Вопрос был не «как мне разобрать даты ISO 8601», а «как мне разобрать этот точный формат даты».
Николас Райли

3
@tiktak ОП спросил: «Мне нужно разобрать строки, такие как X», и мой ответ на это, попробовав обе библиотеки, состоит в том, чтобы использовать другую, потому что у iso8601 все еще остаются важные проблемы. Мое участие или отсутствие такового в таком проекте совершенно не связано с ответом.
Tobia

2
Имейте в виду, что версия iso8601 в пипсах не обновлялась с 2007 года, и в ней есть некоторые серьезные ошибки. Я рекомендую применить некоторые критические исправления самостоятельно или найти одну из многих вилок github, которые уже сделали это github.com/keithhackbarth/pyiso8601-strict
keithhackbarth

6
iso8601 , также известный как pyiso8601 , был обновлен совсем недавно, в феврале 2014 года. Последняя версия поддерживает гораздо более широкий набор строк ISO 8601. Я использовал с хорошим эффектом в некоторых из моих проектов.
Дейв Хейн

34
импорт ре, дата и время
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))

73
Я не согласен, это практически нечитаемо, и, насколько я могу судить, не учитывает Zulu (Z), который делает эту дату-время наивной, даже если были предоставлены данные часового пояса.
Umbrae

14
Я нахожу это вполне читабельным. На самом деле, это, вероятно, самый простой и эффективный способ преобразования без установки дополнительных пакетов.
Тобиа

2
Это эквивалентно d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])) я полагаю.
Сюань

4
вариация:datetime.datetime(*map(int, re.findall('\d+', s))
JFS

3
Это приводит к наивному объекту datetime без часового пояса, верно? Таким образом, бит UTC теряется в переводе?
w00t

32

Какую именно ошибку вы получаете? Это похоже на следующее?

>>> datetime.datetime.strptime("2008-08-12T12:20:30.656234Z", "%Y-%m-%dT%H:%M:%S.Z")
ValueError: time data did not match format:  data=2008-08-12T12:20:30.656234Z  fmt=%Y-%m-%dT%H:%M:%S.Z

Если да, вы можете разделить вашу входную строку на «.», А затем добавить микросекунды к полученному времени.

Попробуй это:

>>> def gt(dt_str):
        dt, _, us= dt_str.partition(".")
        dt= datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S")
        us= int(us.rstrip("Z"), 10)
        return dt + datetime.timedelta(microseconds=us)

>>> gt("2008-08-12T12:20:30.656234Z")
datetime.datetime(2008, 8, 12, 12, 20, 30, 656234)

10
Вы не можете просто раздеться .Z, потому что это означает часовой пояс и может быть другим. Мне нужно преобразовать дату в часовой пояс UTC.
Александр Артеменко

Простой объект даты и времени не имеет понятия часового пояса. Если все ваши времена заканчиваются на «Z», все получаемые вами даты - UTC (время Зулу).
tzot

если часовой пояс отличается от ""или "Z", то это должно быть смещение в часах / минутах, которое может быть непосредственно добавлено / вычтено из объекта datetime. Вы могли бы создать подкласс tzinfo, чтобы справиться с этим, но это, вероятно, не рекомендуется.
SingleNegationElimination

8
Кроме того, "% f" - это спецификатор микросекунды, поэтому строка strptime (без часовых поясов) выглядит следующим образом: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor

1
Это вызовет исключение, если заданная строка даты и времени имеет смещение UTC, отличное от «Z». Он не поддерживает весь формат RFC 3339 и является неполноценным ответом для других, которые правильно обрабатывают смещения UTC.
Марк Амери

25

Начиная с Python 3.7, strptime поддерживает разделители двоеточий в смещениях UTC ( источник ). Таким образом, вы можете использовать:

import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')

РЕДАКТИРОВАТЬ:

Как отметил Мартин, если вы создали объект datetime с помощью isoformat (), вы можете просто использовать datetime.fromisoformat ()


4
Но в 3.7, вы также имеете datetime.fromisoformat()какие ручки строки , как автоматически ваш вход: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Мартин Питерс

2
Хорошая точка зрения. Я согласен, я рекомендую использовать datetime.fromisoformat()иdatetime.isoformat()
Andreas Profous

19

В наши дни Arrow также можно использовать как стороннее решение:

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())

6
Стрелка не поддерживает ISO8601 должным образом: github.com/crsmithdev/arrow/issues/291
штучной упаковке

1
Просто используйте python-dateutil - стрелка требует python-dateutil.
Данизен

Стрелка теперь поддерживает ISO8601. Упомянутые проблемы сейчас закрыты.
Altus

18

Просто используйте python-dateutilмодуль:

>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> print(parsed_t)
datetime.datetime(1984, 6, 2, 19, 5, tzinfo=tzutc())

Документация


1
Разве это не точно ответ @Flimms выше?
Лев

1
Где вы видите его разбор в считанные секунды? Я нашел эту статью, пытаясь получить время эпохи, поэтому я полагал, что кто-то еще будет так же.
Blairg23

1
Это не UTC в моей системе. Скорее, вывод в секундах - это время эпохи Unix, как если бы дата была в моем местном часовом поясе.
Эллиот

1
Этот ответ содержит ошибки и не должен быть принят. Вероятно, весь вопрос должен быть помечен как дубликат stackoverflow.com/questions/11743019/…
tripleee

@tripleee На самом деле я только что проверил код, и он, кажется, возвращает правильный ответ: 455051100(проверено на epochconverter.com ) ,,, разве я что-то упустил?
Blairg23

13

Если вы не хотите использовать dateutil, вы можете попробовать эту функцию:

def from_utc(utcTime,fmt="%Y-%m-%dT%H:%M:%S.%fZ"):
    """
    Convert UTC time string to time.struct_time
    """
    # change datetime.datetime to time, return time.struct_time type
    return datetime.datetime.strptime(utcTime, fmt)

Тестовое задание:

from_utc("2007-03-04T21:08:12.123Z")

Результат:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)

5
Этот ответ основан на жестком кодировании определенного смещения UTC (а именно «Z», что означает +00: 00) в строку формата, переданную в strptime. Это плохая идея, потому что она не сможет проанализировать любое время и дату с другим смещением UTC и вызвать исключение. Посмотрите мой ответ, который описывает, как на самом деле невозможно проанализировать RFC 3339 с помощью strptime.
Марк Амери

1
Он жестко запрограммирован, но его достаточно для случая, когда нужно разобрать только зулу.
Саша

1
@alexander yes - это может иметь место, если, например, вы знаете, что ваша строка даты была сгенерирована toISOStringметодом JavaScript . Но в этом ответе нет упоминания об ограничении дат времени зулусов, и при этом вопрос не указывает, что это все, что нужно, и простое использование dateutilобычно одинаково удобно и менее узко в том, что он может анализировать.
Марк Амери

11

Если вы работаете с Django, он предоставляет модуль dateparse, который принимает множество форматов, похожих на формат ISO, включая часовой пояс.

Если вы не используете Django и не хотите использовать одну из других библиотек, упомянутых здесь, вы, вероятно, можете адаптировать исходный код Django для dateparse для вашего проекта.


Джанго DateTimeFieldиспользует это, когда вы устанавливаете строковое значение.
DJVG

11

Я обнаружил, что ciso8601 - это самый быстрый способ анализа временных меток ISO 8601. Как следует из названия, он реализован на C.

import ciso8601
ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')

GitHub Repo README показывает их> 10x ускорение по отношению ко всем другим библиотекам , перечисленных в других ответах.

Мой личный проект включал много разбора ISO 8601. Было приятно иметь возможность просто переключать вызов и идти в 10 раз быстрее. :)

Изменить: с тех пор я стал сопровождающим ciso8601. Теперь быстрее, чем когда-либо!


Это похоже на отличную библиотеку! К сожалению, для тех, кто хочет оптимизировать синтаксический анализ ISO8601 в Google App Engine, мы не можем его использовать, поскольку это библиотека C, но ваши тесты были полезны, чтобы показать, что native datetime.strptime()является следующим самым быстрым решением. Спасибо, что собрали всю эту информацию!
hamx0r

3
@ hamx0r, учтите, что datetime.strptime()это не полная библиотека синтаксического анализа ISO 8601. Если вы используете Python 3.7, вы можете использовать datetime.fromisoformat()метод, который немного более гибкий. Возможно, вас заинтересует этот более полный список парсеров, который вскоре должен быть объединен с README ciso8601.
movermeyer

ciso8601 работает довольно хорошо, но сначала нужно выполнить «pip install pytz», потому что невозможно проанализировать метку времени с информацией о часовом поясе без зависимости pytz. Пример будет выглядеть так: dob = ciso8601.parse_datetime (result ['dob'] ['date'])
Дирк

2
@Dirk, только в Python 2 . Но даже это должно быть удалено в следующем выпуске.
movermeyer

8

Это работает для stdlib на Python 3.2 и более поздних версиях (при условии, что все метки времени указаны в формате UTC):

from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
    tzinfo=timezone(timedelta(0)))

Например,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)

2
Этот ответ основан на жестком кодировании определенного смещения UTC (а именно «Z», что означает +00: 00) в строку формата, переданную в strptime. Это плохая идея, потому что она не сможет проанализировать любое время и дату с другим смещением UTC и вызвать исключение. Посмотрите мой ответ, который описывает, как на самом деле невозможно проанализировать RFC 3339 с помощью strptime.
Марк Амери

1
В теории да, это не удается. На практике я никогда не встречал дату в формате ISO 8601, которая была не во время зулу. Для моей очень редкой потребности это прекрасно работает и не зависит от какой-либо внешней библиотеки.
Бенджамин Риггс

4
Вы могли бы использовать timezone.utcвместо timezone(timedelta(0)). Кроме того, код работает в Python 2.6+ (по крайней мере), если вы предоставляете utcобъект tzinfo
jfs

Не имеет значения, если вы столкнулись с этим, это не соответствует спецификации.
thennouncer

Вы можете использовать %Zдля часового пояса в самых последних версиях Python.
Свентечие

7

Я автор утилит iso8601. Его можно найти на GitHub или PyPI . Вот как вы можете разобрать свой пример:

>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

6

Одним простым способом преобразования строки даты, подобной ISO 8601, в метку времени UNIX или datetime.datetimeобъект во всех поддерживаемых версиях Python без установки сторонних модулей является использование анализатора даты SQLite .

#!/usr/bin/env python
from __future__ import with_statement, division, print_function
import sqlite3
import datetime

testtimes = [
    "2016-08-25T16:01:26.123456Z",
    "2016-08-25T16:01:29",
]
db = sqlite3.connect(":memory:")
c = db.cursor()
for timestring in testtimes:
    c.execute("SELECT strftime('%s', ?)", (timestring,))
    converted = c.fetchone()[0]
    print("%s is %s after epoch" % (timestring, converted))
    dt = datetime.datetime.fromtimestamp(int(converted))
    print("datetime is %s" % dt)

Вывод:

2016-08-25T16:01:26.123456Z is 1472140886 after epoch
datetime is 2016-08-25 12:01:26
2016-08-25T16:01:29 is 1472140889 after epoch
datetime is 2016-08-25 12:01:29

11
Спасибо. Это отвратительно. Я люблю это.
wchargin

1
Какой невероятный, удивительный, красивый взлом! Спасибо!
Havok

6

Я написал парсер для стандарта ISO 8601 и разместил его на GitHub: https://github.com/boxed/iso8601 . Эта реализация поддерживает все в спецификации, кроме длительностей, интервалов, периодических интервалов и дат вне поддерживаемого диапазона дат модуля Python datetime.

Тесты включены! :П



6

Функция parse_datetime () в Django поддерживает даты со смещением UTC:

parse_datetime('2016-08-09T15:12:03.65478Z') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)

Таким образом, его можно использовать для анализа дат ISO 8601 в полях всего проекта:

from django.utils import formats
from django.forms.fields import DateTimeField
from django.utils.dateparse import parse_datetime

class DateTimeFieldFixed(DateTimeField):
    def strptime(self, value, format):
        if format == 'iso-8601':
            return parse_datetime(value)
        return super().strptime(value, format)

DateTimeField.strptime = DateTimeFieldFixed.strptime
formats.ISO_INPUT_FORMATS['DATETIME_INPUT_FORMATS'].insert(0, 'iso-8601')

4

Потому что ISO 8601 допускает множество вариаций необязательных двоеточий и тире CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Если вы хотите использовать strptime, вы должны сначала удалить эти варианты.

Цель состоит в том, чтобы сгенерировать объект utc datetime.


Если вам нужен базовый случай, который работает для UTC с суффиксом Z, например 2016-06-29T19:36:29.3453Z:

datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")


Если вы хотите обрабатывать смещения часового пояса, например 2016-06-29T19:36:29.3453-0400или 2008-09-03T20:56:35.450686+05:00используйте следующее. Они преобразуют все варианты во что-то без разделителей переменных, например, 20080903T205635.450686+0500делая его более согласованным / более простым для анализа.

import re
# this regex removes all colons and all 
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', timestamp)
datetime.datetime.strptime(conformed_timestamp, "%Y%m%dT%H%M%S.%f%z" )


Если ваша система не поддерживает %zдирективу strptime (вы видите что-то подобное ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'), вам нужно вручную сместить время от Z(UTC). Примечание %zможет не работать в вашей системе в версиях Python <3, поскольку это зависит от поддержки библиотеки c, которая варьируется в зависимости от типа сборки системы / python (например, Jython, Cython и т. Д.).

import re
import datetime

# this regex removes all colons and all 
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', timestamp)

# split on the offset to remove it. use a capture group to keep the delimiter
split_timestamp = re.split(r"[+|-]",conformed_timestamp)
main_timestamp = split_timestamp[0]
if len(split_timestamp) == 3:
    sign = split_timestamp[1]
    offset = split_timestamp[2]
else:
    sign = None
    offset = None

# generate the datetime object without the offset at UTC time
output_datetime = datetime.datetime.strptime(main_timestamp +"Z", "%Y%m%dT%H%M%S.%fZ" )
if offset:
    # create timedelta based on offset
    offset_delta = datetime.timedelta(hours=int(sign+offset[:-2]), minutes=int(sign+offset[-2:]))
    # offset datetime with timedelta
    output_datetime = output_datetime + offset_delta

2

Для чего-то, что работает со стандартной библиотекой 2.X, попробуйте:

calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))

calendar.timegm - это отсутствующая версия gm time.mktime.


1
Это просто игнорирует часовой пояс '2013-01-28T14: 01: 01.335612-08: 00' -> проанализированный как UTC, а не PDT
gatoatigrado

2

Python-dateutil будет генерировать исключение при разборе недопустимых строк даты, поэтому вы можете захотеть перехватить исключение.

from dateutil import parser
ds = '2012-60-31'
try:
  dt = parser.parse(ds)
except ValueError, e:
  print '"%s" is an invalid date' % ds

2

В настоящее время существует Maya: Datetimes for Humans ™ , от автора популярного пакета Requests: HTTP for Humans ™:

>>> import maya
>>> str = '2008-09-03T20:56:35.450686Z'
>>> maya.MayaDT.from_rfc3339(str).datetime()
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=<UTC>)

2

Другой способ заключается в использовании специализированного парсер для ISO-8601 является использование isoparse функции dateutil парсер:

from dateutil import parser

date = parser.isoparse("2008-09-03T20:56:35.450686+01:00")
print(date)

Вывод:

2008-09-03 20:56:35.450686+01:00

Эта функция также упоминается в документации для стандартной функции Python datetime.fromisoformat :

Более полнофункциональный анализатор ISO 8601, dateutil.parser.isoparse доступен в пакете dateutil стороннего производителя.


1

Благодаря ответу великого Марка Эмери я разработал функцию учета всех возможных форматов ISO даты и времени:

class FixedOffset(tzinfo):
    """Fixed offset in minutes: `time = utc_time + utc_offset`."""
    def __init__(self, offset):
        self.__offset = timedelta(minutes=offset)
        hours, minutes = divmod(offset, 60)
        #NOTE: the last part is to remind about deprecated POSIX GMT+h timezones
        #  that have the opposite sign in the name;
        #  the corresponding numeric value is not used e.g., no minutes
        self.__name = '<%+03d%02d>%+d' % (hours, minutes, -hours)
    def utcoffset(self, dt=None):
        return self.__offset
    def tzname(self, dt=None):
        return self.__name
    def dst(self, dt=None):
        return timedelta(0)
    def __repr__(self):
        return 'FixedOffset(%d)' % (self.utcoffset().total_seconds() / 60)
    def __getinitargs__(self):
        return (self.__offset.total_seconds()/60,)

def parse_isoformat_datetime(isodatetime):
    try:
        return datetime.strptime(isodatetime, '%Y-%m-%dT%H:%M:%S.%f')
    except ValueError:
        pass
    try:
        return datetime.strptime(isodatetime, '%Y-%m-%dT%H:%M:%S')
    except ValueError:
        pass
    pat = r'(.*?[+-]\d{2}):(\d{2})'
    temp = re.sub(pat, r'\1\2', isodatetime)
    naive_date_str = temp[:-5]
    offset_str = temp[-5:]
    naive_dt = datetime.strptime(naive_date_str, '%Y-%m-%dT%H:%M:%S.%f')
    offset = int(offset_str[-4:-2])*60 + int(offset_str[-2:])
    if offset_str[0] == "-":
        offset = -offset
    return naive_dt.replace(tzinfo=FixedOffset(offset))

0
def parseISO8601DateTime(datetimeStr):
    import time
    from datetime import datetime, timedelta

    def log_date_string(when):
        gmt = time.gmtime(when)
        if time.daylight and gmt[8]:
            tz = time.altzone
        else:
            tz = time.timezone
        if tz > 0:
            neg = 1
        else:
            neg = 0
            tz = -tz
        h, rem = divmod(tz, 3600)
        m, rem = divmod(rem, 60)
        if neg:
            offset = '-%02d%02d' % (h, m)
        else:
            offset = '+%02d%02d' % (h, m)

        return time.strftime('%d/%b/%Y:%H:%M:%S ', gmt) + offset

    dt = datetime.strptime(datetimeStr, '%Y-%m-%dT%H:%M:%S.%fZ')
    timestamp = dt.timestamp()
    return dt + timedelta(hours=dt.hour-time.gmtime(timestamp).tm_hour)

Обратите внимание, что мы должны посмотреть, если строка не заканчивается Z, мы могли бы проанализировать с помощью %z.


0

Первоначально я пытался с:

from operator import neg, pos
from time import strptime, mktime
from datetime import datetime, tzinfo, timedelta

class MyUTCOffsetTimezone(tzinfo):
    @staticmethod
    def with_offset(offset_no_signal, signal):  # type: (str, str) -> MyUTCOffsetTimezone
        return MyUTCOffsetTimezone((pos if signal == '+' else neg)(
            (datetime.strptime(offset_no_signal, '%H:%M') - datetime(1900, 1, 1))
          .total_seconds()))

    def __init__(self, offset, name=None):
        self.offset = timedelta(seconds=offset)
        self.name = name or self.__class__.__name__

    def utcoffset(self, dt):
        return self.offset

    def tzname(self, dt):
        return self.name

    def dst(self, dt):
        return timedelta(0)


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        dt, sign, offset = strptime(dt[:-6], fmt), dt[-6], dt[-5:]
        return datetime.fromtimestamp(mktime(dt),
                                      tz=MyUTCOffsetTimezone.with_offset(offset, sign))
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

Но это не сработало на отрицательных часовых поясах. Это, однако, я работал нормально, в Python 3.7.3:

from datetime import datetime


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        return datetime.strptime(dt, fmt + '%z')
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

Некоторые тесты отмечают, что выход отличается только точностью микросекунд. Получил 6 цифр точности на моей машине, но YMMV:

for dt_in, dt_out in (
        ('2019-03-11T08:00:00.000Z', '2019-03-11T08:00:00'),
        ('2019-03-11T08:00:00.000+11:00', '2019-03-11T08:00:00+11:00'),
        ('2019-03-11T08:00:00.000-11:00', '2019-03-11T08:00:00-11:00')
    ):
    isoformat = to_datetime_tz(dt_in).isoformat()
    assert isoformat == dt_out, '{} != {}'.format(isoformat, dt_out)

Могу я спросить, почему ты это сделал frozenset(('+', '-'))? Разве обычный кортеж не ('+', '-')должен быть в состоянии выполнить то же самое?
Прахлад Ери

Конечно, но разве это не линейное сканирование, а не идеально хешированный поиск?
AT
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.