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)

25voto

paolov Points 696

Je suis d'accord avec les réponses précédentes, et c'est très bien si vous êtes d'accord pour commencer en UTC. Mais je pense que c'est aussi un scénario courant pour les gens qui travaillent avec une valeur consciente de tz qui a une valeur de datetime qui a un fuseau horaire local non UTC.

Si l'on s'en tenait au nom, on en déduirait probablement que replace() sera applicable et produira le bon objet de type datetime. Ce n'est pas le cas.

le remplacement( tzinfo=... ) semble avoir un comportement aléatoire . Il est donc inutile. Ne l'utilisez pas !

localize est la fonction correcte à utiliser. Exemple :

localdatetime_aware = tz.localize(datetime_nonaware)

Ou un exemple plus complet :

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

me donne une valeur de datetime consciente du fuseau horaire de l'heure locale actuelle :

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)

14voto

Ahmet Points 30

Utilisez dateutil.tz.tzlocal() pour obtenir le fuseau horaire dans votre utilisation de datetime.datetime.now() et datetime.datetime.astimezone() :

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

Notez que datetime.astimezone convertira d'abord votre datetime en UTC puis dans le fuseau horaire, ce qui revient à appeler datetime.replace avec l'information originale sur le fuseau horaire étant None .

10voto

hobs Points 3020

Cela codifie l'avis de @Sérgio et @unutbu. réponses . Il fonctionnera "simplement" avec un pytz.timezone ou un objet Fuseau horaire IANA chaîne.

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

Cela semble être ce que datetime.localize() (ou .inform() ou .awarify() ) devrait faire, accepter à la fois les chaînes et les objets de fuseau horaire pour l'argument tz et choisir par défaut UTC si aucun fuseau horaire n'est spécifié.

1 votes

Merci, cela m'a aidé à "marquer" un objet datetime brut comme "UTC" sans que le système ne suppose d'abord qu'il s'agit de l'heure locale et ne recalcule ensuite les valeurs !

9voto

Harry Moreno Points 1254

Pour ceux qui veulent simplement créer une date qui tient compte du fuseau horaire.

import datetime
import pytz

datetime.datetime(2019, 12, 7, tzinfo=pytz.UTC)

4voto

ivanleoncz Points 1940

Une autre façon d'avoir un datetime objet NON naïf :

>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2021, 5, 1, 22, 51, 16, 219942, tzinfo=datetime.timezone.utc)

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