Comment analyser une date formatée ISO 8601?
j'ai besoin d'analyser RFC 3339 comme les chaînes "2008-09-03T20:56:35.450686Z"
en Python "151910920 de type".
j'ai trouvé strptime
dans la bibliothèque standard de Python, mais ce n'est pas très pratique.
Quelle est la meilleure façon de le faire?
24 réponses
le paquet python-dateutil peut analyser non seulement les chaînes datetime RFC 3339 comme celle de la question, mais aussi d'autres chaînes date et heure ISO 8601 qui ne sont pas conformes à la RFC 3339 (comme celles sans décalage UTC, ou celles qui ne représentent qu'une date).
>>> import dateutil.parser
>>> dateutil.parser.parse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.parse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.parse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.parse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)
soyez averti que le dateutil.parser
est intentionnellement hacky: il tente de deviner le format et rend inévitable hypothèses (personnalisables à la main seulement) dans les cas Ambigus. Donc ne l'utilisez que si vous avez besoin d'analyser des entrées de format inconnu et êtes d'accord pour tolérer des erreurs occasionnelles. (merci ivan_pozdeev )
le nom Pypi est python-dateutil
, pas dateutil
(merci code3monk3y ):
pip install python-dateutil
si vous utilisez Python 3.7, jetez un oeil à cette réponse à propos de datetime.datetime.fromisoformat
.
plusieurs réponses ici suggérez en utilisant datetime.datetime.strptime
pour analyser RFC 3339 ou ISO 8601 datetimes avec des fuseaux horaires, comme celui exposé dans la question:
2008-09-03T20:56:35.450686Z
C'est une mauvaise idée.
en supposant que vous voulez supporter le format complet RFC 3339, y compris le support pour les offsets UTC autres que zéro, alors le code suggéré par ces réponses ne fonctionne pas. En effet, il ne peut pas fonctionner, parce que l'analyse de la syntaxe RFC 3339 en utilisant strptime
est 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 UTC offsets. Le RFC 3339 Internet Date/Time Format exige que chaque date-heure inclut un décalage UTC, et que ces décalages peuvent être soit Z
(abréviation de" Zulu time") ou au format +HH:MM
ou -HH:MM
, comme +05:00
ou -10:30
.
par conséquent, ce sont tous les datetimes valides RFC 3339:
-
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 strptime
et strftime
n'ont pas de directive qui correspond à UTC offsets en format RFC 3339. Une liste complète des directives qu'ils soutiennent peut être trouvée à https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , et la seule directive offset UTC incluse dans la liste est %z
:
%z
UTC offset dans 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 offset RFC 3339, et en effet si nous essayons d'utiliser %z
dans la chaîne de format et 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. En Python 2, nous échouerons pour une raison encore plus simple, qui est que strptime
n'implémente pas du tout la directive %z
en Python 2 .)
les multiples réponses ici qui recommandent strptime
tout le travail autour de cela en incluant un littéral Z
dans leur chaîne de format, qui correspond à la Z
de la question Asker exemple chaîne datetime (et l'écarte, produisant un datetime
objet 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 écarte les informations de fuseau horaire qui étaient incluses dans la chaîne datetime originale, il est douteux que nous devrions considérez même ce résultat comme correct. Mais plus important encore, parce que cette approche implique codage dur un décalage UTC particulier dans la chaîne de format , il va étouffer le moment où il essaie de parser n'importe quel RFC 3339 datetime 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'
sauf si vous êtes certain que vous n'avez besoin de prendre en charge que les datetimes RFC 3339 en temps Zoulou, et pas ceux avec d'autres décalages de fuseau horaire, n'utilisez pas strptime
. Utiliser l'un des nombreux autres approches décrites dans les réponses ici.
essayez le module iso8601 ; il fait exactement cela.
il y a plusieurs autres options mentionnées sur la page WorkingWithTime python.org wiki.
import re,datetime s="2008-09-03T20:56:35.450686Z" d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))
nouveau en python 3.7+
la bibliothèque standard datetime
a introduit une fonction d'inversion datetime.isoformat()
.
classmethod
datetime.fromisoformat(date_string)
:Retour
datetime
correspondant à undate_string
dans l'un des formats de émis pardate.isoformat()
etdatetime.isoformat()
.spécifiquement, cette fonction supporte les chaînes dans le(S) format (s):
YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]
où
*
peut correspondre à n'importe quel caractère.Caution : ceci ne supporte pas l'analyse arbitraire des chaînes ISO 8601 - il est seulement destiné à l'inverse opération
datetime.isoformat()
.
exemple d'utilisation:
from datetime import datetime
date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
Quelle est l'erreur exacte que vous obtenez? Est-ce que c'est 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 partager votre chaîne de caractères ".", puis Ajouter les microsecondes à la datetime que vous avez.
essayez ceci:
>>> 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)
>>>
personne ne l'a encore mentionné. De nos jours, Arrow peut également être utilisé comme une solution de tiers.
>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
à partir de Python 3.7, strptime supporte les délimiteurs de deux points dans les offsets UTC ( source ). Vous pouvez alors utiliser:
import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')
Si vous ne voulez 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)
Test:
from_utc("2007-03-04T21:08:12.123Z")
résultat:
datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
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 voulez pas utiliser l'une des autres bibliothèques mentionnées ici, vous pouvez probablement adapter le code source de Django pour dateparse à votre projet.
tellement plus simple que vous le faites tous.
si vous voulez obtenir les secondes depuis epoch, vous pouvez utiliser python-dateutil pour le convertir en un objet datetime et ensuite le convertir en secondes en utilisant la méthode strftime. Comme ceci:
>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> t_in_seconds = parsed_t.strftime('%s')
>>> t_in_seconds
'455047500'
Note: cela convertira le datetime
donné en temps d'époque. Mais vous pouvez utiliser la fonction strftime()
pour convertir ce datetime
dans n'importe quel format. L'objet parsed_t
ici est de type datetime
à ce point.
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 supporte tout dans les spécifications sauf les durées, intervalles et intervalles périodiques et les dates en dehors de la plage de dates supportée du module pythons datetime.
Tests inclus! : P
je suis l'auteur de iso8601utils. Il peut être trouvé sur github ou sur PyPI . Voici comment vous pouvez analyser votre exemple:
>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
Espérons que cette aide!
Django's parse_datetime () la fonction supporte les dates avec UTC offsets:
parse_datetime('2016-08-09T15:12:03.65478Z') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)
afin qu'il puisse être utilisé pour analyser les dates iso-8601 dans les domaines du projet entier:
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')
j'ai trouvé que ciso8601 est le moyen le plus rapide pour analyser les horodateurs ISO 8601. Comme son nom l'indique, il est mis en œuvre en C.
import ciso8601
ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')
Le dépôt GitHub README montre leur >10x plus rapide par rapport à tous les autres bibliothèques répertoriées dans les autres réponses.
mon projet personnel impliquait beaucoup D'analyse ISO 8601. C'était sympa de pouvoir passer l'appel et aller 10 fois plus vite. :)
Edit: j'ai, depuis, devenue un responsable de ciso8601. Il est maintenant plus rapide que jamais!
pour quelque chose qui fonctionne avec le 2.X bibliothèque standard essayer:
calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))
calendrier.timegm est la version manquante de time.mktime.
le python-dateutil va lancer une exception si vous analysez des chaînes de date invalides, donc vous pouvez vouloir attraper 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
une façon simple de convertir une chaîne de date de type ISO 8601 en un timestamp UNIX ou datetime.datetime
dans toutes les versions Python supportées sans installer de modules tiers est d'utiliser le datateur 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)
sortie:
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
parce que la norme ISO 8601 autorise de nombreuses variantes de couleurs et de tirets optionnels, essentiellement CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
. Si vous voulez utiliser strptime, vous devez d'abord supprimer ces variantes.
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 voulez gérer des décalages de fuseau horaire comme
2016-06-29T19:36:29.3453-0400
ou 2008-09-03T20:56:35.450686+05:00
utilisez ce qui suit. Ceux-ci convertiront toutes les variations en quelque chose sans délimiteurs de variables comme 20080903T205635.450686+0500
ce qui le rend 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 supporte pas la directive
%z
strptime (vous voyez quelque chose comme ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'
), alors vous devez décaler manuellement le temps de Z
(UTC). Note %z
peut ne pas fonctionner sur votre système en version python < 3 car il dépend du support de la bibliothèque c qui varie selon le type de construction système/python (par exemple 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
cela fonctionne pour stdlib sur Python 3.2 à partir de là (edit: en supposant que tous les horodateurs sont UTC):
from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
tzinfo=timezone(timedelta(0)))
p.ex.
>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
merci à grand réponse de Mark Amery's j'ai conçu la fonction pour rendre compte de 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'', 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))
de nos jours Il y a Maya: Datetimes for Humans™ , de L'auteur des requêtes populaires: HTTP for Humans™ package:
>>> 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>)
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 avec Z
, nous pourrions Parser en utilisant %z
.