Comment puis-je obtenir une valeur de datetime.today () en Python qui est «sensible au fuseau horaire»?


310

J'essaie de soustraire une valeur de date de la valeur de datetime.today()pour calculer la date d'il y a quelque chose. Mais il se plaint:

TypeError: can't subtract offset-naive and offset-aware datetimes

La valeur datetime.today()ne semble pas être "sensible au fuseau horaire", tandis que mon autre valeur de date l'est. Comment obtenir une valeur datetime.today()qui soit sensible au fuseau horaire?

En ce moment, cela me donne l'heure locale, qui se trouve être PST, c'est-à-dire UTC - 8 heures. Dans le pire des cas, existe-t-il un moyen de saisir manuellement une valeur de fuseau horaire dans l' datetimeobjet renvoyé par datetime.today()et de la définir sur UTC-8?

Bien sûr, la solution idéale serait qu'il connaisse automatiquement le fuseau horaire.



10
On dirait que nous pouvons utiliser datetime.now().astimezone()depuis Python 3.6
johnchen902

Réponses:


362

Dans la bibliothèque standard, il n'existe aucun moyen multiplateforme de créer des fuseaux horaires conscients sans créer votre propre classe de fuseau horaire.

Sous Windows, il y en a win32timezone.utcnow(), mais cela fait partie de pywin32. Je préférerais plutôt utiliser la bibliothèque pytz , qui a une base de données constamment mise à jour de la plupart des fuseaux horaires.

Travailler avec des fuseaux horaires locaux peut être très délicat (voir les liens "Autres lectures" ci-dessous), vous pouvez donc préférer utiliser UTC tout au long de votre application, en particulier pour les opérations arithmétiques comme le calcul de la différence entre deux points dans le temps.

Vous pouvez obtenir la date / heure actuelle comme suit:

import pytz
from datetime import datetime
datetime.utcnow().replace(tzinfo=pytz.utc)

Gardez cela à l'esprit datetime.today()et datetime.now()renvoyez l' heure locale , pas l'heure UTC, donc leur application .replace(tzinfo=pytz.utc)ne serait pas correcte.

Une autre bonne façon de le faire est:

datetime.now(pytz.utc)

qui est un peu plus court et fait de même.


Pour en savoir plus / regarder pourquoi préférer l'UTC dans de nombreux cas:


75
Et au datetime.now(pytz.utc)lieu de datetime.utcnow().replace(tzinfo = pytz.utc)?
eumiro

5
now(utc)ne revient pas aujourd'hui (à moins qu'il soit minuit en UTC), il renvoie l'heure actuelle en UTC. Vous devez également .replace(hour=0, minute=0, ...)obtenir le début de la journée (comme datetime.today())
jfs

1
Les docs disent que today()renvoie l'heure actuelle, pas minuit. S'il existe un cas d'utilisation où minuit est requis, oui, le remplacement doit être effectué en conséquence. Étant donné que la question initiale portait sur la différence de datetime, je ne pense pas que minuit soit requis.
AndiDog

1
@AndiDog: Mon commentaire implique que je pensais (à tort) que datetime.today()c'est le cas combine(date.today(), time()). datetimea les deux .now()et les .today()méthodes qui (comme vous l'avez correctement souligné) retournent (presque) la même chose. Il n'y a pas de date.now()méthode. dateet les datetimeobjets ne sont pas interchangeables. L'utilisation d'un objet datetime au lieu d'un dateobjet peut produire des bogues subtils; Je ne vois aucune raison datetime.today()d'exister s'il s'agit d'un quasi-doublon de datetime.now().
jfs

6
Ajoutant à cette réponse, si vous utilisez django, utilisez toujours timezone.now()au lieu de datetime.now()car il utilisera automatiquement UTC si USE_TZ = True. timezonese trouve à django.utils.timezone, documentation: docs.djangoproject.com/en/1.11/topics/i18n/timezones
Ryan

107

Obtenez l'heure actuelle, dans un fuseau horaire spécifique:

