J'ai besoin d'analyser les chaînes RFC 3339 comme "2008-09-03T20:56:35.450686Z"
dans le datetime
type Python .
J'ai trouvé strptime
dans la bibliothèque standard Python, mais ce n'est pas très pratique.
Quelle est la meilleure façon de procéder?
J'ai besoin d'analyser les chaînes RFC 3339 comme "2008-09-03T20:56:35.450686Z"
dans le datetime
type Python .
J'ai trouvé strptime
dans la bibliothèque standard Python, mais ce n'est pas très pratique.
Quelle est la meilleure façon de procéder?
Réponses:
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.isoparse
c'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-dateutil
non 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
.
python-dateutil
pas dateutil
, donc: pip install python-dateutil
.
dateutil.parser
est 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.
La datetime
bibliothèque standard a introduit une fonction pour inverser datetime.isoformat()
.
classmethod
datetime.fromisoformat(date_string)
:Renvoie un
datetime
correspondant à undate_string
dans l'un des formats émis pardate.isoformat()
etdatetime.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]]]]
où
*
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')
datetime
peut contenir un tzinfo
, et donc afficher un fuseau horaire, mais datetime.fromisoformat()
ne pas analyser le tzinfo? semble être un bug ..
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"
.
Z
script d'entrée, vous pouvez le modifier avec date_string.replace("Z", "+00:00")
.
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
strptime
est en fait impossible.
datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f')
donc cela a fait l'affaire
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 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 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:MM
ou -HH:MM
, comme +05:00
ou -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 strptime
et strftime
n'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 %z
dans 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 strptime
n'implémente pas du tout la %z
directive dans Python 2. )
Les multiples réponses ici qui recommandent de strptime
contourner ce problème en incluant un littéral Z
dans leur chaîne de format, qui correspond Z
à l'exemple de la chaîne datetime du demandeur (et la rejette, 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 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.
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.
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.
iso8601.parse_date("2008-09-03T20:56:35.450686Z")
importer re, datetime s = "2008-09-03T20: 56: 35.450686Z" d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))
datetime.datetime(*map(int, re.findall('\d+', s))
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)
""
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é.
À 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 ()
datetime.fromisoformat()
quelles chaînes poignées comme votre entrée automatiquement: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00')
.
datetime.fromisoformat()
etdatetime.isoformat()
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())
Utilisez simplement le python-dateutil
module:
>>> 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())
455051100
(vérifié sur epochconverter.com ) ,,, sauf si je manque quelque chose?
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)
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.
toISOString
mé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 dateutil
est généralement aussi pratique et moins étroite dans ce qu'elle peut analyser.
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.
DateTimeField
utilise lorsque vous définissez une valeur de chaîne.
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!
datetime.strptime()
est la prochaine solution la plus rapide. Merci d'avoir rassemblé toutes ces informations!
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.
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)
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.
timezone.utc
place de timezone(timedelta(0))
. En outre, le code fonctionne en Python 2.6+ (au moins) si vous fournissez utc
un objet
%Z
fuseau horaire for dans les versions les plus récentes de Python.
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)
Une façon simple de convertir une chaîne de date de type ISO 8601 en un horodatage UNIX ou un datetime.datetime
objet 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
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
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')
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.
2016-06-29T19:36:29.3453Z
:
datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")
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 variable, comme le 20080903T205635.450686+0500
rendant 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" )
%z
directive 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 %z
peut 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
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.
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
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>)
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.
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))
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
.
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)
frozenset(('+', '-'))
? Un tuple normal ne devrait-il pas ('+', '-')
pouvoir accomplir la même chose?