Jak parsować datę w formacie ISO 8601?

642

Muszę przeanalizować ciągi znaków RFC 3339 jak "2008-09-03T20:56:35.450686Z"w datetimetypie Python .

Znalazłem strptimew standardowej bibliotece Pythona, ale nie jest to zbyt wygodne.

Jak najlepiej to zrobić?

Alexander Artemenko
źródło
6
Błąd Pythona: problem15873
jfs
3
Żeby było jasne: ISO 8601 jest głównym standardem. RFC 3339 to samozwańczy „profil” ISO 8601, który powoduje niemądre zastąpienie reguł ISO 8601.
Basil Bourque,
3
Nie przegap poniższego rozwiązania python3.7 + do odwracania izoformatu ()
Brad M
2
To pytanie nie powinno być zamknięte jako duplikat do powiązanego postu. Ponieważ ten prosi o przeanalizowanie ciągu czasu ISO 8601 (który nie był obsługiwany natywnie przez Pythona przed wersją 3.7), a drugi polega na sformatowaniu obiektu daty / godziny w ciągu epoki przy użyciu przestarzałej metody.
abccd

Odpowiedzi:

462

Pakiet python-dateutil może analizować nie tylko ciągi daty i godziny RFC 3339, jak ten w pytaniu, ale także inne ciągi daty i godziny ISO 8601 , które nie są zgodne z RFC 3339 (takie jak te bez przesunięcia UTC lub te, które reprezentują tylko randka).

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

Zauważ, że dateutil.parser.isoparsejest to prawdopodobnie bardziej rygorystyczne niż bardziej hackerskie dateutil.parser.parse, ale oba są dość wybaczające i będą próbowały zinterpretować przekazany ciąg. Jeśli chcesz wyeliminować możliwość jakichkolwiek błędnych odczytów, musisz użyć czegoś surowszego niż którekolwiek z tych Funkcje.

Nazwa Pypi to python-dateutilnie dateutil(dzięki code3monk3y ):

pip install python-dateutil

Jeśli używasz języka Python 3.7, zapoznaj się z tą odpowiedzią na temat datetime.datetime.fromisoformat.

Flimm
źródło
75
Dla leniwych, to zainstalowany przez python-dateutilnie dateutil, tak: pip install python-dateutil.
cod3monk3y
29
Ostrzegamy, że dateutil.parserjest celowo włamany: próbuje odgadnąć format i przyjmuje nieuniknione założenia (konfigurowalne tylko ręcznie) w niejednoznacznych przypadkach. Używaj go TYLKO, jeśli chcesz przeanalizować dane wejściowe o nieznanym formacie i dobrze jest tolerować okazjonalne błędne odczytywanie.
ivan_pozdeev
2
Zgoda. Przykładem jest przekazanie „daty” 9999. Zwróci to tyle samo, co data i godzina (9999, bieżący miesiąc, bieżący dzień). Moim zdaniem nie jest to prawidłowa data.
timbo
1
@ivan_pozdeev jaki pakiet poleciłbyś do analizy bez zgadywania?
bgusach
2
@ivan_pozdeev dostępna jest aktualizacja modułu odczytującego daty iso8601
TheEililon
196

Nowości w Python 3.7+


datetimeStandardowa biblioteka wprowadziła funkcję odwracania datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Zwraca datetimeodpowiadający a date_stringw jednym z formatów emitowanych przez date.isoformat()i datetime.isoformat().

W szczególności ta funkcja obsługuje ciągi w formacie (formatach):

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

gdzie *można dopasować dowolny pojedynczy znak.

Uwaga : To nie obsługuje parsowania dowolnych ciągów ISO 8601 - jest przeznaczone tylko do działania odwrotnego datetime.isoformat().

