Wie analysiere ich ein ISO 8601-formatiertes Datum?

642

Ich muss RFC 3339- Zeichenfolgen wie "2008-09-03T20:56:35.450686Z"in Pythons datetimeTyp analysieren .

Ich habe strptimein der Python-Standardbibliothek gefunden, aber es ist nicht sehr praktisch.

Was ist der beste Weg, dies zu tun?

Alexander Artemenko
quelle
6
Python-Fehler: Problem15873
jfs
3
Um es klar auszudrücken : ISO 8601 ist der Hauptstandard. RFC 3339 ist ein selbsternanntes „Profil“ von ISO 8601, das einige unkluge Überschreibungen von ISO 8601-Regeln vornimmt .
Basil Bourque
3
Verpassen Sie nicht die unten stehende python3.7 + -Lösung zum Invertieren von isoformat ()
Brad M
2
Diese Frage sollte nicht als Betrug zum verlinkten Beitrag geschlossen werden. Da dies ein bittet zu analysieren eine ISO 8601 - Zeit - String (die nicht nativ von Python vor auf 3,7 unterstützt wurde) und das andere ist zu formatieren , ein Datetime - Objekt in einer Epoche Zeichenfolge eine veraltete Methode.
Abccd

Antworten:

462

Das Python-Dateutil- Paket kann nicht nur RFC 3339-Datums- / Uhrzeitzeichenfolgen wie die betreffende analysieren, sondern auch andere ISO 8601- Datums- und Zeitzeichenfolgen, die nicht RFC 3339 entsprechen (z. B. solche ohne UTC-Offset oder solche, die repräsentieren nur ein Datum).

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

Beachten Sie, dass dies dateutil.parser.isoparsevermutlich strenger ist als das Hacky dateutil.parser.parse, aber beide sind ziemlich verzeihend und versuchen, die von Ihnen übergebene Zeichenfolge zu interpretieren. Wenn Sie die Möglichkeit von Fehlinterpretationen ausschließen möchten, müssen Sie etwas Strengeres als beide verwenden Funktionen.

Der Pypi-Name lautet python-dateutilnicht dateutil(danke code3monk3y ):

pip install python-dateutil

Wenn Sie Python 3.7 verwenden, sehen Sie sich diese Antwort an datetime.datetime.fromisoformat.

Flimm
quelle
75
Für die Faulen ist es über python-dateutilnicht installiert dateutil, also : pip install python-dateutil.
cod3monk3y
29
Seien Sie gewarnt, dass das dateutil.parserabsichtlich hackig ist: Es versucht, das Format zu erraten und macht in mehrdeutigen Fällen unvermeidliche Annahmen (nur von Hand anpassbar). Verwenden Sie es daher NUR, wenn Sie Eingaben mit unbekanntem Format analysieren müssen und gelegentliche Fehlinterpretationen tolerieren können.
ivan_pozdeev
2
Einverstanden. Ein Beispiel ist die Übergabe eines "Datums" von 9999. Dies gibt dasselbe wie datetime (9999, aktueller Monat, aktueller Tag) zurück. Meiner Ansicht nach kein gültiges Datum.
Timbo
1
@ivan_pozdeev Welches Paket würden Sie für das Parsen ohne Raten empfehlen?
Bgusach
2
@ivan_pozdeev Es gibt ein Update für das Modul, das iso8601-Daten liest: dateutil.readthedocs.io/en/stable/…
theEpsilon
196

Neu in Python 3.7+


Die datetimeStandardbibliothek führte eine Funktion zum Invertieren ein datetime.isoformat().

classmethod datetime.fromisoformat(date_string)::

Geben Sie a zurück datetime, das a date_stringin einem der von date.isoformat()und ausgegebenen Formate entsprichtdatetime.isoformat() .

Insbesondere unterstützt diese Funktion Zeichenfolgen in den folgenden Formaten:

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

wo *kann mit jedem einzelnen Zeichen übereinstimmen.

Achtung : Dies unterstützt nicht das Parsen beliebiger ISO 8601-Zeichenfolgen - es ist nur als inverse Operation von gedacht datetime.isoformat().

