Comment analyser une date au format ISO 8601?

643

J'ai besoin d'analyser les chaînes RFC 3339 comme "2008-09-03T20:56:35.450686Z"dans le datetimetype Python .

J'ai trouvé strptimedans la bibliothèque standard Python, mais ce n'est pas très pratique.

Quelle est la meilleure façon de procéder?

Alexander Artemenko
la source
6
Bogue
jfs
3
Pour être clair: ISO 8601 est la norme principale. La RFC 3339 est un «profil» autoproclamé de la norme ISO 8601 qui effectue des remplacements imprudents des règles ISO 8601.
Basil Bourque
3
Ne manquez pas la solution python3.7 + ci-dessous pour inverser l'isoformat ()
Brad M
2
Cette question ne doit pas être fermée en tant que dupe du message lié. Puisque celui-ci demande d' analyser une chaîne de temps ISO 8601 (qui n'était pas prise en charge nativement par python avant la version 3.7) et l'autre consiste à formater un objet datetime dans une chaîne d'époque en utilisant une méthode obsolète.
abccd

Réponses:

461

Le package python-dateutil peut analyser non seulement les chaînes datetime RFC 3339 comme celle de la question, mais également d'autres chaînes de date et d'heure ISO 8601 qui ne sont pas conformes à RFC 3339 (telles que celles sans décalage UTC ou celles qui représentent seulement une date).

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

Notez que dateutil.parser.isoparsec'est probablement plus strict que le plus hacky dateutil.parser.parse, mais les deux sont assez indulgents et tenteront d'interpréter la chaîne que vous transmettez. Si vous voulez éliminer la possibilité de toute erreur de lecture, vous devez utiliser quelque chose de plus strict que l'un ou l'autre les fonctions.

Le nom Pypi est python-dateutilnon dateutil(merci code3monk3y ):

pip install python-dateutil

Si vous utilisez Python 3.7, un coup d' oeil à cette réponse au sujet datetime.datetime.fromisoformat.

Flimm
la source
75
Pour les paresseux, il est installé par python-dateutilpas dateutil, donc: pip install python-dateutil.
cod3monk3y
29
Soyez averti que le dateutil.parserest intentionnellement hacky: il essaie de deviner le format et fait des hypothèses inévitables (personnalisables à la main uniquement) dans les cas ambigus. Donc, utilisez-le UNIQUEMENT si vous avez besoin d'analyser des entrées de format inconnu et que vous pouvez tolérer des erreurs de lecture occasionnelles.
ivan_pozdeev
2
D'accord. Un exemple passe une «date» de 9999. Cela renverra la même chose que datetime (9999, mois en cours, jour en cours). Pas une date valide à mon avis.
timbo
1
@ivan_pozdeev quel package recommanderiez-vous pour une analyse non-devinette?
bgusach
2
@ivan_pozdeev il y a une mise à jour du module qui lit les dates iso8601
theEpsilon
198

Nouveau dans Python 3.7+


La datetimebibliothèque standard a introduit une fonction pour inverser datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Renvoie un datetimecorrespondant à un date_stringdans l'un des formats émis par date.isoformat()et datetime.isoformat().

Plus précisément, cette fonction prend en charge les chaînes au format:

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

*peut correspondre n'importe quel caractère.

Attention : cela ne prend pas en charge l'analyse de chaînes ISO 8601 arbitraires - il est uniquement destiné à l'opération inverse de datetime.isoformat().

Exemple d'utilisation:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
abccd
la source
6
C'est bizarre. Parce que a datetimepeut contenir un tzinfo, et donc afficher un fuseau horaire, mais datetime.fromisoformat()ne pas analyser le tzinfo? semble être un bug ..
Hendy Irawan
20
Ne manquez pas cette note dans la documentation, cela n'accepte pas toutes les chaînes ISO 8601 valides, uniquement celles générées par isoformat. Il n'accepte pas l'exemple de la question en "2008-09-03T20:56:35.450686Z"raison de la fin Z, mais il accepte "2008-09-03T20:56:35.450686".
Flimm
26
Pour supporter correctement le Zscript d'entrée, vous pouvez le modifier avec date_string.replace("Z", "+00:00").
jox
7
Notez que pendant quelques secondes, il ne gère que exactement 0, 3 ou 6 décimales. Si les données d'entrée ont 1, 2, 4, 5, 7 décimales ou plus, l'analyse échouera!
Felk
1
@JDOaktown Cet exemple utilise la bibliothèque datetime de Python native, pas l'analyseur de dateutil. Il échouera réellement si les décimales ne sont pas 0, 3 ou 6 avec cette approche.
abccd
174

