web-dev-qa-db-fra.com

Comment puis-je obtenir l'heure UTC de "minuit" pour un fuseau horaire donné?

Le mieux que je puisse trouver pour l'instant est cette monstruosité:

>>> datetime.utcnow() \
...   .replace(tzinfo=pytz.UTC) \
...   .astimezone(pytz.timezone("Australia/Melbourne")) \
...   .replace(hour=0,minute=0,second=0,microsecond=0) \
...   .astimezone(pytz.UTC) \
...   .replace(tzinfo=None)
datetime.datetime(2008, 12, 16, 13, 0)

C'est-à-dire, en anglais, obtenir l'heure actuelle (en UTC), la convertir en un autre fuseau horaire, régler l'heure sur minuit, puis reconvertir en UTC.

Je n'utilise pas seulement now () ou localtime () car cela utiliserait le fuseau horaire du serveur, pas le fuseau horaire de l'utilisateur.

Je ne peux pas m'empêcher de sentir que je manque quelque chose, des idées?

39
Tom

Je pense que vous pouvez raser quelques appels de méthode si vous le faites comme ceci:

>>> from datetime import datetime
>>> datetime.now(pytz.timezone("Australia/Melbourne")) \
            .replace(hour=0, minute=0, second=0, microsecond=0) \
            .astimezone(pytz.utc)

MAIS… il y a un problème plus important que l'esthétique dans votre code: il donnera le mauvais résultat le jour du passage à ou depuis l'heure d'été.

La raison en est que ni les constructeurs datetime ni replace() ne prennent en compte les modifications DST.

Par exemple:

>>> now = datetime(2012, 4, 1, 5, 0, 0, 0, tzinfo=pytz.timezone("Australia/Melbourne"))
>>> print now
2012-04-01 05:00:00+10:00
>>> print now.replace(hour=0)
2012-04-01 00:00:00+10:00 # wrong! midnight was at 2012-04-01 00:00:00+11:00
>>> print datetime(2012, 3, 1, 0, 0, 0, 0, tzinfo=tz)
2012-03-01 00:00:00+10:00 # wrong again!

Cependant, la documentation de tz.localize() indique:

Cette méthode doit être utilisée pour construire localtimes, plutôt que de passer un argument tzinfo à un constructeur datetime.

Ainsi, votre problème est résolu comme suit:

>>> import pytz
>>> from datetime import datetime, date, time

>>> tz = pytz.timezone("Australia/Melbourne")
>>> the_date = date(2012, 4, 1) # use date.today() here

>>> midnight_without_tzinfo = datetime.combine(the_date, time())
>>> print midnight_without_tzinfo
2012-04-01 00:00:00

>>> midnight_with_tzinfo = tz.localize(midnight_without_tzinfo)
>>> print midnight_with_tzinfo
2012-04-01 00:00:00+11:00

>>> print midnight_with_tzinfo.astimezone(pytz.utc)
2012-03-31 13:00:00+00:00

Aucune garantie pour les dates antérieures à 1582, cependant.

61
user3850

@ hop's answer est erroné le jour de la transition de l'heure d'été (DST) par exemple, le 1er avril 2012. Pour le corriger tz.localize() pourrait être utilisé:

tz = pytz.timezone("Australia/Melbourne")
today = datetime.now(tz).date()
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)
utc_dt = midnight.astimezone(pytz.utc)        

La même chose avec des commentaires:

#!/usr/bin/env python
from datetime import datetime, time
import pytz # pip instal pytz

tz = pytz.timezone("Australia/Melbourne") # choose timezone

# 1. get correct date for the midnight using given timezone.
today = datetime.now(tz).date()

# 2. get midnight in the correct timezone (taking into account DST)
#NOTE: tzinfo=None and tz.localize()
# assert that there is no dst transition at midnight (`is_dst=None`)
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)

# 3. convert to UTC (no need to call `utc.normalize()` due to UTC has no 
#    DST transitions)
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
print midnight.astimezone(pytz.utc).strftime(fmt)
24
jfs

La définition de la variable d'environnement TZ modifie avec quel fuseau horaire les fonctions de date et d'heure de Python fonctionnent.

>>> time.gmtime()
(2008, 12, 17, 1, 16, 46, 2, 352, 0)
>>> time.localtime()
(2008, 12, 16, 20, 16, 47, 1, 351, 0)
>>> os.environ['TZ']='Australia/Melbourne'
>>> time.localtime()
(2008, 12, 17, 12, 16, 53, 2, 352, 1)
0

Chaque fuseau horaire a un numéro, par exemple US/Central = -6. Ceci est défini comme le décalage en heures par rapport à UTC. Étant donné que 0000 est minuit, vous pouvez simplement utiliser ce décalage pour trouver l'heure dans n'importe quel fuseau horaire lorsqu'il est minuit UTC. Pour y accéder, je pense que vous pouvez utiliser

 time.timezone

Selon The Python Docs , time.timezone donne en fait la valeur négative de ce nombre:

time.timezone

Décalage du fuseau horaire local (non DST), en secondes à l'ouest de l'UTC (négatif dans la plupart des pays d'Europe occidentale, positif aux États-Unis, zéro au Royaume-Uni).

Donc, vous utiliseriez simplement ce nombre pour le temps en heures s'il est positif (c'est-à-dire s'il est minuit à Chicago (qui a une valeur de fuseau horaire +6), alors c'est 6000 = 6h UTC).

Si le nombre est négatif, soustrayez de 24. Par exemple, Berlin donnerait -1, donc 24 - 1 => 2300 = 23h.

0
Colin

C'est plus simple avec dateutil.tz que pytz:

>>>import datetime
>>>import dateutil.tz
>>>midnight=(datetime.datetime
             .now(dateutil.tz.gettz('Australia/Melbourne'))
             .replace(hour=0, minute=0, second=0, microsecond=0)
             .astimezone(dateutil.tz.tzutc()))
>>>print(midnight)
2019-04-26 14:00:00+00:00

La documentation tzinfo recommande dateutil.tz puisque Python 3.6. Les objets tzinfo de dateutil.tz n'ont aucun problème avec des anomalies comme DST sans nécessiter la fonctionnalité de localisation de pytz. l'exemple de user3850:

>>> now = (datetime.datetime(2012, 4, 1, 5,  
...         tzinfo = dateutil.tz.gettz('Australia/Melbourne'))) 
>>> print(now.replace(hour = 0).astimezone(dateutil.tz.tzutc()))
2012-03-31 13:00:00+00:00
0
Noyer282