Anwendungsbeispiel:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
abccd
quelle
6
Das ist komisch. Weil a datetimemöglicherweise a enthält tzinfound somit eine Zeitzone ausgibt, aber datetime.fromisoformat()das tzinfo nicht analysiert? scheint wie ein Fehler ..
Hendy Irawan
20
Verpassen Sie diesen Hinweis in der Dokumentation nicht. Er akzeptiert nicht alle gültigen ISO 8601-Zeichenfolgen, sondern nur die von isoformat. Das Beispiel in der Frage wird "2008-09-03T20:56:35.450686Z"aufgrund des Nachlaufs Znicht akzeptiert, aber akzeptiert "2008-09-03T20:56:35.450686".
Flimm
26
Um Zdas Eingabeskript richtig zu unterstützen, kann es mit geändert werden date_string.replace("Z", "+00:00").
Jox
7
Beachten Sie, dass für Sekunden nur genau 0, 3 oder 6 Dezimalstellen verarbeitet werden. Wenn die Eingabedaten 1, 2, 4, 5, 7 oder mehr Dezimalstellen haben, schlägt die Analyse fehl!
Felk
1
@JDOaktown In diesem Beispiel wird die datetime-Bibliothek von nativem Python verwendet, nicht der Parser von dateutil. Es wird tatsächlich fehlschlagen, wenn die Dezimalstellen bei diesem Ansatz nicht 0, 3 oder 6 sind.
Abccd
174

Beachten Sie, dass in Python 2.6+ und Py3K das Zeichen% f Mikrosekunden abfängt.

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

Siehe Ausgabe hier

sethbc
quelle
4
Hinweis - Wenn Sie naive Daten verwenden - ich denke, Sie erhalten überhaupt keine TZ - Z stimmt möglicherweise nicht mit irgendetwas überein.
Danny Staple
24
Diese Antwort (in ihrer aktuellen, bearbeiteten Form) basiert auf der festen Codierung eines bestimmten UTC-Offsets (nämlich "Z", was +00: 00 bedeutet) in der Formatzeichenfolge. Dies ist eine schlechte Idee, da keine Datums- und Uhrzeitangabe mit einem anderen UTC-Offset analysiert und eine Ausnahme ausgelöst werden kann. Siehe meine Antwort , die beschreibt, wie das Parsen von RFC 3339 strptimetatsächlich unmöglich ist.
Mark Amery
1
in meinem Fall hat% f eher Mikrosekunden als Z abgefangen, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') also hat dies den Trick
getan
Bedeutet Py3K Python 3000?!?
Robino
2
@Robino IIRC, "Python 3000" ist ein alter Name für das, was jetzt als Python 3 bekannt ist.
Throw Away Account
161

Mehrere Antworten hier empfehlen , mit datetime.datetime.strptimeder Frage zeigt RFC 3339 oder ISO 8601 Datetimes mit Zeitzonen, wie das analysieren:

2008-09-03T20:56:35.450686Z

Das ist eine schlechte Idee.

Angenommen, Sie möchten das vollständige RFC 3339-Format unterstützen, einschließlich der Unterstützung anderer UTC-Offsets als Null, dann funktioniert der Code, den diese Antworten vorschlagen, nicht. In der Tat kann es nicht funktionieren, weil das Parsen der RFC 3339-Syntax mitstrptime möglich ist. Die vom datetime-Modul von Python verwendeten Formatzeichenfolgen können die RFC 3339-Syntax nicht beschreiben.

Das Problem sind UTC-Offsets. Das Internet- Datums- / Zeitformat nach RFC 3339 erfordert, dass jede Datums- und Uhrzeit einen UTC-Versatz enthält und dass diese Versätze entweder Z(kurz für "Zulu-Zeit") oder in +HH:MModer im -HH:MMFormat wie +05:00oder sein können -10:30.

Folglich sind dies alles gültige RFC 3339-Datenzeiten:

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

Leider haben die von strptimeund verwendeten strftimeFormatzeichenfolgen keine Direktive, die UTC-Offsets im RFC 3339-Format entspricht. Eine vollständige Liste der von ihnen unterstützten Direktiven finden Sie unter https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior . Die einzige in der Liste enthaltene UTC-Offset-Direktive lautet %z:

% z

UTC-Offset in der Form + HHMM oder -HHMM (leere Zeichenfolge, wenn das Objekt naiv ist).