Remarque dans Python 2.6+ et Py3K, le caractère% f capture les microsecondes.

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

Voir le problème ici

sethbc
la source
4
Remarque - si vous utilisez des horaires Naive - je pense que vous n'obtenez pas du tout de TZ - Z peut ne pas correspondre à quoi que ce soit.
Danny Staple
24
Cette réponse (dans sa forme actuelle modifiée) repose sur le codage en dur d'un décalage UTC particulier (à savoir "Z", ce qui signifie +00: 00) dans la chaîne de format. C'est une mauvaise idée car elle ne parviendra pas à analyser une date / heure avec un décalage UTC différent et à déclencher une exception. Voir ma réponse qui décrit comment l'analyse RFC 3339 avec strptimeest en fait impossible.
Mark Amery
1
dans mon cas,% f a pris des microsecondes plutôt que Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') donc cela a fait l'affaire
ashim888
Py3K signifie-t-il Python 3000?!?
Robino
2
@Robino IIRC, "Python 3000" est un ancien nom pour ce qui est maintenant connu sous le nom de Python 3.
Jetez le compte
161

Plusieurs réponses suggèrent ici d' utiliser pour analyser les datetimes RFC 3339 ou ISO 8601 avec des fuseaux horaires, comme celui présenté dans la question:datetime.datetime.strptime

2008-09-03T20:56:35.450686Z

C'est une mauvaise idée.

En supposant que vous souhaitez prendre en charge le format RFC 3339 complet, y compris la prise en charge des décalages UTC autres que zéro, le code suggéré par ces réponses ne fonctionne pas. En effet, cela ne peut pas fonctionner, car l'analyse de la syntaxe RFC 3339 strptimeest impossible. Les chaînes de format utilisées par le module datetime de Python sont incapables de décrire la syntaxe RFC 3339.

Le problème est les décalages UTC. Le format date / heure Internet RFC 3339 requiert que chaque date-heure comprenne un décalage UTC et que ces décalages puissent être Z(abréviation de "heure zoulou") ou au format +HH:MMou -HH:MM, comme +05:00ou -10:30.

Par conséquent, ce sont tous des horaires RFC 3339 valides:

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

Hélas, les chaînes de format utilisées par strptimeet strftimen'ont pas de directive correspondant aux décalages UTC au format RFC 3339. Une liste complète des directives qu'ils prennent en charge se trouve à https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , et la seule directive de décalage UTC incluse dans la liste est %z:

% z

