756 votes

Comment créer un objet datetime tenant compte du fuseau horaire en Python ?

Ce que je dois faire

J'ai un objet datetime sans fuseau horaire, auquel je dois ajouter un fuseau horaire afin de pouvoir le comparer avec d'autres objets datetime avec fuseau horaire. Je ne veux pas convertir l'ensemble de mon application en application sans fuseau horaire pour ce seul cas particulier.

Ce que j'ai essayé

Tout d'abord, pour démontrer le problème :

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

D'abord, j'ai essayé astimezone :

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

Il n'est pas vraiment surprenant que cette opération échoue, puisqu'il s'agit en fait d'une tentative de conversion. Remplacer semblait être un meilleur choix (conformément à Python : Comment obtenir une valeur de datetime.today() qui soit "timezone aware" ? ) :

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

Mais comme vous pouvez le voir, replace semble définir le tzinfo, mais ne rend pas l'objet conscient. Je suis sur le point de revenir à la modification de la chaîne d'entrée pour avoir un fuseau horaire avant de l'analyser (j'utilise dateutil pour l'analyse, si cela a de l'importance), mais cela semble incroyablement maladroit.

De plus, j'ai essayé ceci à la fois dans python 2.6 et python 2.7, avec les mêmes résultats.

Contexte

J'écris un analyseur syntaxique pour certains fichiers de données. Je dois prendre en charge un ancien format dans lequel la chaîne de date ne comporte pas d'indicateur de fuseau horaire. J'ai déjà corrigé la source de données, mais je dois encore prendre en charge l'ancien format de données. Une conversion unique des anciennes données n'est pas envisageable pour diverses raisons liées à la BS. Bien qu'en général, je n'aime pas l'idée de coder en dur un fuseau horaire par défaut, dans ce cas, cela semble être la meilleure option. Je sais avec une confiance raisonnable que toutes les données anciennes en question sont en UTC, je suis donc prêt à accepter le risque d'utiliser ce fuseau par défaut dans ce cas.

2 votes

unaware.replace() rendrait None s'il modifiait unaware en place. Le REPL montre que .replace() renvoie un nouveau datetime objet ici.

3 votes

Ce dont j'avais besoin quand je suis venu ici : import datetime; datetime.datetime.now(datetime.timezone.utc)

4 votes

@MartinThoma J'utiliserais le nom de l'entreprise. tz arg pour être plus lisible : datetime.datetime.now(tz=datetime.timezone.utc)

824voto

unutbu Points 222216

En général, pour qu'une date naïve tienne compte du fuseau horaire, utilisez la fonction méthode de localisation :

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

Pour le fuseau horaire UTC, il n'est pas vraiment nécessaire d'utiliser localize puisqu'il n'y a pas de calcul de l'heure d'été à gérer :

now_aware = unaware.replace(tzinfo=pytz.UTC)

fonctionne. ( .replace renvoie une nouvelle date ; il ne modifie pas unaware .)

14 votes

Eh bien, je me sens stupide. Replace renvoie une nouvelle date. C'est ce qui est dit dans la documentation, et je ne l'avais pas remarqué. Merci, c'est exactement ce que je cherchais.

2 votes

"Remplacer retourne une nouvelle date." Ouaip. L'indice que le REPL vous donne est qu'il vous montre la valeur retournée. :)

0 votes

Merci, j'avais utilisé de dt_aware à unware dt_unware = datetime.datetime(*(dt_aware.timetuple()[:6])),

356voto

kang Points 2329

Tous ces exemples utilisent un module externe, mais vous pouvez obtenir le même résultat en utilisant uniquement le module datetime, comme cela a été présenté dans le document suivant cette réponse SO :

from datetime import datetime, timezone

dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)

print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'

Moins de dépendances et pas de problèmes de pytz.

NOTE : Si vous souhaitez utiliser ceci avec python3 et python2, vous pouvez également utiliser ceci pour l'importation du fuseau horaire (codé en dur pour UTC) :

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()

114voto

Sérgio Points 854

J'ai écrit ceci le 22 novembre 2011, c'est uniquement pour Python 2, je n'ai jamais vérifié si cela fonctionnait pour Python 3.

J'ai utilisé de dt_aware à dt_unaware.

dt_unaware = dt_aware.replace(tzinfo=None)

et de dt_unware à dt_aware

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)

mais la réponse précédente est aussi une bonne solution.

4 votes

Vous pourriez utiliser localtz.localize(dt_unware, is_dst=None) pour lever une exception si dt_unware représente une heure locale inexistante ou ambiguë (remarque : il n'y avait pas de problème de ce type dans la révision précédente de votre réponse où localtz était UTC parce que l'UTC n'a pas de transitions DST

0 votes

@J.F. Sebastian , premier commentaire appliqué

4 votes

J'apprécie que vous montriez les deux sens de la conversion.

48voto

Googol Points 337

J'utilise cette déclaration dans Django pour convertir un temps unaware en un temps aware :

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

45voto

xjcl Points 608

Python 3.9 ajoute la fonction zoneinfo module donc maintenant, seule la bibliothèque standard est nécessaire !

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

Attachez un fuseau horaire :

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

Attachez le fuseau horaire local du système :

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

Ensuite, il est correctement converti dans d'autres fuseaux horaires :

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

Liste Wikipedia des fuseaux horaires disponibles


Windows n'a pas La base de données des fuseaux horaires du système, donc ici un paquet supplémentaire est nécessaire :

pip install tzdata  

Il existe un backport pour permettre l'utilisation de zoneinfo sur Python 3.6 à 3.8 :

pip install backports.zoneinfo

Ensuite :

from backports.zoneinfo import ZoneInfo

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X