Beispiel: (leer), +0000, -0400, +1030

Dies stimmt nicht mit dem Format eines RFC 3339-Offsets überein. Wenn wir versuchen, die Formatzeichenfolge zu verwenden %zund ein RFC 3339-Datum zu analysieren, schlagen wir fehl:

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

(Tatsächlich ist das Obige genau das, was Sie in Python 3 sehen werden. In Python 2 werden wir aus einem noch einfacheren Grund scheitern, nämlich dass strptimedie %zDirektive in Python 2 überhaupt nicht implementiert wird .)

Die mehrfachen Antworten hier, die strptimealle empfehlen , umgehen dies, indem sie ein Literal Zin ihre ZFormatzeichenfolge aufnehmen , das mit der datetime-Zeichenfolge des Fragestellers übereinstimmt (und diese verwirft und ein datetimeObjekt ohne Zeitzone erzeugt):

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

Da hierdurch Zeitzoneninformationen verworfen werden, die in der ursprünglichen datetime-Zeichenfolge enthalten waren, ist es fraglich, ob wir auch dieses Ergebnis als korrekt betrachten sollten. Da dieser Ansatz jedoch die Hardcodierung eines bestimmten UTC-Offsets in die Formatzeichenfolge umfasst, wird er erstickt, sobald versucht wird, eine RFC 3339-Datumszeit mit einem anderen UTC-Offset zu analysieren:

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

Verwenden Sie diese Option nicht, es sei denn, Sie sind sicher, dass Sie nur RFC 3339-Datumszeiten in Zulu-Zeit unterstützen müssen, und nicht solche mit anderen Zeitzonen-Offsets strptime. Verwenden Sie stattdessen einen der vielen anderen in den Antworten beschriebenen Ansätze.

Mark Amery
quelle
79
Es ist umwerfend, warum strptime keine Direktive für Zeitzoneninformationen im ISO-Format hat und warum es nicht analysiert werden kann. Unglaublich.
Csaba Toth
2
@CsabaToth Völlig einverstanden - wenn ich etwas Zeit zum Töten habe, werde ich vielleicht versuchen, es in die Sprache aufzunehmen. Oder Sie könnten es tun, wenn Sie so geneigt wären - ich sehe, Sie haben im Gegensatz zu mir einige C-Erfahrungen.
Mark Amery
1
@CsabaToth - Warum unglaublich? Es funktioniert für die meisten Menschen gut genug, oder sie fanden es einfach genug, es zu umgehen. Wenn Sie die Funktion benötigen, handelt es sich um Open Source, und Sie können sie hinzufügen. Oder bezahlen Sie jemanden, der das für Sie erledigt. Warum sollte jemand seine Freizeit freiwillig zur Verfügung stellen, um Ihre spezifischen Probleme zu lösen? Lass die Quelle bei dir sein.
Peter M. - steht für Monica
2
@PeterMasiar Unglaublich, weil man normalerweise entdeckt, dass Dinge in Python nachdenklich und vollständig implementiert wurden. Diese Liebe zum Detail hat uns verwöhnt, und wenn wir auf etwas in der Sprache stoßen, das "unpythonisch" ist, werfen wir unser Spielzeug aus dem Kinderwagen, so wie ich es gerade tue. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino
2
strptime()in Python 3.7 unterstützt jetzt alles, was in dieser Antwort als unmöglich beschrieben wird ('Z'-Literal und': 'im Zeitzonenversatz). Leider gibt es einen anderen Eckfall, der RFC 3339 grundsätzlich mit ISO 8601 inkompatibel macht, nämlich erstere erlaubt einen negativen Null-Zeitzonenversatz von -00: 00 und letztere nicht.
SergiyKolesnikov
75

Probieren Sie das iso8601- Modul aus. es macht genau das.

Auf der WorkingWithTime- Seite im python.org-Wiki werden mehrere andere Optionen erwähnt .