Décalage UTC sous la forme + HHMM ou -HHMM (chaîne vide si l'objet est naïf).

Exemple: (vide), +0000, -0400, +1030

Cela ne correspond pas au format d'un décalage RFC 3339, et en effet si nous essayons d'utiliser %zdans la chaîne de format et d'analyser une date RFC 3339, nous échouerons:

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

(En fait, ce qui précède est exactement ce que vous verrez dans Python 3. Dans Python 2, nous échouerons pour une raison encore plus simple, qui est qu'il strptimen'implémente pas du tout la %zdirective dans Python 2. )

Les multiples réponses ici qui recommandent de strptimecontourner ce problème en incluant un littéral Zdans leur chaîne de format, qui correspond Zà l'exemple de la chaîne datetime du demandeur (et la rejette, produisant un datetimeobjet sans fuseau horaire):

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

Étant donné que cela supprime les informations de fuseau horaire qui étaient incluses dans la chaîne datetime d'origine, il est douteux que nous devrions même considérer ce résultat comme correct. Mais plus important encore, car cette approche implique de coder en dur un décalage UTC particulier dans la chaîne de formatage , elle s'étouffera au moment où elle essaiera d'analyser une date / heure RFC 3339 avec un décalage UTC différent:

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

À moins que vous ne soyez certain que vous ne devez prendre en charge que les heures de données RFC 3339 en heure zoulou, et non celles avec d'autres décalages de fuseau horaire, ne les utilisez pas strptime. Utilisez plutôt l'une des nombreuses autres approches décrites dans les réponses ici.

Mark Amery
la source
79
C'est ahurissant pourquoi strptime n'a pas de directive pour les informations de fuseau horaire au format ISO, et pourquoi il ne peut pas être analysé. Incroyable.
Csaba Toth du
2
@CsabaToth Entièrement d'accord - si j'ai du temps à tuer, j'essaierai peut-être de l'ajouter dans la langue. Ou vous pourriez le faire, si vous étiez si enclin - je vois que vous avez une certaine expérience C, contrairement à moi.
Mark Amery du
1
@CsabaToth - Pourquoi incroyable? Cela fonctionne assez bien pour la plupart des gens, ou ils ont trouvé une solution de contournement assez facile. Si vous avez besoin de la fonctionnalité, elle est open source et vous pouvez l'ajouter. Ou payez quelqu'un pour le faire pour vous. Pourquoi quelqu'un devrait-il offrir son propre temps libre pour résoudre vos problèmes spécifiques? Que la source soit avec vous.
Peter M. - représente Monica
2
@PeterMasiar Incroyable car généralement on découvre que les choses en python ont été implémentées de manière réfléchie et complète. Nous avons été gâtés par cette attention aux détails et donc lorsque nous tombons sur quelque chose dans le langage qui est "impythonique", nous jetons nos jouets dans la poussette, comme je suis sur le point de le faire en ce moment. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino
2
strptime()en Python 3.7 prend désormais en charge tout ce qui est décrit comme impossible dans cette réponse (littéral «Z» et «:» dans le décalage de fuseau horaire). Malheureusement, il existe un autre cas d'angle qui rend la RFC 3339 fondamentalement incompatible avec ISO 8601, à savoir que la première autorise un décalage de fuseau horaire nul négatif -00: 00 et la dernière non.
SergiyKolesnikov
75

Essayez le module iso8601 ; il fait exactement cela.

Il y a plusieurs autres options mentionnées sur la WorkingWithTime page sur le wiki python.org.

Nicholas Riley
la source
Simple commeiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman
3
La question n'était pas "comment analyser les dates ISO 8601", mais "comment analyser ce format de date exact".
Nicholas Riley
3
@tiktak L'OP a demandé "J'ai besoin d'analyser des chaînes comme X" et ma réponse à cela, après avoir essayé les deux bibliothèques, est d'en utiliser une autre, car iso8601 a encore des problèmes importants à résoudre. Mon implication ou son absence dans un tel projet est totalement indépendante de la réponse.
Tobia
2
Sachez que la version pip d'iso8601 n'a pas été mise à jour depuis 2007 et comporte de graves bogues en suspens. Je recommande d'appliquer vous-même certains correctifs des correctifs ou de trouver l'une des nombreuses fourches github qui l'ont déjà fait github.com/keithhackbarth/pyiso8601-strict
keithhackbarth
6
iso8601 , alias pyiso8601 , a été mis à jour aussi récemment qu'en février 2014. La dernière version prend en charge un ensemble beaucoup plus large de chaînes ISO 8601. J'ai utilisé à bon escient certains de mes projets.
Dave Hein
34
importer re, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))
Ted
la source
73
Je ne suis pas d'accord, c'est pratiquement illisible et, pour autant que je sache, ne prend pas en compte le Zulu (Z) qui rend cette date / heure naïve même si des données de fuseau horaire ont été fournies.
umbrae
14
Je le trouve assez lisible. En fait, c'est probablement le moyen le plus simple et le plus performant de faire la conversion sans installer de packages supplémentaires.
Tobia
2
C'est l'équivalent de d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])) je suppose.
Xuan
4
une variation:datetime.datetime(*map(int, re.findall('\d+', s))
jfs
3
Il en résulte un objet datetime naïf sans fuseau horaire, non? Donc, le bit UTC se perd dans la traduction?
w00t
32

Quelle est l'erreur exacte que vous obtenez? Est-ce comme ceci?

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

Si oui, vous pouvez diviser votre chaîne d'entrée sur ".", Puis ajouter les microsecondes à l'heure que vous avez obtenue.

Essaye ça:

>>> 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
la source
10
Vous ne pouvez pas simplement supprimer .Z car cela signifie un fuseau horaire et peut être différent. J'ai besoin de convertir la date au fuseau horaire UTC.
Alexander Artemenko,
Un objet datetime simple n'a aucun concept de fuseau horaire. Si toutes vos heures se terminent par "Z", toutes les heures que vous obtenez sont UTC (heure zouloue).
tzot le
si le fuseau horaire est différent de ""ou "Z", il doit s'agir d'un décalage en heures / minutes, qui peut être directement ajouté à / soustrait de l'objet datetime. vous pouvez créer une sous-classe tzinfo pour la gérer, mais ce n'est probablement pas recommandé.
SingleNegationElimination
8
De plus, "% f" est le spécificateur de microsecondes, donc une chaîne de strptime (naïve au fuseau horaire) ressemble à: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor
1
Cela déclenchera une exception si la chaîne datetime donnée a un décalage UTC autre que "Z". Il ne prend pas en charge l'intégralité du format RFC 3339 et est une réponse inférieure à d'autres qui gèrent correctement les décalages UTC.
Mark Amery
25

À partir de Python 3.7, strptime prend en charge les délimiteurs deux-points dans les décalages UTC ( source ). Vous pouvez donc utiliser:

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

ÉDITER:

Comme l'a souligné Martijn, si vous avez créé l'objet datetime à l'aide d'isoformat (), vous pouvez simplement utiliser datetime.fromisoformat ()

Andreas Profous
la source
4
Mais en 3.7, vous aussi avez datetime.fromisoformat()quelles chaînes poignées comme votre entrée automatiquement: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters
2
Bon point. Je suis d'accord, je recommande d'utiliser datetime.fromisoformat()etdatetime.isoformat()
Andreas Profous
19

De nos jours, Arrow peut également être utilisé comme une solution tierce:

>>> 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
la source
6
Cependant, Arrow ne prend pas correctement en charge ISO8601: github.com/crsmithdev/arrow/issues/291
mis en boîte le
1
Utilisez simplement python-dateutil - la flèche nécessite python-dateutil.
danizen
Arrow prend désormais en charge ISO8601. Les problèmes référencés sont désormais clos.
Altus
18

Utilisez simplement le python-dateutilmodule:

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

Documentation

Blairg23
la source
1
N'est-ce pas exactement la réponse @Flimms ci-dessus?
leo
1
Où le voyez-vous analyser en quelques secondes? J'ai trouvé cet article en essayant d'obtenir le temps de l'époque, donc j'ai pensé que quelqu'un d'autre le serait aussi.
Blairg23
1
Ce n'est pas UTC sur mon système. Au contraire, la sortie en secondes est l'heure de l'époque unix comme si la date était dans mon fuseau horaire local.
Elliot
1
Cette réponse est boguée et ne doit pas être acceptée. Probablement, toute la question devrait être marquée comme un doublon de stackoverflow.com/questions/11743019/…
tripleee
@tripleee En fait, je viens de vérifier le code et il semble renvoyer la bonne réponse: 455051100(vérifié sur epochconverter.com ) ,,, sauf si je manque quelque chose?
Blairg23
13

Si vous ne souhaitez pas utiliser dateutil, vous pouvez essayer cette fonction:

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)