Przykład zastosowania:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
abccd
źródło
6
To jest dziwne. Ponieważ a datetimemoże zawierać tzinfostrefę czasową, a tym samym wyświetla strefę czasową, ale datetime.fromisoformat()nie analizuje tzinfo? wygląda jak błąd ..
Hendy Irawan
20
Nie przegap tej uwagi w dokumentacji, to nie akceptuje wszystkich prawidłowych ciągów ISO 8601, tylko te wygenerowane przez isoformat. Nie akceptuje przykładu w pytaniu z "2008-09-03T20:56:35.450686Z"powodu końcowego Z, ale akceptuje "2008-09-03T20:56:35.450686".
Flimm
26
Aby poprawnie obsługiwać Zskrypt wejściowy, można go zmodyfikować za pomocą date_string.replace("Z", "+00:00").
jox
7
Pamiętaj, że przez kilka sekund obsługuje tylko dokładnie 0, 3 lub 6 miejsc po przecinku. Jeśli dane wejściowe mają 1, 2, 4, 5, 7 lub więcej miejsc dziesiętnych, parsowanie zakończy się niepowodzeniem!
Felk
1
@JDOaktown W tym przykładzie użyto rodzimej biblioteki datetime Pythona, a nie parsera dateutil. W rzeczywistości nie powiedzie się, jeśli miejsca dziesiętne nie są 0, 3 lub 6 przy takim podejściu.
abccd
174

Uwaga w Pythonie 2.6+ i Py3K znak% f przechwytuje mikrosekundy.

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

Zobacz problem tutaj

sethbc
źródło
4
Uwaga - jeśli używasz danych Naive - Myślę, że w ogóle nie masz TZ - Z może nie pasować do niczego.
Danny Staple
24
Ta odpowiedź (w obecnej, edytowanej formie) polega na zakodowaniu na stałe określonego przesunięcia UTC (a mianowicie „Z”, co oznacza +00: 00) w ciągu formatu. Jest to zły pomysł, ponieważ nie uda mu się parsować daty i godziny z innym przesunięciem UTC i zgłosić wyjątek. Zobacz moją odpowiedź, która opisuje, w jaki sposób parsowanie RFC 3339 strptimejest w rzeczywistości niemożliwe.
Mark Amery
1
w moim przypadku% f złapał mikrosekundy zamiast Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') więc to
załatwiło sprawę
Czy Py3K oznacza Python 3000?!?
Robino,
2
@Robino IIRC, „Python 3000” to stara nazwa tego, co jest obecnie znane jako Python 3.
Wyrzuć konto
161

Kilka odpowiedzi tutaj sugeruje użycie datetime.datetime.strptimedo parsowania czasów danych RFC 3339 lub ISO 8601 ze strefami czasowymi, takich jak ta pokazana w pytaniu:

2008-09-03T20:56:35.450686Z

To jest zły pomysł.

Zakładając, że chcesz obsługiwać pełny format RFC 3339, w tym obsługę przesunięć UTC innych niż zero, kod sugerowany przez te odpowiedzi nie działa. Rzeczywiście, nie może działać, ponieważ analiza składni RFC 3339 przy użyciu strptimejest niemożliwa. Ciągi formatujące używane przez moduł datetime Pythona nie są w stanie opisać składni RFC 3339.

Problemem są przesunięcia UTC. RFC 3339 Internet Date / Time Format wymaga, aby każda data-czas zawiera UTC offset, i że te przesunięcia mogą być albo Z(skrót od „czasu Zulu”) lub w +HH:MMlub -HH:MMformatu, jak +05:00i -10:30.

W związku z tym wszystkie są poprawnymi danymi RFC 3339:

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

Niestety, ciągi formatu używane przez strptimei strftimenie mają dyrektywy, która odpowiada przesunięciom UTC w formacie RFC 3339. Pełna lista obsługiwanych przez nie dyrektyw znajduje się na stronie https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , a jedyną dyrektywą offsetową UTC zawartą na liście jest %z:

% z

Przesunięcie UTC w postaci + HHMM lub -HHMM (pusty ciąg, jeśli obiekt jest naiwny).