import datetime
import pytz
my_date = datetime.datetime.now(pytz.timezone('US/Pacific'))

2
Regardez ça .
wim

1
vous ne devez PAS utiliser l'heure localisée, sauf pour la sortie. Beaucoup de choses tournent mal lors de l'utilisation du datetime basé sur le fuseau horaire: un simple timedelta ne prend pas en compte l'heure d'été sauf si vous êtes en heure UTC pour commencer. Utilisez toujours un fuseau horaire compatible avec UTC. convertir en fuseau horaire local sur la sortie si nécessaire.
MrE

4
Pour réitérer un désaccord avec @MrE que j'ai déjà exprimé dans les commentaires sur la réponse acceptée: il y a des raisons parfaitement valables de travailler avec des heures de données localisées et "vous ne devriez PAS utiliser l'heure localisée sauf pour la sortie" est un conseil trop large. Supposons que vous ajoutiez 1 jour à une date / heure quelques heures avant une limite d'heure d'été à laquelle les horloges remontent d'une heure. Quel résultat voulez-vous? Si vous pensez que l'heure doit être la même, utilisez des heures de données localisées. Si vous pensez que cela devrait être une heure plus tôt, utilisez des heures UTC ou des fuseaux horaires naïfs. Ce qui est logique dépend du domaine.
Mark Amery

@MarkAmery autant que je peux convenir que vous souhaitiez peut-être AJOUTER ou soustraire un certain nombre de jours ou d'heures et ne pas vous soucier des problèmes de fuseau horaire (comme votre exemple), ce commentaire concerne le passage du temps corrigé du fuseau horaire à un client. Étant donné que Python est principalement utilisé pour les processus back-end, il passe du temps à un client. Le serveur doit toujours transmettre la date / heure en UTC et le client doit le convertir en sa propre date / heure / fuseau horaire local, sinon de mauvaises choses se produisent: vérifiez simplement la sortie de datetime.datetime(2016, 11, 5, 9, 43, 45, tzinfo=pytz.timezone('US/Pacific'))et voyez si c'est ce que vous
attendiez

"Le serveur doit toujours transmettre la date / heure en UTC et le client doit le convertir en sa propre date / heure / fuseau horaire local" - non, ce n'est pas universellement vrai. Parfois, l'utilisation du fuseau horaire du client est inappropriée et le fuseau horaire approprié doit être transmis dans le cadre des données. Si, en tant que Londonien, je vois les heures de réunion d'un club d'échecs de San Francisco sur leur site Web, je devrais les voir à l'heure de San Francisco, pas à l'heure de Londres.
Mark Amery

69

Dans Python 3, la bibliothèque standard facilite beaucoup plus la spécification de l'UTC comme fuseau horaire:

>>> import datetime
>>> datetime.datetime.now(datetime.timezone.utc)
datetime.datetime(2016, 8, 26, 14, 34, 34, 74823, tzinfo=datetime.timezone.utc)

Si vous voulez une solution qui utilise uniquement la bibliothèque standard et qui fonctionne à la fois en Python 2 et Python 3, voir la réponse de jfs .

Si vous avez besoin du fuseau horaire local, pas de l'UTC, voir la réponse de Mihai Capotă


19

Voici une solution stdlib qui fonctionne à la fois sur Python 2 et 3:

from datetime import datetime

now = datetime.now(utc) # Timezone-aware datetime.utcnow()
today = datetime(now.year, now.month, now.day, tzinfo=utc) # Midnight

todayest une instance datetime consciente représentant le début de la journée (minuit) en UTC et utcest un objet tzinfo ( exemple de la documentation ):

from datetime import tzinfo, timedelta

ZERO = timedelta(0)

class UTC(tzinfo):
    def utcoffset(self, dt):
        return ZERO

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return ZERO

utc = UTC()

Connexes: comparaison des performances de plusieurs façons d'obtenir minuit (début d'une journée) pour une heure UTC donnée . Remarque: il est plus complexe d' obtenir minuit pour un fuseau horaire avec un décalage UTC non fixe .


16