Tester:

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

Résultat:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
enchanteur
la source
5
Cette réponse repose sur le codage en dur d'un décalage UTC particulier (à savoir "Z", ce qui signifie +00: 00) dans la chaîne de format transmise à strptime. C'est une mauvaise idée car elle ne parviendra pas à analyser une date / heure avec un décalage UTC différent et à déclencher une exception. Voir ma réponse qui décrit comment l'analyse RFC 3339 avec strptime est en fait impossible.
Mark Amery
1
Il est codé en dur, mais c'est suffisant pour le cas où vous devez analyser le zoulou uniquement.
Sasha
1
@alexander yes - ce qui peut être le cas si, par exemple, vous savez que votre chaîne de date a été générée avec la toISOStringméthode JavaScript . Mais il n'y a aucune mention de la limitation des dates zouloues dans cette réponse, pas plus que la question n'indique que c'est tout ce qui est nécessaire, et l'utilisation dateutilest généralement aussi pratique et moins étroite dans ce qu'elle peut analyser.
Mark Amery
11

Si vous travaillez avec Django, il fournit le module dateparse qui accepte un tas de formats similaires au format ISO, y compris le fuseau horaire.

Si vous n'utilisez pas Django et que vous ne souhaitez pas utiliser l'une des autres bibliothèques mentionnées ici, vous pouvez probablement adapter le code source de Django pour dateparse à votre projet.

