Як проаналізувати дату у форматі ISO 8601?


642

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

я знайшов strptime у стандартній бібліотеці Python, але це не дуже зручно.

Який найкращий спосіб зробити це?




3
Щоб було зрозуміло: ISO 8601 - це головний стандарт. RFC 3339 - це самопроголошений "профіль" ISO 8601, який робить деякі необгрунтовані зміни правил ISO 8601.
Василь Бурк

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

2
Це питання не повинно бути закритим як відповідна до пов’язаної посади. Оскільки цей просить проаналізувати часовий рядок ISO 8601 (який не підтримувався спочатку python до 3.7), а інший - відформатувати об'єкт datetime в рядок епохи за допомогою застарілого методу.
abccd

Відповіді:


462

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

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

Зауважте, що dateutil.parser.isoparse, мабуть, суворіше, ніж більш хакі dateutil.parser.parse, але вони обидва прощають і спробують інтерпретувати рядок, який ви передаєте. Якщо ви хочете усунути можливість будь-яких помилок, вам потрібно використовувати щось суворіше, ніж будь-яке з цих. функції.

Назва Pypi є python-dateutil, не dateutil(спасибі code3monk3y ):

pip install python-dateutil

Якщо ви використовуєте Python 3.7, поглянути на цей відповідь про datetime.datetime.fromisoformat.


75
Для ледачих це встановлено через python-dateutilnot dateutil, так що : pip install python-dateutil.
cod3monk3y

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

2
Домовились. Прикладом є подання "дати" 9999. Це повернеться так само, як і час (дата 9999, поточний місяць, поточний день). На мій погляд, не дійсна дата.
тембо

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

2
@ivan_pozdeev є оновлення модуля, який читає iso8601 дати: dateutil.readthedocs.io/en/stable/…
theEpsilon

196

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

6
Це дивно. Тому що a datetimeможе містити a tzinfo, і таким чином виводити часовий пояс, але datetime.fromisoformat()не розбирати tzinfo? здається помилкою ..
Хенді Іраван

20
Не пропустіть цю записку в документації, це не приймає все допустимі ISO 8601 рядків, тільки ті , породжені isoformat. Він не приймає приклад у запитанні "2008-09-03T20:56:35.450686Z"через прорив Z, але він приймає "2008-09-03T20:56:35.450686".
Flimm

26
Для належної підтримки Zсценарій введення можна модифікувати за допомогою date_string.replace("Z", "+00:00").
jox

7
Зверніть увагу, що за секунди він обробляє лише 0, 3 або 6 знаків після коми. Якщо вхідні дані містять 1, 2, 4, 5, 7 або більше десяткових знаків, розбір не вдасться!
Фельк

1
@JDOaktown У цьому прикладі використовується нативна бібліотека дат Python, а не парсер dateutil. Він фактично вийде з ладу, якщо десяткових знаків не буде 0, 3 або 6 при такому підході.
abccd

174

Зауважте в Python 2.6+ та Py3K, символ% f ловить мікросекунди.

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

Дивіться питання тут


4
Примітка - якщо ви користуєтеся наївними датами - я думаю, ви взагалі не отримаєте TZ - Z може нічого не відповідати.
Danny Staple

24
Ця відповідь (у її теперішній редагованій формі) спирається на жорстке кодування певного зміщення UTC (а саме "Z", що означає +00: 00) у рядку формату. Це погана ідея, оскільки вона не зможе проаналізувати будь-яку дату з іншим зміщенням UTC і створить виняток. Дивіться мою відповідь, яка описує, як розбирати RFC 3339 з strptimeнасправді неможливо.
Марк Амері

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

Чи означає Py3K Python 3000?!?
Робіно

2
@Robino IIRC, "Python 3000" - це стара назва того, що зараз відомо як Python 3.
Викиньте рахунок

161

Кілька відповідей тут пропонують використовувати datetime.datetime.strptimeдля аналізу RFC 3339 або ISO 8601 дат із часовими поясами, як той, який представлений у запитанні:

2008-09-03T20:56:35.450686Z

Це погана ідея.

Якщо припустити, що ви хочете підтримувати повний формат RFC 3339, включаючи підтримку зсувів UTC, відмінних від нуля, то код, на який можна відповісти, не працює. Дійсно, він не може працювати, оскільки для аналізу синтаксису RFC 3339 використовуєтьсяstrptime неможливий. Рядки формату, використовувані модулем дат Python, не можуть описати синтаксис RFC 3339.

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