Przykład: (pusty), +0000, -0400, +1030

To nie pasuje do formatu przesunięcia RFC 3339, a jeśli spróbujemy użyć %zw ciągu formatu i przeanalizować datę RFC 3339, nie powiedzie się:

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

(W rzeczywistości powyższe jest dokładnie tym, co zobaczysz w Pythonie 3. W Pythonie 2 nie powiedzie się z jeszcze prostszego powodu, to strptimeznaczy,%z że w ogóle nie implementuje dyrektywy w Pythonie 2 ).

Wiele odpowiedzi tutaj, które zalecają strptimeobejście tego problemu, poprzez dołączenie literału Zw łańcuchu formatu, który odpowiada Zprzykładowemu ciągowi datetime zapytania pytającego (i odrzuca go, tworząc datetimeobiekt bez strefy czasowej):

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

Ponieważ powoduje to odrzucenie informacji o strefie czasowej zawartych w oryginalnym ciągu daty i godziny, wątpliwe jest, czy nawet ten wynik należy uznać za poprawny. Ale co ważniejsze, ponieważ takie podejście wymaga zakodowania na stałe określonego przesunięcia UTC w ciągu formatu , zadławi się w chwili, gdy spróbuje parsować dowolną datę / godzinę RFC 3339 z innym przesunięciem 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'

Jeśli nie masz pewności , że potrzebujesz obsługi danych RFC 3339 tylko w czasie Zulu, a nie tych z innymi przesunięciami stref czasowych, nie używaj strptime. Zamiast tego użyj jednego z wielu innych podejść opisanych w odpowiedziach tutaj.

Mark Amery
źródło
79
Zastanawia się, dlaczego strptime nie ma dyrektywy dotyczącej informacji o strefie czasowej w formacie ISO i dlaczego nie można jej przeanalizować. Niesamowite.
Csaba Toth,
2
@CsabaToth Całkowicie się zgodził - jeśli mam trochę czasu na zabicie, być może spróbuję dodać to do języka. Lub możesz to zrobić, jeśli jesteś tak skłonny - widzę, że masz trochę doświadczenia w C, w przeciwieństwie do mnie.
Mark Amery
1
@CsabaToth - Dlaczego niesamowity? Działa wystarczająco dobrze dla większości ludzi, lub znaleźli dość łatwe obejście. Jeśli potrzebujesz tej funkcji, jest to program typu open source i możesz go dodać. Lub zapłać komuś za to. Dlaczego ktoś miałby poświęcić swój wolny czas na rozwiązanie określonych problemów? Niech źródło będzie z tobą.
Peter M. - oznacza Monica
2
@PeterMasiar Incredible, ponieważ zwykle odkrywa się, że rzeczy w Pythonie zostały zaimplementowane w sposób przemyślany i pełny. Zaskoczyło nas to przywiązanie do szczegółów, więc kiedy natkniemy się na coś w języku „niepythonic”, wyrzucamy zabawki z wózka, co właśnie zamierzam zrobić. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino
2
strptime()w Pythonie 3.7 obsługuje teraz wszystko opisane w tej odpowiedzi jako niemożliwe (literał „Z” i „:” w przesunięciu strefy czasowej). Niestety, istnieje inny przypadek narożny, który sprawia, że ​​RFC 3339 jest zasadniczo niezgodny z ISO 8601, a mianowicie ten pierwszy pozwala na ujemne przesunięcie strefy czasowej zerowej -00: 00, a później nie.
SergiyKolesnikov
75

Wypróbuj moduł iso8601 ; robi dokładnie to.

Istnieje kilka innych opcji wymienionych na stronie WorkingWithTime na wiki python.org.