Nicholas Riley
quelle
Einfach wieiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman
3
Die Frage lautete nicht "Wie analysiere ich ISO 8601-Daten?", Sondern "Wie analysiere ich genau dieses Datumsformat?".
Nicholas Riley
3
@tiktak Das OP fragte "Ich muss Zeichenfolgen wie X analysieren" und meine Antwort darauf, nachdem ich beide Bibliotheken ausprobiert habe, ist die Verwendung einer anderen, da bei iso8601 wichtige Probleme noch offen sind. Meine Beteiligung oder mein Fehlen an einem solchen Projekt hat nichts mit der Antwort zu tun.
Tobia
2
Beachten Sie, dass die Pip-Version von iso8601 seit 2007 nicht mehr aktualisiert wurde und einige schwerwiegende Fehler aufweist, die noch ausstehen. Ich empfehle etwas kritisch über die Patches selbst anwenden oder eine der vielen Github Gabeln finden , die dies bereits getan haben github.com/keithhackbarth/pyiso8601-strict
keithhackbarth
6
iso8601 , auch bekannt als pyiso8601 , wurde erst im Februar 2014 aktualisiert. Die neueste Version unterstützt einen viel breiteren Satz von ISO 8601-Zeichenfolgen. Ich habe in einigen meiner Projekte gute Arbeit geleistet.
Dave Hein
34
import re, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))
Ted
quelle
73
Ich bin anderer Meinung, dies ist praktisch unlesbar und berücksichtigt, soweit ich das beurteilen kann, nicht das Zulu (Z), das diese Datumszeit naiv macht, obwohl Zeitzonendaten bereitgestellt wurden.
Umbrae
14
Ich finde es gut lesbar. In der Tat ist dies wahrscheinlich die einfachste und leistungsstärkste Methode, um die Konvertierung durchzuführen, ohne zusätzliche Pakete zu installieren.
Tobia
2
Dies entspricht d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])), nehme ich an.
Xuan
4
eine Variation:datetime.datetime(*map(int, re.findall('\d+', s))
jfs
3
Dies führt zu einem naiven Datum / Uhrzeit-Objekt ohne Zeitzone, oder? Das UTC-Bit geht also bei der Übersetzung verloren?
w00t
32

Was ist der genaue Fehler, den Sie erhalten? Ist es wie folgt?

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

Wenn ja, können Sie Ihre Eingabezeichenfolge auf "." Teilen und dann die Mikrosekunden zu der Datumszeit hinzufügen, die Sie erhalten haben.

Versuche dies:

>>> 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
quelle
10
Sie können .Z nicht einfach entfernen, da dies Zeitzone bedeutet und unterschiedlich sein kann. Ich muss das Datum in die UTC-Zeitzone konvertieren.
Alexander Artemenko
Ein einfaches datetime-Objekt hat kein Konzept für die Zeitzone. Wenn alle Ihre Zeiten mit "Z" enden, sind alle Daten, die Sie erhalten, UTC (Zulu-Zeit).
Zot
Wenn die Zeitzone etwas anderes als ""oder ist "Z", muss es sich um einen Versatz in Stunden / Minuten handeln, der direkt zum Datum / Uhrzeit-Objekt hinzugefügt / von diesem subtrahiert werden kann. Sie könnten eine tzinfo-Unterklasse erstellen, um damit umzugehen, aber das wird wahrscheinlich nicht empfohlen.
SingleNegationElimination
8
Zusätzlich ist "% f" der Mikrosekunden-Spezifizierer, sodass eine (zeitzonen-naive) Strptime-Zeichenfolge wie folgt aussieht: "% Y-% m-% dT% H:% M:% S.% f".
Quodlibetor
1
Dies löst eine Ausnahme aus, wenn die angegebene Datums- / Uhrzeitzeichenfolge einen anderen UTC-Offset als "Z" aufweist. Es unterstützt nicht das gesamte RFC 3339-Format und ist eine schlechtere Antwort als andere, die UTC-Offsets ordnungsgemäß verarbeiten.
Mark Amery
23

Ab Python 3.7 unterstützt strptime Doppelpunktbegrenzer in UTC-Offsets ( Quelle ). So können Sie dann verwenden:

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

BEARBEITEN:

Wie Martijn betont hat, können Sie, wenn Sie das datetime-Objekt mit isoformat () erstellt haben, einfach datetime.fromisoformat () verwenden.

Andreas Profous
quelle
4
In 3.7 haben Sie aber auch , datetime.fromisoformat()welche Zeichenfolgen wie Ihre Eingabe automatisch behandelt werden : datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters
2
Guter Punkt. Ich stimme zu, ich empfehle zu verwenden datetime.fromisoformat()unddatetime.isoformat()
Andreas Profous
19

In diesen Tagen kann Arrow auch als Lösung von Drittanbietern verwendet werden:

>>> 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
quelle
6
Arrow unterstützt ISO8601 jedoch nicht richtig: github.com/crsmithdev/arrow/issues/291
Boxed
1
Verwenden Sie einfach Python-Dateutil - Pfeil erfordert Python-Dateutil.
Danizen
Arrow unterstützt jetzt ISO8601. Die genannten Probleme sind jetzt geschlossen.
Altus
17

Verwenden Sie einfach das python-dateutilModul:

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

Dokumentation

Blairg23
quelle
1
Ist das nicht genau die Antwort von @Flimms oben?
Leo
1
Wo sehen Sie ihn in Sekunden analysieren? Ich fand diesen Artikel, indem ich versuchte, Epochenzeit zu bekommen, also dachte ich, dass es auch jemand anderes sein würde.
Blairg23
1
Dies ist nicht UTC auf meinem System. Die Ausgabe in Sekunden ist vielmehr die Unix-Epochenzeit, als ob das Datum in meiner lokalen Zeitzone wäre.
Elliot
1
Diese Antwort ist fehlerhaft und sollte nicht akzeptiert werden. Wahrscheinlich sollte die ganze Frage als Duplikat von stackoverflow.com/questions/11743019/…
Tripleee
@tripleee Eigentlich habe ich gerade den Code überprüft und es scheint die richtige Antwort zurückzugeben: 455051100(überprüft bei epochconverter.com ) ,,, es sei denn, ich vermisse etwas?
Blairg23
13

Wenn Sie dateutil nicht verwenden möchten, können Sie diese Funktion ausprobieren:

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)