Отже, усі ці дійсні дати RFC 3339:

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

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

% z

Зсув UTC у вигляді + 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.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

Оскільки це відкидає інформацію про часовий пояс, яка була включена в початковий рядок дати, сумнівно, чи слід вважати навіть цей результат правильним. Але що ще важливіше, оскільки цей підхід передбачає жорстке кодування певного зміщення UTC у рядку формату , він задушить момент, коли він намагається розібрати будь-який час RFC 3339 з іншим зміщенням UTC:

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

Якщо ви не впевнені, що вам потрібно підтримувати лише дати RFC 3339 у зулуський час, а не ті, які мають інші зміщення часового поясу, не використовуйте strptime. Використовуйте замість цього один із безлічі інших підходів, описаних у відповідях.


79
Думає, що у strptime немає директиви щодо інформації про часовий пояс у форматі ISO і чому її неможливо проаналізувати. Неймовірно.
Csaba Toth

2
@CsabaToth Повністю погодився - якщо у мене буде час на вбивство, можливо, я спробую додати його до мови. Або ти міг би так зробити, якщо ти був так схильний - я бачу, ти маєш певний досвід С на відміну від мене.
Марк Амері

1
@CsabaToth - Чому неймовірно? Це працює досить добре для більшості людей, або вони знайшли досить легке вирішення. Якщо вам потрібна функція, вона є відкритим джерелом, і ви можете її додати. Або заплатити комусь, щоб це зробити за тебе. Чому хтось повинен добровільно використовувати власний вільний час для вирішення ваших конкретних проблем? Нехай джерело буде з вами.
Пітер М. - виступає за Моніку

