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

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

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

Как лучше всего это сделать?

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

Basil Bourque 04.08.2018 02:43

Не пропустите приведенное ниже решение python3.7 + для инвертирования isoformat ()

Brad M 09.10.2018 06:09

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

abccd 19.12.2018 19:54
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
718
5
470 950
27

Ответы 27

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

>>> 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)

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

Alexander Artemenko 24.09.2008 19:49

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

tzot 24.09.2008 20:03

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

SingleNegationElimination 05.07.2011 02:24

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

quodlibetor 16.07.2012 20:52

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

Mark Amery 07.06.2015 21:12

import re,datetime
s = "2008-09-03T20:56:35.450686Z"
d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))

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

umbrae 21.12.2011 19:02

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

Tobia 21.11.2012 18:27

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

Xuan 21.05.2013 13:18

def from_utc (date_str): "" "Преобразовать строку данных времени UTC в time.struct_time" "" UTC_FORMAT = "% Y-% m-% dT% H:% M:% S.% fZ" return time.strptime (date_str , UTC_FORMAT)

enchanter 28.03.2014 02:41

вариант: datetime.datetime(*map(int, re.findall('\d+', s))

jfs 16.05.2014 06:19

В результате получается наивный объект datetime без часового пояса, верно? Значит бит UTC теряется при переводе?

w00t 13.06.2014 01:46

@ w00t: aware_d = d.replace(tzinfo=timezone.utc)

jfs 25.10.2014 07:24

Это дает преимущество работы с неполными строками iso, включая даты и время даты без секунд.

Eric 29.01.2015 19:48

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

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

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

Pakman 26.04.2012 02:36

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

Nicholas Riley 20.09.2012 15:04

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

Tobia 28.01.2013 12:56

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

keithhackbarth 24.06.2013 23:10
iso8601, a.k.a. pyiso8601, has been updated as recently as Feb 2014. The latest version supports a much broader set of ISO 8601 strings. I've been using to good effect in some of my projects.
Dave Hein 13.11.2014 03:50

К сожалению, библиотека под названием "iso8601" на pypi тривиально неполна. В нем четко указано, что он не обрабатывает даты, основанные на номерах недель, просто чтобы выбрать один пример.

boxed 05.01.2016 15:29

@Tobia: iso8601, кажется, снова получает обновления.

Georg Schölly 03.08.2016 12:19

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

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

См. Проблему здесь

Обратите внимание: если вы используете наивные даты - я думаю, вы вообще не получите TZ - Z может ничего не соответствовать.

Danny Staple 02.02.2015 20:08

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

Mark Amery 07.06.2015 20:59

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

ashim888 09.02.2016 08:33

Py3K означает Python 3000?!?

Robino 13.11.2017 18:35

Не работает, если нет ms или tz.

Robino 13.11.2017 20:02

@Robino IIRC, «Python 3000» - старое название того, что сейчас известно как Python 3.

Throw Away Account 02.10.2018 02:14

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

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

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

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

gatoatigrado 29.01.2013 05:31

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

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

Как правило, ссылки на инструмент или библиотеку должны сопровождаться примечаниями по использованию, конкретным объяснением того, как связанный ресурс применим к проблеме, или некоторым примером кода, или, если возможно, на все вышеперечисленное.

Samuel Liew 23.09.2018 07:05

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

>>> 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.

Для ленивых он устанавливается через python-dateutil, а не через dateutil, поэтому: pip install python-dateutil.

cod3monk3y 13.03.2014 01:55

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

ivan_pozdeev 24.04.2015 02:34

Согласовано. В примере передается «дата» 9999. Она вернет то же самое, что и datetime (9999, текущий месяц, текущий день). На мой взгляд, это неверная дата.

timbo 24.06.2016 02:08

@ivan_pozdeev какой пакет вы бы порекомендовали для парсинга без догадок?

bgusach 10.01.2018 15:54

@bgusach iso8601как предлагает другой ответ.

ivan_pozdeev 10.01.2018 16:06

@ivan_pozdeev, но это для iso8601, а не для rfc3339. Хотя вопрос немного сбивает с толку, кажется, рассматривать и то, и другое как одно и то же. Я думал, мы говорили только о rfc3339.

bgusach 10.01.2018 16:50

@bgusach RFC 3339, прямо в аннотации: «Этот документ определяет формат даты и времени для использования в Интернет-протоколах, который является профилем стандарта ISO 8601 для представления даты и времени с использованием григорианского календаря».

ivan_pozdeev 10.01.2018 19:57

@ivan_pozdeev Тогда я поправлюсь, спасибо. Я посмотрел документ, но не понял, что a profile of the ISO 8601 означает a strict subset of ISO 8601 (я не носитель языка). Кстати, кажется, есть небольшая несовместимость между ними и TZ -00:00, но я не думаю, что это может вызвать какие-либо проблемы в моем случае.

bgusach 11.01.2018 18:22

В Python 3 синтаксический анализатор всегда использует часовой пояс tzlocal, независимо от того, отображается ли Z в конце строки времени, в системах, которые настроены на использование UTC в качестве часового пояса по умолчанию. Числовые смещения создают объект tzinfo tzoffset.

Throw Away Account 02.10.2018 02:10

Для более короткого способа записать это вы можете сделать: from dateutil.parser import parse as parsedate, а затем использовать parsedate() вместо dateutil.parser.parse()

gitaarik 04.03.2019 20:31

@ivan_pozdeev есть обновление модуля, который читает даты iso8601: dateutil.readthedocs.io/en/stable/…

theEpsilon 14.01.2020 20:30

К сожалению, вам нужно установить стороннюю библиотеку для очень распространенного использования формата даты, я имею в виду нотацию, оканчивающуюся на Z.

ᐅdevrimbaris 11.03.2021 17:20

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

Если вы не хотите использовать 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)

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