Nicholas Riley
źródło
Proste jakiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman,
3
Pytanie nie brzmiało „jak parsować daty ISO 8601”, lecz „jak parsować ten dokładny format daty”.
Nicholas Riley,
3
@tiktak OP zapytał: „Muszę parsować łańcuchy takie jak X”, a moja odpowiedź na to, po wypróbowaniu obu bibliotek, polega na użyciu innej, ponieważ iso8601 wciąż ma ważne problemy. Moje zaangażowanie lub jego brak w takim projekcie nie ma żadnego związku z odpowiedzią.
Tobia,
2
Należy pamiętać, że wersja pip iso8601 nie została zaktualizowana od 2007 roku i ma kilka poważnych błędów, które są nierozstrzygnięte. Zalecam samodzielne zastosowanie niektórych krytycznych poprawek lub znaleźć jeden z wielu rozwidleń github, które już to zrobiły, github.com/keithhackbarth/pyiso8601-strict
keithhackbarth
6
iso8601 , znany również jako pyiso8601 , został zaktualizowany dopiero w lutym 2014 r. Najnowsza wersja obsługuje znacznie szerszy zestaw ciągów znaków ISO 8601. Korzystałem z dobrych efektów w niektórych moich projektach.
Dave Hein
34
import re, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))
Przetrząsać
źródło
73
Nie zgadzam się, jest to praktycznie nieczytelne i, o ile mogę stwierdzić, nie bierze pod uwagę Zulu (Z), co czyni tę datę naiwną, nawet jeśli podano dane strefy czasowej.
umbrae
14
Uważam to za dość czytelne. W rzeczywistości jest to prawdopodobnie najłatwiejszy i najskuteczniejszy sposób przeprowadzenia konwersji bez instalowania dodatkowych pakietów.
Tobia,
2
Jest to równoważne z d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])).
Xuan
4
odmiana:datetime.datetime(*map(int, re.findall('\d+', s))
jfs
3
W rezultacie powstaje naiwny obiekt typu data-godzina bez strefy czasowej, prawda? Więc bit UTC gubi się w tłumaczeniu?
w00t
32

Jaki dokładnie otrzymujesz błąd? Czy to wygląda następująco?

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

Jeśli tak, możesz podzielić ciąg wejściowy na „.”, A następnie dodać mikrosekundy do otrzymanego czasu danych.

Spróbuj tego:

>>> 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)
tzot
źródło
10
Nie można po prostu rozebrać pliku .Z, ponieważ oznacza on strefę czasową i może być inny. Muszę przekonwertować datę na strefę czasową UTC.
Alexander Artemenko
Zwykły obiekt daty-godziny nie ma pojęcia strefy czasowej. Jeśli wszystkie czasy kończą się na „Z”, wszystkie otrzymane czasy danych to UTC (czas Zulu).
tzot
jeśli strefa czasowa jest inna niż ""lub "Z", to musi być przesunięciem w godzinach / minutach, które można bezpośrednio dodawać / odejmować od obiektu datetime. Państwo mogłoby stworzyć tzinfo podklasy, aby go obsługiwać, ale to chyba nie polecamy.
SingleNegationElimination
8
Dodatkowo „% f” jest specyfikatorem mikrosekundowym, więc łańcuch czasu (naiwny dla strefy czasowej) wygląda następująco: „% Y-% m-% dT% H:% M:% S.% f”.
quodlibetor
1
Spowoduje to wyjątek, jeśli podany ciąg datetime ma przesunięcie UTC inne niż „Z”. Nie obsługuje całego formatu RFC 3339 i jest gorszą odpowiedzią dla innych, które odpowiednio obsługują przesunięcia UTC.
Mark Amery
24

Począwszy od Python 3.7, strptime obsługuje ograniczniki jelita grubego w przesunięciach UTC ( źródło ). Możesz więc użyć:

import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')

EDYTOWAĆ:

Jak zauważył Martijn, jeśli utworzyłeś obiekt datetime za pomocą isoformat (), możesz po prostu użyć datetime.fromisoformat ()