Une autre méthode pour construire un objet datetime sensible au fuseau horaire représentant l'heure actuelle:

import datetime
import pytz

pytz.utc.localize( datetime.datetime.utcnow() )  

notez que pytz.utcet pytz.UTCsont tous deux définis (et sont les mêmes)
drevicko

2
Cette réponse est meilleure que celle acceptée car elle est plus universelle: replace()le fuseau horaire ing est généralement sujet aux erreurs dans la plupart des autres utilisations, tandis que l' localize()ing est le moyen préféré d'attribuer le fuseau horaire à des horodatages naïfs.
Antony Hatchkins

@AntonyHatchkins: .localize()méthode échoue pour ambiguë heure locale (entrée non-UTC). La réponse de @ philfreo qui utilise.now(pytz_timezone) continue de fonctionner dans de tels cas.
jfs

Comme spécifié dans les documents python, .now(pytz_timezone)fait exactement la même chose que localize(utcnow)- d'abord il génère l'heure actuelle en UTC, puis il lui assigne un fuseau horaire: "<...> Dans ce cas, le résultat est équivalent à tz.fromutc(datetime.utcnow().replace(tzinfo=tz))". Les deux réponses sont correctes et fonctionnent toujours.
Antony Hatchkins

1
Le seul temps naïf (non-utc) qui peut être rendu en toute sécurité au fuseau horaire est maintenant : le système sous-jacent est censé connaître la valeur UTC et pytzvia OLSON db est censé savoir comment le convertir en n'importe quel fuseau horaire dans le monde. Il est difficile de prendre en compte tout autre fuseau horaire naïf (non utc) en raison de l'ambiguïté pendant les changements d'heure d'été. Ce n'est pas un problème de .localize(lui donner une is_dstvaleur le fait fonctionner pour n'importe quelle date). C'est un problème inhérent à la pratique de l'heure d'été.
Antony Hatchkins

16

Un one-liner utilisant uniquement la bibliothèque standard fonctionne à partir de Python 3.3. Vous pouvez obtenir un datetimeobjet conscient du fuseau horaire local en utilisant astimezone(comme suggéré par johnchen902 ):

from datetime import datetime, timezone

aware_local_now = datetime.now(timezone.utc).astimezone()

print(aware_local_now)
# 2020-03-03 09:51:38.570162+01:00

print(repr(aware_local_now))
# datetime.datetime(2020, 3, 3, 9, 51, 38, 570162, tzinfo=datetime.timezone(datetime.timedelta(0, 3600), 'CET'))

2
La documentation est d'une grande aide, trouvée ici: docs.python.org/3.8/library/… . Cette fonctionnalité incroyablement basique est enfouie profondément dans un paragraphe obscur des documents, de sorte que cette réponse de stackoverflow est effectivement le seul endroit sur Internet entier avec ces informations. Dans la documentation, on peut également voir que, depuis Python 3.6, datetime.now()peut être appelé sans aucun argument et retourner le résultat local correct (les naïfs datetimesont présumés être dans le fuseau horaire local).
atimholt

8

Si vous utilisez Django , vous pouvez définir des dates non compatibles avec tz (uniquement UTC ).

Commentez la ligne suivante dans settings.py:

USE_TZ = True

8
où avez-vous vu Django mentionné dans cette question?
vonPetrushev

1
Une âme gentille a supprimé mes excuses précédentes, donc encore une fois: honte à moi, mauvaise réponse car la question n'est pas spécifique à Django. Je l'ai laissé car cela pourrait aider certains utilisateurs de toute façon, mais je le supprimerai lorsque le score approche 0. Si cette réponse est inappropriée, n'hésitez pas à voter.
laffuste

6

pytz est une bibliothèque Python qui permet des calculs de fuseau horaire précis et multiplateforme à l'aide de Python 2.3 ou supérieur.

Avec le stdlib, ce n'est pas possible.

Voir une question similaire sur SO .


6

Voici une façon de le générer avec stdlib:

import time
from datetime import datetime

FORMAT='%Y-%m-%dT%H:%M:%S%z'
date=datetime.strptime(time.strftime(FORMAT, time.localtime()),FORMAT)