Mark Amery 07.06.2015 21:15

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

Sasha 27.07.2015 11:53

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

Mark Amery 20.08.2015 16:41

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

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

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

boxed 05.01.2016 15:26

Просто используйте python-dateutil - стрелке требуется python-dateutil.

danizen 04.02.2019 19:41

Arrow теперь поддерживает ISO8601. Упомянутые вопросы закрыты.

Altus 04.03.2020 16:15

Это работает для 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)

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

Mark Amery 07.06.2015 21:15

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

Benjamin Riggs 30.12.2015 00:28

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

jfs 31.12.2015 04:24

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

theannouncer 25.02.2019 23:45

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

sventechie 26.03.2019 21:53

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

2008-09-03T20:56:35.450686Z

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

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

Проблема в смещении UTC. RFC 3339 Формат даты / времени в Интернете требует, чтобы каждая дата-время включала смещение UTC, и чтобы эти смещения могли быть либо Z (сокращение от «Zulu time»), либо в формате +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:

%z

UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive).

Example: (empty), +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 мы потерпим неудачу по еще более простой причине - strptime вообще не реализует директиву %z в Python 2.)

Несколько ответов здесь, которые рекомендуют 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 во времени Zulu, а не с другим смещением часового пояса, не используйте strptime. Вместо этого используйте один из многих других подходов, описанных в ответах здесь.

Поразительно, почему в strptime нет директивы для информации о часовом поясе в формате ISO и почему ее нельзя проанализировать. Невероятный.

Csaba Toth 18.09.2015 20:43

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

Mark Amery 18.09.2015 20:45

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

Peter M. - stands for Monica 06.01.2016 17:42

Поскольку это в основном означает, что вы не можете надежно анализировать даты ISO 8601 с использованием чистого python (определенно не для python 2 и 3), я в конечном итоге использовал вместо этого популярную библиотеку стрелок: arrow.readthedocs.io/en/latest

Joris 03.12.2016 18:14

@MarkAmery большое спасибо за это объяснение. Сэкономил мне массу времени на поиски чего-то, чего нет в Python.

cnk 09.05.2017 23:56

Спасибо. Я думал, что сошел с ума, потому что не мог этого понять. В итоге я использовал %z, я просто сначала пропускаю строку через s = s[:-3] + s[-2:], чтобы удалить двоеточие.

Jacktose 16.06.2017 19:10

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

Robino 13.11.2017 18:41

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

SergiyKolesnikov 25.10.2018 22:09

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

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

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

djvg 14.12.2018 13:10

Благодаря великолепному Ответ Марка Амери я разработал функцию для учета всех возможных форматов 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'', 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))

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


If you just want a basic case that work for UTC with the Z suffix like 2016-06-29T19:36:29.3453Z:
datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")


If you want to handle timezone offsets like 2016-06-29T19:36:29.3453-0400 or 2008-09-03T20:56:35.450686+05:00 use the following. These will convert all variations into something without variable delimiters like 20080903T205635.450686+0500 making it more consistent/easier to parse.
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" )


If your system does not support the %z strptime directive (you see something like ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z') then you need to manually offset the time from Z (UTC). Note %z may not work on your system in python versions < 3 as it depended on the c library support which varies across system/python build type (i.e. Jython, Cython, etc.).
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

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.

Одним из простых способов преобразования строки даты, подобной 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

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

wchargin 31.01.2019 21:41

Какой невероятный, классный, красивый хак! Спасибо!

Havok 08.02.2020 10:31

Функция Django parse_datetime () поддерживает даты со смещением 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')

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

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

Просто используйте модуль 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())

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

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