Andreas Profous
źródło
4
Ale w 3.7, to również posiadają datetime.fromisoformat()który obsługuje ciągi jak twój wprowadzania automatycznie: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters
2
Słuszna uwaga. Zgadzam się, polecam używać datetime.fromisoformat()idatetime.isoformat()
Andreas Profous
19

W tych dniach, Arrow może być również używany jako rozwiązanie zewnętrzne:

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
Ilker Kesen
źródło
6
Arrow nie obsługuje poprawnie ISO8601: github.com/crsmithdev/arrow/issues/291
zapakowano
1
Wystarczy użyć python-dateutil - strzałka wymaga python-dateutil.
danizen
Arrow obsługuje teraz ISO8601. Problemy, o których mowa, są teraz zamknięte.
Altus
17

Wystarczy użyć python-dateutilmodułu:

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

Dokumentacja

Blairg23
źródło
1
Czy to nie jest dokładnie odpowiedź @Flimms powyżej?
leo
1
Gdzie widzisz go analizującego w kilka sekund? Znalazłem ten artykuł, próbując znaleźć czas na epokę, więc pomyślałem, że ktoś inny też.
Blairg23,
1
To nie UTC w moim systemie. Dane wyjściowe w sekundach to raczej czas epoki uniksowej, jakby data była w mojej lokalnej strefie czasowej.
Elliot
1
Ta odpowiedź jest błędna i nie powinna być akceptowana. Prawdopodobnie całe pytanie należy oznaczyć jako duplikat stackoverflow.com/questions/11743019/...
tripleee
@tripleee Właściwie właśnie sprawdziłem kod i wydaje się, że zwraca poprawną odpowiedź: 455051100(sprawdzone na epochconverter.com ) ,,, chyba że czegoś brakuje?
Blairg23,
13

Jeśli nie chcesz używać dateutil, możesz wypróbować tę funkcję:

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)

Test:

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

Wynik:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
czarownik
źródło
5
Ta odpowiedź polega na zakodowaniu na stałe określonego przesunięcia UTC (mianowicie „Z”, co oznacza +00: 00) w ciągu formatu przekazanego do strptime. Jest to zły pomysł, ponieważ nie uda mu się parsować daty i godziny z innym przesunięciem UTC i nie zgłosi wyjątku. Zobacz moją odpowiedź, która opisuje, w jaki sposób parsowanie RFC 3339 ze strptime jest w rzeczywistości niemożliwe.
Mark Amery
1
Jest sztywno zakodowany, ale wystarcza na wypadek, gdy trzeba parsować tylko zulu.
Sasha
1
@alexander tak - może tak być, jeśli na przykład wiesz, że łańcuch daty został wygenerowany toISOStringmetodą JavaScript . Ale w tej odpowiedzi nie ma wzmianki o ograniczeniu dat Zulu, ani pytanie nie wskazywało, że to wszystko, co jest potrzebne, a samo użycie dateutiljest zwykle równie wygodne i mniej wąskie w tym, co może przeanalizować.
Mark Amery
11

Jeśli pracujesz z Django, udostępnia on moduł dateparse który akceptuje wiele formatów podobnych do formatu ISO, w tym strefę czasową.

Jeśli nie korzystasz z Django i nie chcesz używać żadnej z wymienionych tu bibliotek, prawdopodobnie możesz dostosować kod źródłowy Django dla dateparse do swojego projektu.

Don Kirkby
źródło
Django DateTimeFieldużywa tego podczas ustawiania wartości ciągu.
djvg
11

Odkryłem, że ciso8601 jest najszybszym sposobem na analizowanie znaczników czasu ISO 8601. Jak sama nazwa wskazuje, jest zaimplementowany w C.

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

The GitHub Repo README pokazuje ich> 10x przyspieszenie w porównaniu do wszystkich innych bibliotek wymienionych w innych odpowiedzi.

Mój osobisty projekt wymagał dużej analizy ISO 8601. Miło było po prostu przełączyć rozmowę i przejść 10 razy szybciej. :)