date stockera la date locale et le décalage par rapport à UTC , pas la date au fuseau horaire UTC, vous pouvez donc utiliser cette solution si vous avez besoin d'identifier à quel fuseau horaire la date est générée . Dans cet exemple et dans mon fuseau horaire local:

date
datetime.datetime(2017, 8, 1, 12, 15, 44, tzinfo=datetime.timezone(datetime.timedelta(0, 7200)))

date.tzname()
'UTC+02:00'

La clé ajoute la %zdirective à la représentation FORMAT, pour indiquer le décalage UTC de la structure temporelle générée. D'autres formats de représentation peuvent être consultés dans la documentation du module datetime

Si vous avez besoin de la date au fuseau horaire UTC, vous pouvez remplacer time.localtime () par time.gmtime ()

date=datetime.strptime(time.strftime(FORMAT, time.gmtime()),FORMAT)

date    
datetime.datetime(2017, 8, 1, 10, 23, 51, tzinfo=datetime.timezone.utc)

date.tzname()
'UTC'

Éditer

Cela ne fonctionne que sur python3 . La directive z n'est pas disponible sur le code python 2 _strptime.py


ValueError: 'z' est une mauvaise directive au format '% Y-% m-% dT% H:% M:% S% z'
jno

Vous êtes sur python 2, non? Malheureusement, il semble que la directive z ne soit pas disponible sur python 2. Code
_strptime.py

6

Utilisez dateutil comme décrit dans Python datetime.datetime.now () qui est sensible au fuseau horaire :

from dateutil.tz import tzlocal
# Get the current date/time with the timezone.
now = datetime.datetime.now(tzlocal())

1
Voir cette réponse de JF Sebastian pour une situation où cela donne un résultat incorrect.
Antony Hatchkins

2
Je pense que l'erreur dans l'autre post n'est pertinente que dans des cas d'utilisation spécifiques. La tzlocal()fonction est toujours l'une des solutions les plus simples et doit certainement être mentionnée ici.
user8162

2

Obtenir une date sensible au utcfuseau horaire dans le fuseau horaire suffit pour que la soustraction de date fonctionne.

Mais si vous voulez une date prenant en compte le fuseau horaire dans votre fuseau horaire actuel, tzlocalc'est la voie à suivre:

from tzlocal import get_localzone  # pip install tzlocal
from datetime import datetime
datetime.now(get_localzone())

PS dateutila une fonction similaire ( dateutil.tz.tzlocal). Mais malgré le partage du nom, il a une base de code complètement différente, qui, comme l'a noté JF Sebastian, peut donner de mauvais résultats.


python est généralement utilisé sur le serveur. Le fuseau horaire local sur un serveur est généralement inutile et doit toujours être défini sur UTC. La configuration de datetime tzinfo de cette manière échoue dans certains cas. mieux utiliser UTC, puis localiser le fuseau horaire souhaité uniquement en sortie. par exemple, tout calcul de timedelta ne prend pas en compte l'heure d'été, donc cela devrait être fait en UTC, puis localisé.
MrE

@MrE Mauvais, hors sujet, exemples?
Antony Hatchkins

essayez d'utiliser un objet datetime localisé dans un fuseau horaire qui observe l'heure d'été, ajoutez un certain nombre de jours pour changer l'état de l'heure d'été, et vous verrez que le fonctionnement sur des objets datetime dans le fuseau horaire localisé échoue et ne respectera pas l'heure d'été. D'où mon commentaire que vous devriez TOUJOURS faire n'importe quelle opération datetime en temps UTC.
MrE

le point étant: ne faites pas cela, effectuez vos opérations en UTC, puis utilisez datetime.astimezone (fuseau horaire) pour convertir le fuseau horaire local en sortie.
MrE

2

Voici une solution utilisant un fuseau horaire lisible et qui fonctionne avec today ():

from pytz import timezone

datetime.now(timezone('Europe/Berlin'))
datetime.now(timezone('Europe/Berlin')).today()

