Naïve datetime
versus aware datetime
Les objets datetime
par défaut sont dits "naïfs" : ils conservent les informations de temps sans les informations de fuseau horaire. Pensez à un datetime
naïf comme un nombre relatif (par exemple : +4
) sans origine claire (en fait, votre origine sera commune à toute la limite de votre système).
En revanche, pensez à un datetime
conscient comme des nombres absolus (par exemple : 8
) avec une origine commune pour le monde entier.
Sans les informations sur le fuseau horaire, vous ne pouvez pas convertir le datetime "naïf" vers une autre représentation non naïve du temps (où se situe +4
si nous ne savons pas d'où partir ?). C'est pourquoi vous ne pouvez pas avoir de méthode datetime.datetime.toutctimestamp()
. (cf : http://bugs.python.org/issue1457227)
Pour vérifier si votre datetime
dt
est naïf, vérifiez dt.tzinfo
, si c'est None
, alors il est naïf :
datetime.now() ## DANGER : retourne un datetime naïf pointant sur l'heure locale
datetime(1970, 1, 1) ## retourne un datetime naïf pointant sur l'heure donnée par l'utilisateur
J'ai des datetimes naïfs, que puis-je faire ?
Vous devez faire une hypothèse en fonction de votre contexte particulier : La question que vous devez vous poser est la suivante : votre datetime
était-il en UTC ? ou était-ce l'heure locale ?
-
Si vous utilisiez l'UTC (vous êtes hors de danger) :
import calendar
def dt2ts(dt):
"""Convertit un objet datetime en timestamp UTC
les datetime naïfs seront considérés comme UTC.
"""
return calendar.timegm(dt.utctimetuple())
-
Si vous n'utilisiez PAS l'UTC, bienvenue en enfer.
Vous devez rendre vos datetimes non naïfs avant d'utiliser la fonction précédente, en leur redonnant leur fuseau horaire prévu.
Vous aurez besoin du nom du fuseau horaire et de l'information sur si l'heure d'été était en vigueur lors de la création du datetime naïf cible (la dernière information sur l'heure d'été est nécessaire pour les cas particuliers) :
import pytz ## pip install pytz
mytz = pytz.timezone('Europe/Amsterdam') ## Définissez votre fuseau horaire
dt = mytz.normalize(mytz.localize(dt, is_dst=True)) ## Définissez is_dst en conséquence
Conséquences de ne pas fournir is_dst
:
Ne pas utiliser is_dst
générera un horaire incorrect (et un timestamp UTC) si le datetime cible a été créé pendant un changement d'heure d'été (par exemple, en retirant une heure).
Fournir un is_dst
incorrect générera naturellement un horaire incorrect (et un timestamp UTC) uniquement en cas de chevauchement ou de trous dans l'heure d'été. Et, lorsque fournir également un horaire incorrect, se produisant dans des "trous" (heure qui n'a jamais existé en raison du changement d'heure de l'été), is_dst
donnera une interprétation de comment considérer cet horaire erroné, et c'est le seul cas où .normalize(..)
fera quelque chose, car il le traduira alors en un horaire réel (changeant le datetime ET l'objet DST si nécessaire). Notez que .normalize()
n'est pas requis pour avoir un timestamp UTC correct à la fin, mais il est probablement recommandé si vous n'aimez pas l'idée d'avoir des horaires erronés dans vos variables, surtout si vous réutilisez cette variable ailleurs.
et ÉVITEZ D'UTILISER CE QUI SUIT : (cf : Convertir le fuseau horaire du datetime avec pytz)
dt = dt.replace(tzinfo=timezone('Europe/Amsterdam')) ## MAUVAIS !!
Pourquoi ? car .replace()
remplace aveuglément le tzinfo
sans prendre en compte l'heure cible et choisira un mauvais objet DST. Tandis que .localize()
utilise l'heure cible et votre indice is_dst
pour sélectionner le bon objet DST.
Ancienne réponse incorrecte (merci @J.F.Sebastien d'avoir soulevé cela) :
Heureusement, il est assez facile de deviner le fuseau horaire (votre origine locale) lorsque vous créez votre objet datetime naïf car il est lié à la configuration système que vous ne devriez probablement PAS changer entre la création de l'objet datetime naïf et le moment où vous voulez obtenir le timestamp UTC. Cette astuce peut être utilisée pour poser une question imparfaite.
En utilisant time.mktime
nous pouvons créer un utc_mktime
:
def utc_mktime(utc_tuple):
"""Retourne le nombre de secondes écoulées depuis l'époque
Notez qu'aucun fuseau horaire n'est pris en compte.
le tuple UTC doit être : (année, mois, jour, heure, minute, seconde)
"""
if len(utc_tuple) == 6:
utc_tuple += (0, 0, 0)
return time.mktime(utc_tuple) - time.mktime((1970, 1, 1, 0, 0, 0, 0, 0, 0))
def datetime_to_timestamp(dt):
"""Convertit un objet datetime en timestamp UTC"""
return int(utc_mktime(dt.timetuple()))
Assurez-vous que votre objet datetime
est créé dans le même fuseau horaire que celui qui a créé votre datetime
.
Cette dernière solution est incorrecte car elle suppose que le décalage UTC actuel est le même que le décalage UTC à partir de l'ÉPOQUE. Ce qui n'est pas le cas pour de nombreux fuseaux horaires (à des moments spécifiques de l'année pour les décalages de l'heure d'été).