web-dev-qa-db-fra.com

Obtenir le décalage de fuseau horaire correct dans Python en utilisant le fuseau horaire local

Ok, permettez-moi de commencer par dire que mon fuseau horaire est CET/CEST. Le moment exact où il passe de CEST à CET (retour de DST, qui est GMT + 2, à normal, qui GMT + 1, donc) est toujours le dernier dimanche d'octobre à 3 heures du matin. En 2010, c'était le 31 octobre à 3 heures du matin.

Notez maintenant ce qui suit:

>>> import datetime
>>> import pytz.reference
>>> local_tnz = pytz.reference.LocalTimezone()
>>> local_tnz.utcoffset(datetime.datetime(2010, 10, 31, 2, 12, 30))
datetime.timedelta(0, 3600)

C'est faux comme expliqué ci-dessus.

>>> local_tnz.utcoffset(datetime.datetime(2010, 10, 30, 2, 12, 30))
datetime.timedelta(0, 7200)
>>> local_tnz.utcoffset(datetime.datetime(2010, 10, 31, 2, 12, 30))
datetime.timedelta(0, 7200)

Maintenant, c'est soudainement correct: /

Je sais qu'il y a déjà plusieurs questions à ce sujet, mais la solution proposée est toujours "utiliser la localisation", mais mon problème ici est que le LocalTimezone ne fournit pas cette méthode.

En fait, j'ai plusieurs horodatages en millisecondes dont j'ai besoin de l'utcoffset du fuseau horaire local (pas seulement le mien, mais de toute personne utilisant le programme). L'un d'eux est 1288483950000 ou Sun 31 Oct 2010 02:12:30 GMT + 0200 (CEST) dans mon fuseau horaire.

Actuellement, je fais ce qui suit pour obtenir l'objet datetime:

datetime.datetime.fromtimestamp(int(int(millis)/1E3)) 

et ceci pour obtenir l'utcoffset en quelques minutes:

-int(local_tnz.utcoffset(date).total_seconds()/60)

