Мне нужно разобрать строки RFC 3339, такие как "2008-09-03T20:56:35.450686Z", в тип Python datetime.
Я нашел strptime в стандартной библиотеке Python, но это не очень удобно.
Как лучше всего это сделать?
связанные: Преобразование меток времени со смещением в объект datetime с помощью strptime
Чтобы было ясно: ISO 8601 - основной стандарт. RFC 3339 - это самопровозглашенный «профиль» ISO 8601, который устанавливает некоторые правила неразумно отвергает из ISO 8601.
Не пропустите приведенное ниже решение python3.7 + для инвертирования isoformat ()
Этот вопрос не следует закрывать как тупик по отношению к связанному сообщению. Так как этот запрашивает разбирать строку времени ISO 8601 (которая изначально не поддерживалась python до версии 3.7), а другой - формат объект datetime в строку эпохи с использованием устаревшего метода.






Какую именно ошибку вы получаете? Это похоже на следующее?
>>> 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.
Простой объект datetime не имеет понятия о часовом поясе. Если все ваши времена заканчиваются на «Z», все получаемые вами даты - UTC (время зулусского времени).
если часовой пояс отличается от "" или "Z", тогда это должно быть смещение в часах / минутах, которое можно напрямую добавить / вычесть из объекта datetime. вы мог создаете подкласс tzinfo для его обработки, но это, вероятно, не рекомендуется.
Кроме того, «% f» - это спецификатор микросекунды, поэтому строка strptime (наивная для часовых поясов) выглядит так: «% Y-% m-% dT% H:% M:% S.% f».
Это вызовет исключение, если данная строка даты и времени имеет смещение UTC, отличное от "Z". Он не поддерживает весь формат RFC 3339 и является худшим ответом по сравнению с другими, которые правильно обрабатывают смещения UTC.
import re,datetime
s = "2008-09-03T20:56:35.450686Z"
d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))
Я не согласен, это практически нечитаемо и, насколько я могу судить, не принимает во внимание Zulu (Z), что делает это datetime наивным, даже если были предоставлены данные о часовом поясе.
Я нахожу это вполне читаемым. Фактически, это, вероятно, самый простой и эффективный способ выполнить преобразование без установки дополнительных пакетов.
Я полагаю, это эквивалент d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])).
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)
вариант: datetime.datetime(*map(int, re.findall('\d+', s))
В результате получается наивный объект datetime без часового пояса, верно? Значит бит UTC теряется при переводе?
@ w00t: aware_d = d.replace(tzinfo=timezone.utc)
Это дает преимущество работы с неполными строками iso, включая даты и время даты без секунд.
Попробуйте модуль iso8601; он делает именно это.
Есть несколько других вариантов, упомянутых на странице WorkingWithTime в wiki python.org.
Просто как iso8601.parse_date("2008-09-03T20:56:35.450686Z")
Вопрос был не в том, «как разобрать даты ISO 8601», а в том, «как разобрать этот точный формат даты».
@tiktak OP спросил: «Мне нужно проанализировать строки, такие как X», и мой ответ на это, попробовав обе библиотеки, - использовать другую, потому что у iso8601 есть важные проблемы, которые все еще открыты. Мое участие или отсутствие такового в таком проекте совершенно не связано с ответом.
Имейте в виду, что pip-версия iso8601 не обновлялась с 2007 года и имеет несколько серьезных ошибок, которые не решены. Я рекомендую применить некоторые критические исправления самостоятельно или найти один из многих форков github, которые уже сделали это github.com/keithhackbarth/pyiso8601-strict
К сожалению, библиотека под названием "iso8601" на pypi тривиально неполна. В нем четко указано, что он не обрабатывает даты, основанные на номерах недель, просто чтобы выбрать один пример.
@Tobia: iso8601, кажется, снова получает обновления.
Обратите внимание, что в Python 2.6+ и Py3K символ% f улавливает микросекунды.
>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
См. Проблему здесь
Обратите внимание: если вы используете наивные даты - я думаю, вы вообще не получите TZ - Z может ничего не соответствовать.
Этот ответ (в его текущей отредактированной форме) основан на жестком кодировании определенного смещения UTC (а именно «Z», что означает +00: 00) в строку формата. Это плохая идея, потому что он не сможет проанализировать любое datetime с другим смещением UTC и вызвать исключение. См. мой ответ, который описывает, как анализ RFC 3339 с strptime фактически невозможен.
в моем случае% f поймал микросекунды, а не Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f'), так что это помогло
Py3K означает Python 3000?!?
Не работает, если нет ms или tz.
@Robino IIRC, «Python 3000» - старое название того, что сейчас известно как Python 3.
Для того, что работает со стандартной библиотекой 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
Я написал парсер для стандарта ISO 8601 и разместил его на GitHub: https://github.com/boxed/iso8601. Эта реализация поддерживает все, что указано в спецификации, за исключением продолжительности, интервалов, периодических интервалов и дат за пределами поддерживаемого диапазона дат модуля Python datetime.
Тесты включены! :П
Как правило, ссылки на инструмент или библиотеку должны сопровождаться примечаниями по использованию, конкретным объяснением того, как связанный ресурс применим к проблеме, или некоторым примером кода, или, если возможно, на все вышеперечисленное.
Пакет 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.
Имейте в виду, что dateutil.parser намеренно взломан: он пытается угадать формат и делает неизбежные предположения (настраиваемые только вручную) в неоднозначных случаях. Поэтому используйте его ТОЛЬКО, если вам нужно проанализировать ввод неизвестного формата и вы можете допускать случайные ошибки чтения.
Согласовано. В примере передается «дата» 9999. Она вернет то же самое, что и datetime (9999, текущий месяц, текущий день). На мой взгляд, это неверная дата.
@ivan_pozdeev какой пакет вы бы порекомендовали для парсинга без догадок?
@bgusach iso8601как предлагает другой ответ.
@ivan_pozdeev, но это для iso8601, а не для rfc3339. Хотя вопрос немного сбивает с толку, кажется, рассматривать и то, и другое как одно и то же. Я думал, мы говорили только о rfc3339.
@bgusach RFC 3339, прямо в аннотации: «Этот документ определяет формат даты и времени для использования в Интернет-протоколах, который является профилем стандарта ISO 8601 для представления даты и времени с использованием григорианского календаря».
@ivan_pozdeev Тогда я поправлюсь, спасибо. Я посмотрел документ, но не понял, что a profile of the ISO 8601 означает a strict subset of ISO 8601 (я не носитель языка). Кстати, кажется, есть небольшая несовместимость между ними и TZ -00:00, но я не думаю, что это может вызвать какие-либо проблемы в моем случае.
В Python 3 синтаксический анализатор всегда использует часовой пояс tzlocal, независимо от того, отображается ли Z в конце строки времени, в системах, которые настроены на использование UTC в качестве часового пояса по умолчанию. Числовые смещения создают объект tzinfo tzoffset.
Для более короткого способа записать это вы можете сделать: from dateutil.parser import parse as parsedate, а затем использовать parsedate() вместо dateutil.parser.parse()
@ivan_pozdeev есть обновление модуля, который читает даты iso8601: dateutil.readthedocs.io/en/stable/…
К сожалению, вам нужно установить стороннюю библиотеку для очень распространенного использования формата даты, я имею в виду нотацию, оканчивающуюся на Z.
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 фактически невозможен.
Он жестко запрограммирован, но его достаточно для случая, когда вам нужно только разобрать zulu.
@alexander yes - это может иметь место, если, например, вы знаете, что ваша строка даты была сгенерирована с помощью метода JavaScript toISOString. Но в этом ответе нет упоминания об ограничении датами зулусского времени, и вопрос не указывает на то, что это все, что нужно, и просто использование dateutil обычно одинаково удобно и менее узко в том, что он может анализировать.
В наши дни Стрелка также можно использовать как стороннее решение:
>>> 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
Просто используйте python-dateutil - стрелке требуется python-dateutil.
Arrow теперь поддерживает ISO8601. Упомянутые вопросы закрыты.
Это работает для 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 фактически невозможен.
Теоретически да, это не так. На практике я никогда не сталкивался с датой в формате ISO 8601, отличной от зулусского времени. Для моей очень редкой потребности это отлично работает и не зависит от какой-либо внешней библиотеки.
вы можете использовать timezone.utc вместо timezone(timedelta(0)). Кроме того, код работает на Python 2.6+ (по крайней мере), если вы поставка объекта utc tzinfo
Неважно, сталкивались ли вы с этим, он не соответствует спецификации.
Вы можете использовать %Z для часового пояса в самых последних версиях Python.
Несколькоответыздесьпредложить с использованием 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.450686Z2008-09-03T20:56:35.450686+05:002008-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 и почему ее нельзя проанализировать. Невероятный.
@CsabaToth Полностью согласен - если у меня будет время убить, возможно, я попробую добавить его в язык. Или вы могли бы это сделать, если бы вы были так склонны - я вижу, у вас есть некоторый опыт в Си, в отличие от меня.
@CsabaToth - Почему невероятно? Это работает достаточно хорошо для большинства людей, или они нашли достаточно простой обходной путь. Если вам нужна эта функция, она имеет открытый исходный код, и вы можете ее добавить. Или заплатите кому-нибудь, кто сделает это за вас. Почему кто-то должен добровольно посвящать свое свободное время решению ваших конкретных проблем? Пусть источник будет с вами.
Поскольку это в основном означает, что вы не можете надежно анализировать даты ISO 8601 с использованием чистого python (определенно не для python 2 и 3), я в конечном итоге использовал вместо этого популярную библиотеку стрелок: arrow.readthedocs.io/en/latest
@MarkAmery большое спасибо за это объяснение. Сэкономил мне массу времени на поиски чего-то, чего нет в Python.
Спасибо. Я думал, что сошел с ума, потому что не мог этого понять. В итоге я использовал %z, я просто сначала пропускаю строку через s = s[:-3] + s[-2:], чтобы удалить двоеточие.
@PeterMasiar Невероятно, потому что обычно обнаруживается, что вещи в python были реализованы продуманно и полностью. Мы были избалованы таким вниманием к деталям, и поэтому, когда мы натыкаемся на что-то на языке, который является «непифоническим», мы выбрасываем наши игрушки из коляски, как я собираюсь сделать это прямо сейчас. Вхаааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааа :-(
strptime() в Python 3.7 теперь поддерживает все, что в этом ответе описано как невозможное (литерал «Z» и «:» в смещении часового пояса). К сожалению, есть еще один угловой случай, который делает RFC 3339 принципиально несовместимым с ISO 8601, а именно: первый допускает отрицательное смещение нулевого часового пояса -00: 00, а второй - нет.
Если вы работаете с Django, он предоставляет модуль dateparse, который принимает множество форматов, похожих на формат ISO, включая часовой пояс.
Если вы не используете Django и не хотите использовать одну из других упомянутых здесь библиотек, вы, вероятно, можете адаптировать исходный код Django для dateparse к своему проекту.
DateTimeField в Django использует это, когда вы устанавливаете строковое значение.
Благодаря великолепному Ответ Марка Амери я разработал функцию для учета всех возможных форматов 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.
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 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" )
%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
Спасибо. Это отвратительно. Я люблю это.
Какой невероятный, классный, красивый хак! Спасибо!
Функция 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')
Просто используйте модуль 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 выше?
Где вы видите его парсинг за секунды? Я нашел эту статью, пытаясь узнать время эпохи, поэтому я подумал, что кто-то другой тоже подойдет.
Это нет UTC в моей системе. Скорее, вывод в секундах - это время эпохи unix, как если бы дата была в моем местном часовом поясе.
Этот ответ содержит ошибки, и его не следует принимать. Наверное, весь вопрос стоит пометить как дубликат stackoverflow.com/questions/11743019/…
@tripleee На самом деле я только что проверил код, и, похоже, он вернул правильный ответ: 455051100 (проверено на epochconverter.com) ,,, если я что-то не упускаю?
При этом datetime.datetime.timestamp(), вероятно, является лучшим решением.
@Blairg Как объясняется в дубликате, это зависит от вашей библиотеки C и, конечно же, от вашего часового пояса. Я, например, могу надежно воспроизвести; Я получаю неправильный ответ в MacOS с sprintf("%s"). Код, который работает не везде, по определению содержит ошибки.
Я обнаружил, что 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, имейте в виду, что datetime.strptime() не является полной библиотекой синтаксического анализа ISO 8601. Если вы используете Python 3.7, вы можете использовать метод datetime.fromisoformat(), который немного более гибкий. Возможно, вы интересует этот более полный список парсеров, который скоро должен быть объединен с ciso8601 README.
ciso8601 работает довольно хорошо, но сначала нужно выполнить «pip install pytz», потому что невозможно проанализировать метку времени с информацией о часовом поясе без зависимости pytz. Пример будет выглядеть так: dob = ciso8601.parse_datetime (result ['dob'] ['date'])
@ Дирк, только в Python 2. Но даже тот следует удалить в следующем выпуске.
Начиная с 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:00').
Хорошая точка зрения. Согласен, рекомендую использовать datetime.fromisoformat() и datetime.isoformat()
Это единственный ответ, который действительно соответствует критериям вопроса. Если вам нужно использовать strptime, это правильный ответ
В стандартной библиотеке datetime появилась функция инвертирования datetime.isoformat().
classmethoddatetime.fromisoformat(date_string):Return a
datetimecorresponding to adate_stringin one of the formats emitted bydate.isoformat()anddatetime.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? похоже на ошибку ..
Не пропустите это примечание в документации, это не принимает действительные строки все ISO 8601, только те, которые сгенерированы isoformat. Он не принимает пример в вопросе "2008-09-03T20:56:35.450686Z" из-за конечного Z, но принимает "2008-09-03T20:56:35.450686".
Для правильной поддержки Z сценарий ввода может быть изменен с помощью date_string.replace("Z", "+00:00").
Обратите внимание, что для секунд он обрабатывает только 0, 3 или 6 знаков после запятой. Если входные данные имеют 1, 2, 4, 5, 7 или более десятичных знаков, синтаксический анализ завершится неудачно!
Фелк: Для меня это не подводит. 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 В этом примере используется собственная библиотека datetime Python, а не синтаксический анализатор dateutil. На самом деле это не удастся, если десятичные разряды не равны 0, 3 или 6 при таком подходе.
Я запускаю Python 3.7.3 и получаю AttributeError: type object 'datetime.time' has no attribute 'fromisoformat', когда запускаю его в используемом мной фреймворке.
fromisoformat находится по адресу datetime.datetime.fromisoformat() - я знаю, что это сбивает с толку!
Как уже отмечалось, этот метод будет успешно анализировать только вывод isoformat и не полностью совместим с ISO-8601, но очень немногие языки полностью совместимы, учитывая, насколько велик и непонятен этот стандарт. Да, Java будет принимать часовые пояса и смещения даты, но все, что больше этого, также упадет
В настоящее время существует 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(('+', '-'))? Разве обычный кортеж вроде ('+', '-') не должен делать то же самое?
Конечно, но разве это не линейное сканирование, а не идеально хэшированный поиск?
Другой способ - использовать специализированный парсер для 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)
Ошибка Python: issue15873: datetime: добавить возможность синтаксического анализа даты и времени RFC 3339