Prüfung:

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

Ergebnis:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
Zauberer
quelle
5
Diese Antwort beruht auf der festen Codierung eines bestimmten UTC-Offsets (nämlich "Z", was +00: 00 bedeutet) in der an übergebenen Formatzeichenfolge strptime. Dies ist eine schlechte Idee, da keine Datums- und Uhrzeitangabe mit einem anderen UTC-Offset analysiert und eine Ausnahme ausgelöst werden kann. Siehe meine Antwort , die beschreibt, wie das Parsen von RFC 3339 mit strptime tatsächlich unmöglich ist.
Mark Amery
1
Es ist fest codiert, aber es reicht aus, wenn Sie nur Zulu analysieren müssen.
Sasha
1
@alexander yes - Dies kann der Fall sein, wenn Sie beispielsweise wissen, dass Ihre Datumszeichenfolge mit der JavaScript- toISOStringMethode generiert wurde . In dieser Antwort wird jedoch weder die Beschränkung auf Zulu-Zeitdaten erwähnt, noch wurde in der Frage darauf hingewiesen, dass dies alles ist, was benötigt wird, und die bloße Verwendung dateutilist in der Regel gleichermaßen praktisch und weniger eng in der Analyse.
Mark Amery
11

Wenn Sie mit Django arbeiten, wird das Dateparse-Modul bereitgestellt , das eine Reihe von Formaten akzeptiert, die dem ISO-Format ähnlich sind, einschließlich der Zeitzone.

Wenn Sie Django nicht verwenden und keine der anderen hier genannten Bibliotheken verwenden möchten, können Sie wahrscheinlich den Django-Quellcode für dateparse an Ihr Projekt anpassen .

Don Kirkby
quelle
Django DateTimeFieldverwendet dies, wenn Sie einen Zeichenfolgenwert festlegen.
DJVG
11

Ich habe festgestellt, dass ciso8601 der schnellste Weg ist, ISO 8601-Zeitstempel zu analysieren. Wie der Name schon sagt, ist es in C implementiert.

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

Die GitHub Repo README zeigt ihre> 10-fache Beschleunigung im Vergleich zu allen anderen Bibliotheken, die in den anderen Antworten aufgeführt sind.

Mein persönliches Projekt beinhaltete viel ISO 8601-Parsing. Es war schön, einfach den Anruf umschalten und 10x schneller gehen zu können. :) :)

