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?

472
demandé sur Martin Thoma 2008-09-24 19:17:00

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 .

321
répondu Flimm 2018-08-23 16:25:26

Note en python 2.6+ et Py3K, le caractère %f prend des microsecondes.

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

voir numéro ici

126
répondu sethbc 2016-01-15 18:02:05

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.

111
répondu Mark Amery 2017-05-23 12:18:27

essayez le module iso8601 ; il fait exactement cela.

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

65
répondu Nicholas Riley 2013-10-30 11:47:40
import re,datetime
s="2008-09-03T20:56:35.450686Z"
d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))
35
répondu Ted 2008-09-24 15:27:24

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 à un date_string dans l'un des formats de émis par date.isoformat() et datetime.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]]]]

* 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')
27
répondu abccd 2018-04-11 20:32:10

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)
>>> 
25
répondu tzot 2013-02-24 15:38:52

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())
19
répondu Ilker Kesen 2015-04-06 23:11:57

à 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')
13
répondu Andreas Profous 2018-01-31 09:52:55

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)
12
répondu enchanter 2014-03-27 22:50:16

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.

10
répondu Don Kirkby 2015-09-30 21:42:22

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'

Source

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.

8
répondu Blairg23 2017-05-23 12:34:51

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

6
répondu boxed 2013-03-02 13:31:49

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!

6
répondu Marc Wilson 2016-10-26 05:20:18

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')
5
répondu Artem Vasilev 2016-09-08 09:42:18

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!

5
répondu movermeyer 2018-06-05 13:19:37

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.

2
répondu Gordon Wrigley 2011-07-21 06:47:19

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
2
répondu user2646026 2013-08-09 15:53:22

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
2
répondu Damian Yerrick 2016-08-25 16:16:26

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
2
répondu theannouncer 2018-03-27 20:56:59

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)
1
répondu Benjamin Riggs 2015-12-29 21:31:28

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))
1
répondu omikron 2017-05-23 11:55:00

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>)
1
répondu jrc 2018-09-24 18:21:02
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 .

0
répondu Denny Weinberg 2016-08-09 10:17:22