2
@PeterMasiar Неймовірно, тому що зазвичай виявляється, що речі в python були реалізовані продумано та повно. Нас зіпсувала ця увага до деталей, і тому, коли ми натрапляємо на щось "непітонічне" мовою, ми викидаємо свої іграшки з дитячої коляски, як я зараз це роблю. Whaaaaaaaaaa Whaa wahaaaaa :-(
Робіно

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

75

Спробуйте модуль iso8601 ; вона робить саме це.

Є кілька інших варіантів, згаданих на сторінці WorkingWithTime на вікі python.org.


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

3
Питання було не в тому, "як я розбираю дати ISO 8601", а в "як я розбираю цей точний формат дати".
Ніколас Райлі

3
@tiktak ОП запитала "мені потрібно проаналізувати рядки типу X", і моя відповідь на це, спробувавши обидві бібліотеки, полягає у використанні іншої, тому що у iso8601 є ще важливі проблеми. Моя участь чи відсутність у такому проекті абсолютно не пов'язана з відповіддю.
Тобія

2
Майте на увазі, що версія pip iso8601 не оновлювалася з 2007 року та має серйозні помилки, які є видатними. Рекомендую самостійно застосувати деякі критичні виправлення або знайти один із багатьох вил github, які вже зробили це github.com/keithhackbarth/pyiso8601-strict
keithhackbarth

6
iso8601 , він же pyiso8601 , було оновлено нещодавно лютого 2014 року. Остання версія підтримує значно ширший набір рядків ISO 8601. Я використовую хороший ефект у деяких своїх проектах.
Дейв Хайн

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

73
Я не погоджуюся, це практично не читається, і наскільки я можу сказати, він не враховує зулу (Z), який робить цей час наївним, навіть якщо дані про часовий пояс були надані.
umbrae

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

2
Це еквівалент d = datetime.datetime (* припустимо, * map (int, re.split ('\ D', s) [: - 1])).
Сюань

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

3
Це призводить до появи наївного об'єкта дати без часового поясу, правда? Отже, біт UTC втрачається при перекладі?
w00t

32

Яку точну помилку ви отримаєте? Це як наступне?

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

Якщо так, ви можете розділити вхідний рядок на ".", А потім додати мікросекунди до отриманого вами дати.

Спробуйте це:

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

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

10
Не можна просто знімати .Z, оскільки це означає часовий пояс і може бути різним. Мені потрібно конвертувати дату в часовий пояс UTC.
Олександр Артеменко

Простий об’єкт дати не має поняття часового поясу. Якщо всі ваші часи закінчуються на "Z", усі отримані вами дати - UTC (зулуський час).
tzot

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

8
Додатково, "% f" є мікросекундним специфікатором, тому рядок струму часу (наївний часовим поясом) виглядає так: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor

1
Це призведе до виключення, якщо заданий рядок дат має зміщення UTC, відмінне від "Z". Він не підтримує весь формат RFC 3339 і є неповноцінною відповіддю для інших, які правильно поводять зсув UTC.
Марк Амері

24

Починаючи з 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')

Редагувати:

Як вказував Мартійн, якщо ви створили об’єкт дати за допомогою isoformat (), ви можете просто використовувати datetime.fromisoformat ()


4
Але в 3.7, ви також маєте datetime.fromisoformat()якісь ручки рядки , як автоматично ваш вхід: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters

2
Гарна думка. Я згоден, рекомендую використовувати datetime.fromisoformat()іdatetime.isoformat()
Andreas Profous

19

У наші дні Arrow також може використовуватися як стороннє рішення:

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

6
Стрілка не підтримує ISO8601 належним чином: github.com/crsmithdev/arrow/isissue/291
упаковці

1
Просто використовуйте python-dateutil - стрілка вимагає python-dateutil.
danizen

Тепер стрілка підтримує ISO8601. Питання, на які посилаються, зараз закриті.
Альтус

17

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

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

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


1
Це не точно відповідь @Flimms вище?
лео

1
Де ви бачите його розбору за секунди? Я знайшов цю статтю, намагаючись отримати епоху, тож зрозумів, що ще хтось буде.
Blairg23

1
Це не UTC в моїй системі. Швидше, вихід у секундах - це час епохи Unix, як ніби дата була в моєму локальному часовому поясі.
Елліот

1
Ця відповідь є помилковою, і її не слід приймати. Можливо , все питання повинен бути позначений як дублікат stackoverflow.com/questions/11743019 / ...
tripleee

@tripleee Насправді я просто перевірив код, і він, схоже, повертає правильну відповідь: 455051100(перевірено на epochconverter.com ) ,, якщо я щось не пропускаю?
Blairg23

13

Якщо ви не хочете використовувати dateutil, ви можете спробувати цю функцію:

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

Тест:

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

Результат:

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

5
Ця відповідь покладається на жорстке кодування певного зміщення UTC (а саме "Z", що означає +00: 00) у рядку формату, переданому в strptime. Це погана ідея, оскільки вона не зможе проаналізувати будь-яку дату з іншим зміщенням UTC і створить виняток. Дивіться мою відповідь, яка описує, як розбір RFC 3339 зі strptime насправді неможливий.
Марк Амері

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

1
@alexander так - це може бути так, якщо, наприклад, ви знаєте, що ваш рядок дати був створений toISOStringметодом JavaScript . Але в цій відповіді не згадується обмеження часових дат зулу, а також питання не вказує на те, що це все, що потрібно, і просто використання, dateutilяк правило, однаково зручно і менш вузько в тому, що він може аналізувати.
Марк Амері

11

Якщо ви працюєте з Django, він надає модуль dataparse, який приймає купу форматів, схожих на формат ISO, включаючи часовий пояс.

Якщо ви не використовуєте Django і не хочете використовувати одну з інших бібліотек, згаданих тут, ви, ймовірно, зможете адаптувати вихідний код Django для аналізу часу у вашому проекті.


Django DateTimeFieldвикористовує це під час встановлення рядкового значення.
djvg

11

Я знайшов ciso8601 як найшвидший спосіб розбору часових позначок ISO 8601. Як випливає з назви, вона реалізована в С.

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

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

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

Редагувати: я з тих пір став підтримувачем ciso8601. Зараз це швидше, ніж будь-коли!


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

3
@ hamx0r, майте на увазі, що datetime.strptime()це не повна бібліотека розбору ISO 8601. Якщо ви перебуваєте на Python 3.7, ви можете скористатися datetime.fromisoformat()методом, який є трохи більш гнучким. Можливо, вас зацікавить цей більш повний список парсерів, які незабаром повинні бути об'єднані в ciso8601 README.
movermeyer

ciso8601 працює досить приємно, але спершу треба зробити "встановити протокол pytz", оскільки не можна проаналізувати часову позначку з інформацією про часовий пояс без залежності пітса. Приклад виглядатиме так: dob = ciso8601.parse_datetime (результат ['dob'] ['дата'])
Дірк

2
@Dirk, лише в Python 2 . Але навіть це слід зняти в наступному випуску.
movermeyer

8

Це працює для stdlib на Python 3.2 (якщо всі часові позначки є UTC):

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

Наприклад,

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

2
Ця відповідь покладається на жорстке кодування певного зміщення UTC (а саме "Z", що означає +00: 00) у рядку формату, переданому в strptime. Це погана ідея, оскільки вона не зможе проаналізувати будь-яку дату з іншим зміщенням UTC і створить виняток. Дивіться мою відповідь, яка описує, як розбір RFC 3339 зі strptime насправді неможливий.
Марк Амері

1
Теоретично, так, це не вдається. На практиці я ніколи не стикався з датою у форматі ISO 8601, яка не була в зулуський час. Для моєї дуже випадкової потреби це чудово працює і не покладається на якусь зовнішню бібліотеку.
Бенджамін Ріггс

4
ви можете використовувати timezone.utcзамість цього timezone(timedelta(0)). Також код працює в Python 2.6+ (принаймні), якщо ви постачаєте utcоб'єкт
tzinfo

Неважливо, чи стикалися ви, це не відповідає специфікації.
диктор

Ви можете використовувати %Zчасовий пояс в останніх версіях Python.
sventechie

7

Я автор утиліт iso8601. Його можна знайти на GitHub або на PyPI . Ось як можна розібрати свій приклад:

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

6

Один простий спосіб конвертувати рядок дат, подібних ISO 8601, у часову позначку або datetime.datetimeоб'єкт UNIX у всіх підтримуваних версіях Python без встановлення сторонніх модулів, - це використовувати аналізатор дат SQLite .

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

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

Вихід:

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

11
Дякую. Це огидно. Я це люблю.
wchargin

1
Який неймовірний, дивовижний, гарний хак! Дякую!
Хавок

6

Я зашифрував парсер для стандарту ISO 8601 і розмістив його на GitHub: https://github.com/boxed/iso8601 . Ця реалізація підтримує все, що є в специфікації, за винятком тривалості, інтервалів, періодичних інтервалів та дат, що не відповідають підтримуваному діапазону дат модуля дати Python.

Тести включені! : P



6

Функція parse_datetime () Django підтримує дати із зміщенням UTC:

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

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

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

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

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

4

Оскільки ISO 8601, в основному, дозволяє мати багато варіантів необов'язкових колонок і тире CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Якщо ви хочете використовувати strptime, вам потрібно спершу викреслити ці варіанти.

Мета полягає в генерації об'єкта utc datetime.


Якщо ви просто хочете базовий випадок, який працює для UTC з суфіксом Z на зразок 2016-06-29T19:36:29.3453Z:

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


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

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


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

import re
import datetime

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

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

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

2

Для чогось, що працює зі стандартною бібліотекою 2.X, спробуйте:

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

Calendar.timegm - відсутня gm версія time.mktime.


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

2

У python-dateutil буде викинуто виняток при аналізі недійсних рядків дати, тому ви можете захопити виняток.

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

2

На сьогоднішній день є Майя: Дата для Humans ™ від автора популярного пакету Запити: HTTP for Humans ™:

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

2

Інший спосіб - використовувати спеціалізований аналізатор для ISO-8601 - це використовувати ізопарну функцію аналізатора датування:

from dateutil import parser

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

Вихід:

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

Ця функція також згадується в документації до стандартної функції Python datetime.fromisoformat :

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


1

Завдяки чудовій відповіді Марка Амері я розробив функцію для врахування всіх можливих форматів ISO поточного часу:

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

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

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

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

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

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

Зауважте, що ми повинні подивитися, якщо рядок не закінчується Z, ми могли б розібратися з використанням %z.


0

Спочатку я спробував:

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

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

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

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

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

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


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

Але це не спрацювало на негативних часових поясах. Це, однак, я добре працював, в Python 3.7.3:

from datetime import datetime


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

У деяких тестах зауважимо, що вихід відрізняється лише точністю мікросекунд. На моїй машині отримано 6 цифр точності, але YMMV:

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

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

Зрозуміло, але хіба це не лінійне сканування, а не ідеально розбірливий пошук?
AT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.