leo 26.09.2017 21:48

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

Blairg23 27.09.2017 00:18

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

Elliot 06.01.2018 04:03

Этот ответ содержит ошибки, и его не следует принимать. Наверное, весь вопрос стоит пометить как дубликат stackoverflow.com/questions/11743019/…

tripleee 12.11.2018 14:11

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

Blairg23 13.11.2018 02:22

При этом datetime.datetime.timestamp(), вероятно, является лучшим решением.

Blairg23 13.11.2018 02:23

@Blairg Как объясняется в дубликате, это зависит от вашей библиотеки C и, конечно же, от вашего часового пояса. Я, например, могу надежно воспроизвести; Я получаю неправильный ответ в MacOS с sprintf("%s"). Код, который работает не везде, по определению содержит ошибки.

tripleee 13.11.2018 07:52

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

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

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

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

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

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

hamx0r 03.07.2018 20:18

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

movermeyer 03.07.2018 22:50

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

Dirk 28.07.2018 14:20

@ Дирк, только в Python 2. Но даже тот следует удалить в следующем выпуске.

movermeyer 29.07.2018 20:41

Начиная с 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 ()

Но в версии 3.7 у вас также есть datetime.fromisoformat(), который автоматически обрабатывает такие строки, как ваш ввод: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:0‌​0').

Martijn Pieters 30.01.2019 15:53

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

Andreas Profous 19.06.2019 23:11

Это единственный ответ, который действительно соответствует критериям вопроса. Если вам нужно использовать strptime, это правильный ответ

Danielo515 22.02.2021 09:53

Новое в Python 3.7+


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

classmethoddatetime.fromisoformat(date_string):

Return a datetime corresponding to a date_string in one of the formats emitted by date.isoformat() and datetime.isoformat().

Specifically, this function supports strings in the format(s):

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

where * can match any single character.

Caution: This does not support parsing arbitrary ISO 8601 strings - it is only intended as the inverse operation of datetime.isoformat().

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

from datetime import datetime

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

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

Hendy Irawan 17.07.2018 16:23

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

Flimm 23.08.2018 19:27

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

jox 02.12.2018 13:47

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

Felk 29.05.2019 00:30

Фелк: Для меня это не подводит. import dateutil.parser as dp #Felk: эта строка имеет 7 десятичных знаков x: str = '2019-08-19T17: 56: 37.5820007Z' dp.parse (x) Out [4]: ​​datetime.datetime (2019, 8, 19, 17, 56, 37, 582000, tzinfo = tzutc ())

JDOaktown 19.08.2019 23:59

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

abccd 22.08.2019 13:14

Я запускаю Python 3.7.3 и получаю AttributeError: type object 'datetime.time' has no attribute 'fromisoformat', когда запускаю его в используемом мной фреймворке.

ingyhere 11.11.2019 08:57

fromisoformat находится по адресу datetime.datetime.fromisoformat() - я знаю, что это сбивает с толку!

Kiran 25.07.2020 18:00

Как уже отмечалось, этот метод будет успешно анализировать только вывод isoformat и не полностью совместим с ISO-8601, но очень немногие языки полностью совместимы, учитывая, насколько велик и непонятен этот стандарт. Да, Java будет принимать часовые пояса и смещения даты, но все, что больше этого, также упадет

user1596707 04.08.2020 11:38

В настоящее время существует 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>)

Первоначально я пробовал:

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(('+', '-'))? Разве обычный кортеж вроде ('+', '-') не должен делать то же самое?

Prahlad Yeri 08.06.2019 16:46

Конечно, но разве это не линейное сканирование, а не идеально хэшированный поиск?

A T 08.06.2019 17:16

Другой способ - использовать специализированный парсер для ISO-8601 - использовать функцию изопарчатый парсера 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:

A more full-featured ISO 8601 parser, dateutil.parser.isoparse is available in the third-party package dateutil.

Простой вариант из одного из комментариев: замените 'Z' на '+00:00' - и используйте fromisoformat Python 3.7 +:

from datetime import datetime

s = "2008-09-03T20:56:35.450686Z"

datetime.fromisoformat(s.replace('Z', '+00:00'))
# datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=datetime.timezone.utc)

Хотя strptime может анализировать символ 'Z' в формате UTC, fromisoformat быстрее на ~ x40 (см. Также: Более быстрый strptime):

%timeit datetime.fromisoformat(s.replace('Z', '+00:00'))
346 ns ± 22.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f%z')
14.2 µs ± 452 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit dateutil.parser.parse(s)
80.1 µs ± 3.32 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

(Python 3.8.7 x64 в Windows 10)

Другие вопросы по теме