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

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

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

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

Ответ 1

Пакет 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 что dateutil.parser намеренно взломан: он пытается угадать формат и делает неизбежные предположения (настраиваемые только вручную) в неоднозначных случаях. Так что используйте его ТОЛЬКО, если вам нужно разобрать ввод неизвестного формата, и вы можете терпеть случайные неправильные чтения. (спасибо иван_поздеев)

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

pip install python-dateutil

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

Ответ 2

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

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

См. вопрос здесь

Ответ 3

Несколько ответы здесь предлагать с помощью 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 Internet Date/Time Format требует, чтобы каждое время даты включало смещение UTC и что эти смещения могут быть Z (короткие для "Zulu time" ) или в формате +HH:MM или -HH:MM, например +05:00 или -10:30.

Следовательно, это все действительные RFC 3339 datetimes:

  • 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 offset в форме + HHMM или -HHMM (пустая строка, если объект наивен).

Пример: (пустой), +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 объект без часового пояса):

>>> 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. Используйте один из многих других подходов, описанных здесь в ответах.

Ответ 4

Новое в 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')

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

В наши дни 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())

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

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

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

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

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

Ответ 14

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

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

Ответ 15

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

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

Ответ 16

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

Ответ 17

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

Ответ 18

Функция 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')

Ответ 19

Поскольку 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, которая варьируется в зависимости от типа сборки system/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

Ответ 20

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

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

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

Ответ 21

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

Ответ 22

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

Ответ 23

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

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

Ответ 24

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.

Ответ 25

Сначала я пробовал с помощью:

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)

Ответ 26

Другой способ - использовать специализированный парсер для 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.