web-dev-qa-db-fra.com

Comment rendre conscient un fuseau horaire datetime non conscient dans python

Ce que je dois faire

J'ai un objet datetime non conscient du fuseau horaire auquel je dois ajouter un fuseau horaire pour pouvoir le comparer à d'autres objets datetime sensibles à un fuseau horaire. Je ne veux pas convertir l'intégralité de mon application en fuseau horaire qui ne soit pas au courant de ce cas.

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

J'ai d'abord essayé l'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
>>>

Ce n'est pas très surprenant que cela ait échoué, car il s'agit en fait d'une conversion. Remplacer semblait être un meilleur choix (selon Python: Comment obtenir une valeur de datetime.today () qui soit "consciente du fuseau horaire"? ):

>>> 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 constater, remplacer semble définir le paramètre tzinfo, mais ne rend pas l'objet conscient. Je suis prêt à redéfinir la chaîne d'entrée afin de créer un fuseau horaire avant de l'analyser (j'utilise dateutil pour l'analyse, si cela compte), mais cela semble incroyablement simpliste.

De plus, j'ai essayé ceci 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 toujours prendre en charge le format de données hérité. Une conversion unique des données existantes n'est pas une option pour diverses raisons liées aux services commerciaux. 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 certitude raisonnable que toutes les données existantes en question sont en UTC, je suis donc prêt à accepter le risque de ne pas respecter cette valeur dans ce cas.

424
Mark Tozzi

En général, pour créer une date et heure naïve prenant en compte le fuseau horaire, utilisez la méthode localize :

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 car il n’ya pas de calcul de l’heure avancée à traiter:

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

travaux. (.replace renvoie une nouvelle date et heure; elle ne modifie pas unaware.)

501
unutbu

Tous ces exemples utilisent un module externe, mais vous pouvez obtenir le même résultat en utilisant uniquement le module datetime, comme indiqué également dans this SO answer :

from datetime import datetime
from datetime import timezone

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

print(dt.replace(tzinfo=timezone.utc).isoformat())
'2017-01-12T22:11:31+00:00'

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

REMARQUE: Si vous souhaitez utiliser cela avec python3 et python2, vous pouvez également l'utiliser pour l'importation de 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()
113
kang

J'ai utilisé de dt_aware à dt_unaware

dt_unaware = dt_aware.replace(tzinfo=None)

et dt_unware à dt_aware

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

mais répondre avant est aussi une bonne solution.

70
Sérgio

J'utilise cette instruction dans Django pour convertir une heure non consciente en heure consciente:

from Django.utils import timezone

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

Je suis d’accord avec les réponses précédentes, et c’est bien si vous pouvez commencer en UTC. Mais je pense que c’est également un scénario courant pour les personnes de travailler avec une valeur prise en compte tz qui a un date/heure qui a un fuseau horaire local non UTC.

Si vous n'utilisiez que le nom, on pourrait probablement en déduire que replace () sera applicable et produira le bon objet prenant en compte la date et l'heure. Ce n'est pas le cas.

le remplacement (tzinfo = ...) semble être aléatoire dans son comportement. C'est donc inutile. Ne l'utilisez pas!

localiser 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 date/heure sensible à un 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>)
11
paolov

Ceci codifie @ (Sérgio) et @ unutbu réponses . Cela fonctionnera simplement avec un objet pytz.timezone ou une chaîne fuseau horaire IANA .

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 ressemble à ce que devrait faire datetime.localize() (ou .inform() ou .awarify()): accepter les chaînes et les objets de fuseau horaire pour l'argument tz et utiliser la valeur UTC par défaut si aucun fuseau horaire n'est spécifié.

8
hobs

Afin d'ajouter le fuseau horaire local; (en utilisant dateutil)

from dateutil import tz
import datetime

dt_unaware = datetime.datetime(2017, 6, 24, 12, 24, 36)

dt_aware = dt_unaware.replace(tzinfo=tz.tzlocal())
3
Ahmet

deux méthode que je connais purement

from datetime import datetime
import pytz

naive = datetime.now()
aware = naive.replace(tzinfo=pytz.UTC)

ou

aware = pytz.UTC.localize(naive)

bien sûr, vous pouvez utiliser n’importe quel fuseau horaire au lieu de UTC,

2
Doom

Dans le format de la réponse de unutbu; J'ai créé un module utilitaire qui gère de telles choses, avec une syntaxe plus intuitive. Peut être installé avec pip.

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')
0
Turtles Are Cute