Edycja: Od tego czasu stałem się opiekunem ciso8601. Jest teraz szybszy niż kiedykolwiek!

movermeyer
źródło
To wygląda na świetną bibliotekę! Dla tych, którzy chcą zoptymalizować analizę ISO8601 w Google App Engine, niestety nie możemy jej użyć, ponieważ jest to biblioteka C, ale twoje testy porównawcze wykazały, że natywna datetime.strptime()jest kolejnym najszybszym rozwiązaniem. Dziękujemy za połączenie wszystkich tych informacji!
hamx0r
3
@ hamx0r, pamiętaj, że datetime.strptime()nie jest to pełna biblioteka parsująca ISO 8601. Jeśli korzystasz z języka Python 3.7, możesz użyć datetime.fromisoformat()metody, która jest nieco bardziej elastyczna. Być może zainteresuje Cię ta bardziej kompletna lista parserów, która wkrótce powinna zostać scalona z ciso8601 README.
movermeyer,
ciso8601 działa całkiem nieźle, ale najpierw trzeba wykonać „pip install pytz”, ponieważ nie można przeanalizować znacznika czasu z informacjami o strefie czasowej bez zależności od pytza. Przykład może wyglądać następująco: dob = ciso8601.parse_datetime (result ['dob'] ['date'])
Dirk
2
@Dirk, tylko w Python 2 . Ale nawet to powinno zostać usunięte w następnej wersji.
movermeyer
8

Działa to dla stdlib w Pythonie 3.2 i nowszych (zakładając, że wszystkie znaczniki czasu to UTC):

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

Na przykład,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
Benjamin Riggs
źródło
2
Ta odpowiedź polega na zakodowaniu na stałe określonego przesunięcia UTC (mianowicie „Z”, co oznacza +00: 00) w ciągu formatu przekazanego do strptime. Jest to zły pomysł, ponieważ nie uda mu się parsować daty i godziny z innym przesunięciem UTC i nie zgłosi wyjątku. Zobacz moją odpowiedź, która opisuje, w jaki sposób parsowanie RFC 3339 ze strptime jest w rzeczywistości niemożliwe.
Mark Amery
1
Teoretycznie tak, to się nie udaje. W praktyce nigdy nie spotkałem daty w formacie ISO 8601, która nie była w czasach Zulu. W moich bardzo rzadkich przypadkach działa to świetnie i nie zależy od niektórych zewnętrznych bibliotek.
Benjamin Riggs,
4
możesz użyć timezone.utczamiast timezone(timedelta(0)). Również prace kodu w Pythonie 2.6+ (co najmniej), jeśli dostarczy utctzinfo obiektu
JFS
Nie ma znaczenia, czy go spotkałeś, nie pasuje do specyfikacji.
theouncouncer
Możesz użyć %Zstrefy czasowej for w najnowszych wersjach Pythona.
sventechie
7

Jestem autorem narzędzi iso8601. Można go znaleźć na GitHub lub na PyPI . Oto jak możesz przeanalizować swój przykład:

>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
Marc Wilson
źródło
6

Jednym prostym sposobem na konwersję łańcucha daty podobnego do ISO 8601 na znacznik czasu lub datetime.datetimeobiekt UNIX we wszystkich obsługiwanych wersjach Pythona bez instalowania modułów innych firm jest użycie analizatora daty 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)

Wynik:

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
Damian Yerrick
źródło
11
Dzięki. To jest ochydne. Kocham to.
wchargin
1
Co za niesamowity, niesamowity, piękny hack! Dzięki!
Havok
6

Zakodowałem parser dla standardu ISO 8601 i umieściłem go w GitHub: https://github.com/boxed/iso8601 . Ta implementacja obsługuje wszystko w specyfikacji, z wyjątkiem czasów trwania, interwałów, interwałów okresowych i dat poza obsługiwanym zakresem dat modułu datetime modułu Python.