Bearbeiten: Ich bin seitdem ein Betreuer von ciso8601 geworden. Es ist jetzt schneller als je zuvor!

Movermeyer
quelle
Das sieht aus wie eine tolle Bibliothek! Für diejenigen, die die ISO8601-Analyse in Google App Engine optimieren möchten, können wir sie leider nicht verwenden, da es sich um eine C-Bibliothek handelt. Ihre Benchmarks haben jedoch gezeigt, dass native datetime.strptime()die nächstschnellste Lösung ist. Vielen Dank, dass Sie all diese Informationen zusammengestellt haben!
Hamx0r
3
@ hamx0r, beachten Sie, dass datetime.strptime()es sich nicht um eine vollständige ISO 8601-Analysebibliothek handelt. Wenn Sie mit Python 3.7 arbeiten, können Sie datetime.fromisoformat()die etwas flexiblere Methode verwenden. Diese vollständigere Liste von Parsern könnte Sie interessieren, die bald in die cISo8601 README integriert werden sollten.
Movermeyer
ciso8601 funktioniert ganz gut, aber man muss zuerst "pip install pytz" ausführen, da man ohne die pytz-Abhängigkeit keinen Zeitstempel mit Zeitzoneninformationen analysieren kann. Beispiel würde aussehen wie folgt: dob = ciso8601.parse_datetime (Ergebnis ['dob'] ['Datum'])
Dirk
2
@Dirk, nur in Python 2 . Aber auch das sollte in der nächsten Version entfernt werden.
Movermeyer
8

Dies funktioniert für stdlib ab Python 3.2 (vorausgesetzt, alle Zeitstempel sind UTC):

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

Zum Beispiel,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
Benjamin Riggs
quelle
2
Diese Antwort beruht auf der festen Codierung eines bestimmten UTC-Offsets (nämlich "Z", was +00: 00 bedeutet) in der an übergebenen Formatzeichenfolge strptime. Dies ist eine schlechte Idee, da keine Datums- und Uhrzeitangabe mit einem anderen UTC-Offset analysiert und eine Ausnahme ausgelöst werden kann. Siehe meine Antwort , die beschreibt, wie das Parsen von RFC 3339 mit strptime tatsächlich unmöglich ist.
Mark Amery
1
Theoretisch scheitert dies. In der Praxis bin ich noch nie auf ein Datum im ISO 8601-Format gestoßen, das nicht zur Zulu-Zeit gehörte. Für meine gelegentlichen Bedürfnisse funktioniert dies hervorragend und ist nicht auf eine externe Bibliothek angewiesen.
Benjamin Riggs
4
Sie könnten timezone.utcanstelle von verwenden timezone(timedelta(0)). Auch der Code funktioniert in Python 2.6+ (zumindest) , wenn Sie liefern utcTzinfo Objekt
JFS
Es spielt keine Rolle, ob Sie darauf gestoßen sind, es entspricht nicht der Spezifikation.
Ansager
Sie können die %Zfor-Zeitzone in den neuesten Versionen von Python verwenden.
Sventechie
7

Ich bin der Autor von iso8601 utils. Es kann auf GitHub oder auf PyPI gefunden werden . So können Sie Ihr Beispiel analysieren:

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

Eine einfache Möglichkeit, eine ISO 8601-ähnliche Datumszeichenfolge datetime.datetimein allen unterstützten Python-Versionen in einen UNIX-Zeitstempel oder ein UNIX- Objekt zu konvertieren, ohne Module von Drittanbietern zu installieren, ist die Verwendung des Datums-Parsers von 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)

Ausgabe:

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
quelle
11
Vielen Dank. Das ist eklig. Ich liebe es.
Wchargin
1
Was für ein unglaublicher, großartiger, wunderschöner Hack! Vielen Dank!
Havok
6

Ich habe einen Parser für den ISO 8601-Standard codiert und auf GitHub gestellt: https://github.com/boxed/iso8601 . Diese Implementierung unterstützt alles in der Spezifikation mit Ausnahme von Dauer, Intervallen, periodischen Intervallen und Daten außerhalb des unterstützten Datumsbereichs des Python-Datums- / Uhrzeitmoduls.

Tests sind enthalten! : P.