Don Kirkby
la source
Django's l' DateTimeFieldutilise lorsque vous définissez une valeur de chaîne.
djvg
11

J'ai trouvé que ciso8601 était le moyen le plus rapide d'analyser les horodatages ISO 8601. Comme son nom l'indique, il est implémenté en C.

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

Le fichier LISEZMOI GitHub Repo montre leur accélération> 10x par rapport à toutes les autres bibliothèques répertoriées dans les autres réponses.

Mon projet personnel impliquait beaucoup d'analyses ISO 8601. C'était agréable de pouvoir simplement passer l'appel et aller 10 fois plus vite. :)

Edit: je suis depuis devenu un mainteneur de ciso8601. C'est maintenant plus rapide que jamais!

movermeyer
la source
Cela ressemble à une grande bibliothèque! Pour ceux qui souhaitent optimiser l'analyse ISO8601 sur Google App Engine, malheureusement, nous ne pouvons pas l'utiliser car il s'agit d'une bibliothèque C, mais vos benchmarks étaient perspicaces pour montrer que le natif datetime.strptime()est la prochaine solution la plus rapide. Merci d'avoir rassemblé toutes ces informations!
hamx0r
3
@ hamx0r, sachez qu'il datetime.strptime()ne s'agit pas d'une bibliothèque d'analyse ISO 8601 complète. Si vous êtes sur Python 3.7, vous pouvez utiliser la datetime.fromisoformat()méthode, qui est un peu plus flexible. Vous pourriez être intéressé par cette liste plus complète d'analyseurs qui devraient bientôt être fusionnés avec le fichier README ciso8601.
movermeyer
ciso8601 fonctionne assez bien, mais il faut d'abord faire "pip install pytz", car on ne peut pas analyser un horodatage avec des informations de fuseau horaire sans la dépendance pytz. L'exemple ressemblerait à: dob = ciso8601.parse_datetime (result ['dob'] ['date'])
Dirk
2
@Dirk, uniquement dans Python 2 . Mais même cela devrait être supprimé dans la prochaine version.
movermeyer
8

Cela fonctionne pour stdlib sur Python 3.2 (en supposant que tous les horodatages sont UTC):

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

Par exemple,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
Benjamin Riggs
la source
2
Cette réponse repose sur le codage en dur d'un décalage UTC particulier (à savoir "Z", ce qui signifie +00: 00) dans la chaîne de format transmise à strptime. C'est une mauvaise idée car elle ne parviendra pas à analyser une date / heure avec un décalage UTC différent et à déclencher une exception. Voir ma réponse qui décrit comment l'analyse RFC 3339 avec strptime est en fait impossible.
Mark Amery
1
En théorie, oui, cela échoue. En pratique, je n'ai jamais rencontré de date au format ISO 8601 qui n'était pas en heure zoulou. Pour mon besoin très occasionnel, cela fonctionne très bien et ne dépend pas d'une bibliothèque externe.
Benjamin Riggs
4
vous pouvez utiliser à la timezone.utcplace de timezone(timedelta(0)). En outre, le code fonctionne en Python 2.6+ (au moins) si vous fournissez utcun objet
tzinfo
Peu importe si vous l'avez rencontré, il ne correspond pas à la spécification.
theeannouncer
Vous pouvez utiliser le %Zfuseau horaire for dans les versions les plus récentes de Python.
sventechie
7

Je suis l'auteur d'utilitaires iso8601. Il peut être trouvé sur GitHub ou sur PyPI . Voici comment analyser votre exemple:

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

Une façon simple de convertir une chaîne de date de type ISO 8601 en un horodatage UNIX ou un datetime.datetimeobjet dans toutes les versions Python prises en charge sans installer de modules tiers consiste à utiliser l' analyseur de date de 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)