Testy są włączone! : P

zapakowane
źródło
6

Funkcja parse_datetime () Django obsługuje daty z przesunięciami UTC:

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

Można go zatem wykorzystać do analizy dat ISO 8601 w polach w ramach całego projektu:

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')
Artem Wasilew
źródło
4

Ponieważ ISO 8601 pozwala zasadniczo na wiele odmian opcjonalnych dwukropków i myślników CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Jeśli chcesz użyć Strptime, musisz najpierw usunąć te odmiany.

Celem jest wygenerowanie obiektu datetime utc.


Jeśli chcesz tylko podstawowy przypadek, który działa dla UTC z sufiksem Z, taki jak 2016-06-29T19:36:29.3453Z:

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


Jeśli chcesz obsługiwać przesunięcia stref czasowych, takie jak 2016-06-29T19:36:29.3453-0400lub 2008-09-03T20:56:35.450686+05:00skorzystaj z poniższych. Spowodują to przekształcenie wszystkich odmian w coś bez ograniczników zmiennych, takich jak 20080903T205635.450686+0500uczynienie go bardziej spójnym / łatwiejszym do parsowania.

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


Jeśli twój system nie obsługuje %zdyrektywy Strptime (widzisz coś takiego ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'), musisz ręcznie przesunąć czas od Z(UTC). Uwaga %zmoże nie działać w systemie w wersjach języka Python <3, ponieważ zależała od obsługi biblioteki c, która różni się w zależności od typu kompilacji system / python (tj. Jython, Cython itp.).

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
theannouncer
źródło
2

Aby uzyskać coś, co działa ze standardową biblioteką 2.X, spróbuj:

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

calendar.timegm to brakująca wersja gm time.mktime.

Gordon Wrigley
źródło
1
To po prostu ignoruje strefę czasową „2013-01-28T14: 01: 01.335612-08: 00” -> przeanalizowane jako UTC, a nie PDT
gatoatigrado
2

Python-dateutil zgłosi wyjątek, jeśli parsuje niepoprawne ciągi daty, więc możesz chcieć złapać wyjątek.

from dateutil import parser
ds = '2012-60-31'
try:
  dt = parser.parse(ds)
except ValueError, e:
  print '"%s" is an invalid date' % ds
użytkownik2646026
źródło
2

Obecnie jest Maya: Datetimes for Humans ™ , autor popularnego pakietu 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>)
jrc
źródło
2

Innym sposobem jest użycie specjalistycznego parser ISO-8601 jest użycie isoparse funkcję dateutil parsera:

from dateutil import parser

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

Wynik:

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

Ta funkcja jest również wspomniana w dokumentacji standardowej funkcji Python datetime.fromisoformat :

Bardziej funkcjonalny parser ISO 8601, dateutil.parser.isoparse jest dostępny w pakiecie dateutil innej firmy.

zawuza
źródło
1

Dzięki wielkiej odpowiedzi Marka Ameryka opracowałem funkcję rozliczania wszystkich możliwych formatów ISO daty / godziny:

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))
omikron
źródło
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)

Zauważ, że powinniśmy sprawdzić, czy ciąg nie kończy się na Z, możemy przeanalizować za pomocą %z.

Denny Weinberg
źródło
0

Początkowo próbowałem z:

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)

Ale to nie działało na ujemnych strefach czasowych. To jednak działało dobrze w Pythonie 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)

W niektórych testach należy zauważyć, że wyjście różni się jedynie precyzją w mikrosekundach. Mam 6 cyfr precyzji na moim komputerze, ale 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)
W
źródło
Czy mogę zapytać, dlaczego to zrobiłeś frozenset(('+', '-'))? Czy normalny krotek nie powinien ('+', '-')być w stanie osiągnąć tego samego?
Prahlad Yeri
Jasne, ale czy nie jest to skan liniowy, a nie idealnie zaszyfrowane wyszukiwanie?
AT