ce qui, malheureusement, est erroné à de nombreuses reprises :(.

Des idées?

Remarque: j'utilise python3.2.4, pas que cela devrait avoir de l'importance dans ce cas.

MODIFIER:

Trouvé la solution grâce à @JamesHolderness:

def datetimeFromMillis(millis):
    return pytz.utc.localize(datetime.datetime.utcfromtimestamp(int(int(millis)/1E3)))

def getTimezoneOffset(date):
    return -int(date.astimezone(local_tz).utcoffset().total_seconds()/60)

Avec local_tz égal à tzlocal.get_localzone () du module tzlocal.

18
Joren Van Severen

Selon Wikipedia , la transition vers et depuis l'heure d'été a lieu à 01h00 UTC.

  • À 00:12 UTC, vous êtes toujours à l'heure d'été d'Europe centrale (c'est-à-dire UTC + 02: 00), donc l'heure locale est 02:12.

  • À 01:12 UTC, vous êtes de retour à l'heure d'Europe centrale standard (c'est-à-dire UTC + 01: 00), donc l'heure locale est à nouveau 02:12.

Lorsque vous revenez de l'heure d'été à l'heure standard, l'heure locale passe de 02:59 à 02:00 et l'heure se répète. Ainsi, lorsque vous demandez le décalage UTC de 02:12 (heure locale), la réponse pourrait être soit +01: 00 ou +02: 00 - cela dépend de la version de 02:12 dont vous parlez.

Après une enquête plus approfondie sur la bibliothèque pytz, je pense que votre problème peut être que vous ne devriez pas utiliser l'implémentation pytz.reference, qui peut ne pas très bien gérer ces ambiguïtés. Citant les commentaires dans le code source:

Référencez les implémentations tzinfo des documents Python. Utilisées pour les tests car elles ne sont correctes que pour les années 1987 à 2006. Ne les utilisez pas pour du code réel.

Travailler avec des temps ambigus en pytz

Ce que vous devriez faire est de construire un objet timezone pour le fuseau horaire approprié:

import pytz
cet = pytz.timezone('CET')

Ensuite, vous pouvez utiliser la méthode utcoffset pour calculer le décalage UTC d'une date/heure dans ce fuseau horaire.

dt = datetime.datetime(2010, 10, 31, 2, 12, 30)
offset = cet.utcoffset(dt)

Notez que l'exemple ci-dessus lèvera une exception AmbiguousTimeError, car il ne peut pas dire laquelle des deux versions de 02:12:30 vous vouliez dire. Heureusement, pytz vous permettra de spécifier si vous souhaitez la version dst ou la version standard en définissant le paramètre is_dst. Par exemple:

offset = cet.utcoffset(dt, is_dst = True)

Notez que cela ne nuit pas de définir ce paramètre sur tous les appels à utcoffset, même si l'heure ne serait pas ambiguë. Selon la documentation, il n'est utilisé que pendant les périodes ambiguës de transition DST pour résoudre cette ambiguïté.

Comment gérer les horodatages

En ce qui concerne les horodatages, il est préférable de les stocker en tant que valeurs UTC aussi longtemps que possible, sinon vous risquez de jeter des informations précieuses. Convertissez donc d'abord en datetime UTC avec la méthode datetime.utcfromtimestamp.

dt = datetime.datetime.utcfromtimestamp(1288483950)

Utilisez ensuite pytz pour localiser l'heure au format UTC, afin que le fuseau horaire soit attaché à l'objet datetime.

dt = pytz.utc.localize(dt)

Enfin, vous pouvez convertir ce datetime UTC dans votre fuseau horaire local et obtenir le décalage de fuseau horaire comme ceci:

offset = dt.astimezone(cet).utcoffset()

Notez que cet ensemble de calculs produira les décalages corrects pour les deux 1288483950 et 1288487550, même si les deux horodatages sont représentés par 02:12:30 dans le fuseau horaire CET.

Détermination du fuseau horaire local

Si vous devez utiliser le fuseau horaire local de votre ordinateur plutôt qu'un fuseau horaire fixe, vous ne pouvez pas le faire directement à partir de pytz. Vous ne pouvez pas non plus simplement construire un objet pytz.timezone en utilisant le nom du fuseau horaire de time.tzname, car les noms ne seront pas toujours reconnus par pytz .

La solution est d'utiliser le module tzlocal - son seul but est de fournir cette fonctionnalité manquante dans pytz. Vous l'utilisez comme ceci:

import tzlocal
local_tz = tzlocal.get_localzone()

La fonction get_localzone () renvoie un objet pytz.timezone, vous devriez donc pouvoir utiliser cette valeur dans tous les endroits où j'ai utilisé le cet variable dans les exemples ci-dessus.

26
James Holderness

Étant donné un horodatage en millisecondes, vous pouvez obtenir le décalage utc pour le fuseau horaire local en utilisant uniquement stdlib:

#!/usr/bin/env python
from datetime import datetime

millis = 1288483950000
ts = millis * 1e-3
# local time == (utc time + utc offset)
utc_offset = datetime.fromtimestamp(ts) - datetime.utcfromtimestamp(ts)

Si nous ignorons le temps autour des secondes intercalaires, il n'y a pas d'ambiguïté ou de temps inexistant.

Il prend en charge l'heure d'été et les modifications du décalage utc pour d'autres raisons si le système d'exploitation maintient un db de fuseau horaire historique, par exemple, il devrait fonctionner sur Ubuntu pour n'importe quelle date passée/présente, mais peut se casser sur Windows pour les dates antérieures qui utilisaient un décalage utc différent.

Voici la même chose en utilisant le module tzlocal qui devrait fonctionner sur les systèmes * nix et Win32:

#!/usr/bin/env python
from datetime import datetime
from tzlocal import get_localzone # pip install tzlocal

millis = 1288483950000
ts = millis * 1e-3
local_dt = datetime.fromtimestamp(ts, get_localzone())
utc_offset = local_dt.utcoffset()

Voir Comment convertir un python utc datetime en un datetime local en utilisant uniquement python bibliothèque standard?

Pour obtenir le décalage utc en quelques minutes (Python 3.2+):

from datetime import timedelta

minutes = utc_offset / timedelta(minutes=1)

N'utilisez pas pytz.reference.LocalTimezone(), c'est uniquement pour les tests .

9
jfs