verpackt
quelle
2
Im Allgemeinen sollten Links zu einem Tool oder einer Bibliothek mit Verwendungshinweisen, einer spezifischen Erläuterung der Anwendbarkeit der verknüpften Ressource auf das Problem oder einem Beispielcode oder, wenn möglich, allen oben genannten Elementen versehen sein.
Samuel Liew
6

Die Funktion parse_datetime () von Django unterstützt Datumsangaben mit UTC-Offsets:

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

Es kann also zum Parsen von ISO 8601-Daten in Feldern innerhalb des gesamten Projekts verwendet werden:

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 Vasilev
quelle
4

Da ISO 8601 grundsätzlich viele Variationen optionaler Doppelpunkte und Bindestriche zulässt CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Wenn Sie strptime verwenden möchten, müssen Sie diese Variationen zuerst entfernen.

Ziel ist es, ein utc datetime-Objekt zu generieren.


Wenn Sie nur einen einfachen Fall wünschen, der für UTC mit dem Suffix Z funktioniert, wie 2016-06-29T19:36:29.3453Z:

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


Wenn Sie Zeitzonen-Offsets wie folgt behandeln 2016-06-29T19:36:29.3453-0400oder 2008-09-03T20:56:35.450686+05:00verwenden möchten . Diese konvertieren alle Variationen in etwas ohne variable Trennzeichen, wie z. B. 20080903T205635.450686+0500eine konsistentere / einfachere Analyse.

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


Wenn Ihr System die %zstrptime-Direktive nicht unterstützt (Sie sehen so etwas wie ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'), müssen Sie die Zeit von Z(UTC) manuell versetzen . Hinweis %zfunktioniert möglicherweise nicht auf Ihrem System in Python-Versionen <3, da dies von der Unterstützung der c-Bibliothek abhängt, die je nach System- / Python-Build-Typ (z. B. Jython, Cython usw.) unterschiedlich ist.

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
Der Ansager
quelle
2

Für etwas, das mit der 2.X-Standardbibliothek funktioniert, versuchen Sie:

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

calendar.timegm ist die fehlende gm-Version von time.mktime.

Gordon Wrigley
quelle
1
Dies ignoriert nur die Zeitzone '2013-01-28T14: 01: 01.335612-08: 00' -> analysiert als UTC, nicht PDT
gatoatigrado
2

Das Python-Dateutil löst eine Ausnahme aus, wenn ungültige Datumszeichenfolgen analysiert werden. Daher möchten Sie möglicherweise die Ausnahme abfangen.

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

Heutzutage gibt es Maya: Datetimes for Humans ™ vom Autor des beliebten Requests: HTTP for Humans ™ -Pakets:

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

Eine andere Möglichkeit, einen speziellen Parser für ISO-8601 zu verwenden, ist die Verwendung der Isoparse- Funktion des Dateutil-Parsers:

from dateutil import parser

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

Ausgabe:

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

Diese Funktion wird auch in der Dokumentation zur Standard-Python-Funktion datetime.fromisoformat erwähnt :

Dateutil.parser.isoparse, ein umfassenderer ISO 8601-Parser, ist im Paket dateutil eines Drittanbieters verfügbar.

zawuza
quelle
1

Dank der großartigen Antwort von Mark Amery habe ich eine Funktion entwickelt, die alle möglichen ISO-Formate von datetime berücksichtigt:

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

Beachten Sie, dass wir prüfen sollten, ob der String nicht mit endet Z, wir könnten ihn mit analysieren %z.

Denny Weinberg
quelle
0

Anfangs habe ich versucht mit:

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)

Bei negativen Zeitzonen funktionierte das jedoch nicht. Dies funktionierte jedoch in Python 3.7.3 einwandfrei:

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)

Bei einigen Tests ist zu beachten, dass sich der Ausgang nur durch die Genauigkeit von Mikrosekunden unterscheidet. Ich habe 6 Stellen Genauigkeit auf meiner Maschine, aber 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)
BEIM
quelle
Darf ich fragen, warum hast du getan frozenset(('+', '-'))? Sollte ein normales Tupel ('+', '-')nicht in der Lage sein, dasselbe zu erreichen?
Prahlad Yeri
Sicher, aber ist das nicht eher ein linearer Scan als eine perfekt gehashte Suche?
AT