Production:

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
la source
11
Merci. C'est dégoûtant. J'aime cela.
wchargin
1
Quel incroyable, génial, beau hack! Merci!
Havok
6

J'ai codé un analyseur pour la norme ISO 8601 et je l'ai mis sur GitHub: https://github.com/boxed/iso8601 . Cette implémentation prend en charge tout dans la spécification, à l'exception des durées, intervalles, intervalles périodiques et dates en dehors de la plage de dates prise en charge du module datetime de Python.

Les tests sont inclus! : P

en boîte
la source
2
En règle générale, les liens vers un outil ou une bibliothèque doivent être accompagnés de notes d'utilisation, d'une explication spécifique de la façon dont la ressource liée est applicable au problème, ou d'un exemple de code , ou si possible tout ce qui précède.
Samuel Liew
6

La fonction parse_datetime () de Django prend en charge les dates avec des décalages UTC:

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

Il pourrait donc être utilisé pour analyser les dates ISO 8601 dans les champs de l'ensemble du projet:

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
la source
4

Parce qu'ISO 8601 autorise de nombreuses variantes de deux-points et de tirets facultatifs CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Si vous souhaitez utiliser strptime, vous devez d'abord supprimer ces variations.

Le but est de générer un objet datetime utc.


Si vous voulez juste un cas de base qui fonctionne pour UTC avec le suffixe Z comme 2016-06-29T19:36:29.3453Z:

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


Si vous souhaitez gérer les décalages de fuseau horaire comme 2016-06-29T19:36:29.3453-0400ou 2008-09-03T20:56:35.450686+05:00utilisez ce qui suit. Ceux-ci convertiront toutes les variations en quelque chose sans délimiteurs de variable, comme le 20080903T205635.450686+0500rendant plus cohérent / plus facile à analyser.

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


Si votre système ne prend pas en charge la %zdirective strptime (vous voyez quelque chose comme ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'), vous devez alors décaler manuellement l'heure deZ (UTC). Remarque %zpeut ne pas fonctionner sur votre système dans les versions python <3 car cela dépend du support de la bibliothèque c qui varie selon le type de construction système / python (c'est-à-dire Jython, Cython, etc.).

import re
import datetime

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

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

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

Pour quelque chose qui fonctionne avec la bibliothèque standard 2.X, essayez:

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

calendar.timegm est la version gm manquante de time.mktime.

Gordon Wrigley
la source
1
Cela ignore simplement le fuseau horaire '2013-01-28T14: 01: 01.335612-08: 00' -> analysé en UTC, pas PDT
gatoatigrado
2

Le python-dateutil lèvera une exception si l'analyse des chaînes de date invalides, donc vous voudrez peut-être intercepter l'exception.

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

De nos jours, il y a Maya: Datetimes for Humans ™ , de l'auteur du populaire package 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
la source
2

Une autre façon est d'utiliser l' analyseur spécialisé pour ISO-8601 est d'utiliser isoparse fonction de dateutil analyseur:

from dateutil import parser

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

Production:

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

Cette fonction est également mentionnée dans la documentation de la fonction Python standard datetime.fromisoformat :

Un analyseur ISO 8601 plus complet, dateutil.parser.isoparse est disponible dans le package tiers dateutil.

zawuza
la source
1

Grâce à la bonne réponse de Mark Amery, j'ai conçu une fonction pour prendre en compte tous les formats ISO possibles de datetime:

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

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

Notez que nous devrions regarder si la chaîne ne se termine pas par Z, nous pourrions analyser en utilisant %z.

Denny Weinberg
la source
0

Au départ, j'ai essayé avec:

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)

Mais cela n'a pas fonctionné sur les fuseaux horaires négatifs. Cependant, j'ai bien fonctionné, dans 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)

Certains tests, notent que la sortie ne diffère que par la précision des microsecondes. Je suis arrivé à 6 chiffres de précision sur ma machine, mais 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)
À
la source
Puis-je demander pourquoi avez-vous fait frozenset(('+', '-'))? Un tuple normal ne devrait-il pas ('+', '-')pouvoir accomplir la même chose?
Prahlad Yeri
Bien sûr, mais n'est-ce pas un scan linéaire plutôt qu'une recherche parfaitement hachée?
AT