Vous pouvez répertorier tous les fuseaux horaires comme suit:

import pytz

pytz.all_timezones
pytz.common_timezones # or

1

Surtout pour les fuseaux horaires non UTC:

Le seul fuseau horaire qui a sa propre méthode est timezone.utc, mais vous pouvez truquer un fuseau horaire avec n'importe quel décalage UTC si vous en avez besoin en utilisant timedelta& timezone, et en le forçant à l'aide .replace.

In [1]: from datetime import datetime, timezone, timedelta

In [2]: def force_timezone(dt, utc_offset=0):
   ...:     return dt.replace(tzinfo=timezone(timedelta(hours=utc_offset)))
   ...:

In [3]: dt = datetime(2011,8,15,8,15,12,0)

In [4]: str(dt)
Out[4]: '2011-08-15 08:15:12'

In [5]: str(force_timezone(dt, -8))
Out[5]: '2011-08-15 08:15:12-08:00'

L'utilisation timezone(timedelta(hours=n))comme fuseau horaire est la vraie solution miracle ici, et elle a beaucoup d'autres applications utiles.


0

Si vous obtenez l'heure et la date actuelles en python, puis importez la date et l'heure, le package pytz en python après vous obtiendrez la date et l'heure actuelles comme ..

from datetime import datetime
import pytz
import time
str(datetime.strftime(datetime.now(pytz.utc),"%Y-%m-%d %H:%M:%S%t"))

0

Une autre alternative, dans mon esprit une meilleure, utilise à la Pendulumplace de pytz. Considérez le code simple suivant:

>>> import pendulum

>>> dt = pendulum.now().to_iso8601_string()
>>> print (dt)
2018-03-27T13:59:49+03:00
>>>

Pour installer Pendulum et voir leur documentation, rendez-vous ici . Il a des tonnes d'options (comme la simple prise en charge des formats ISO8601, RFC3339 et bien d'autres), de meilleures performances et a tendance à produire un code plus simple.


Je ne sais pas pourquoi le vote ici, ce code fonctionne dans plusieurs programmes qui fonctionnent 7/24 pour moi :). pas que ça me dérange un autre avis, mais s'il vous plaît dites pourquoi cela ne fonctionne pas pour vous, pour me permettre de le vérifier. Merci d'avance
ng10

0

Utilisez le fuseau horaire comme indiqué ci-dessous pour une date-heure compatible avec le fuseau horaire. La valeur par défaut est UTC:

from django.utils import timezone
today = timezone.now()

0

Tyler de 'howchoo' a fait un très bon article qui m'a aidé à avoir une meilleure idée des objets Datetime, lien ci-dessous

Travailler avec Datetime

essentiellement, je viens d'ajouter ce qui suit à la fin de mes deux objets datetime

.replace(tzinfo=pytz.utc)

Exemple:

import pytz
import datetime from datetime

date = datetime.now().replace(tzinfo=pytz.utc)

0

essayez pnp_datetime , tout le temps utilisé et renvoyé est avec le fuseau horaire, et ne causera aucun problème de naïf de décalage et de décalage.

>>> from pnp_datetime.pnp_datetime import Pnp_Datetime
>>>
>>> Pnp_Datetime.utcnow()
datetime.datetime(2020, 6, 5, 12, 26, 18, 958779, tzinfo=<UTC>)

0

Il convient de souligner que depuis Python 3.6, vous n'avez besoin que de la bibliothèque standard pour obtenir un objet datetime sensible au fuseau horaire qui représente l'heure locale (le réglage de votre système d'exploitation). Utilisation de l' astimezone ()

import datetime

datetime.datetime(2010, 12, 25, 10, 59).astimezone()
# e.g.
# datetime.datetime(2010, 12, 25, 10, 59, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600), 'Mitteleuropäische Zeit'))

datetime.datetime(2010, 12, 25, 12, 59).astimezone().isoformat()
# e.g.
# '2010-12-25T12:59:00+01:00'

# I'm on CET/CEST

(voir le commentaire de @ johnchen902).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.