Как мне разобрать дату в формате ISO 8601? - PullRequest
527 голосов
/ 24 сентября 2008

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

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

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

Ответы [ 25 ]

374 голосов
/ 05 марта 2013

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

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

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

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

pip install python-dateutil

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

145 голосов
/ 24 сентября 2008

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

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

См. Выпуск здесь

136 голосов
/ 07 июня 2015

Несколько ответов здесь предложить с использованием 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 или * Формат 1026 *, например +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, у нас не получится:

>>> <b><i>from datetime import datetime</i></b>
>>> <b><i>datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%f%z")</b></i>
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'
>>> <b><i>datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%f%z")</i></b>
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 объект без часового пояса):

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

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

>>> <b><i>datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%fZ")</i></b>
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. Вместо этого используйте один из многих других подходов, описанных в ответах.

84 голосов
/ 11 апреля 2018

Новое в Python 3.7 +


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

classmethod datetime.fromisoformat(date_string):

Возвращает datetime, соответствующий 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')
69 голосов
/ 24 сентября 2008

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

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

34 голосов
/ 24 сентября 2008
import re,datetime
s="2008-09-03T20:56:35.450686Z"
d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))
28 голосов
/ 24 сентября 2008

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

>>> 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)
19 голосов
/ 15 февраля 2015

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

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
19 голосов
/ 31 января 2018

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

import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')
12 голосов
/ 28 марта 2014

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