Ne peut pas soustraire des heures de temps naïves et sensibles à l'offset

305

J'ai un timestamptzchamp sensible au fuseau horaire dans PostgreSQL. Lorsque je tire des données du tableau, je veux ensuite soustraire le temps maintenant afin que je puisse obtenir son âge.

Le problème que je vais avoir est que les deux datetime.datetime.now()et datetime.datetime.utcnow()semblent revenir fuseau horaire horodatages pas au courant, ce qui conduit à me faire cette erreur:

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

Existe-t-il un moyen d'éviter cela (de préférence sans qu'un module tiers soit utilisé).

EDIT: Merci pour les suggestions, cependant essayer d'ajuster le fuseau horaire semble me donner des erreurs .. donc je vais juste utiliser des horodatages ignorés du fuseau horaire dans PG et toujours insérer en utilisant:

NOW() AT TIME ZONE 'UTC'

De cette façon, tous mes horodatages sont UTC par défaut (même si c'est plus ennuyeux de le faire).

Ian
la source

Réponses:

316

avez-vous essayé de supprimer la prise de conscience du fuseau horaire?

depuis http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

Il se peut que vous deviez également ajouter une conversion de fuseau horaire.

modifier: Veuillez connaître l'âge de cette réponse. Une réponse Python 3 est ci-dessous.

phillc
la source
32
Cela semble être la seule façon de procéder. Semble assez boiteux que python ait un support tellement merdique pour les fuseaux horaires qu'il a besoin d'un module tiers pour fonctionner correctement avec les horodatages ..
Ian
33
(Juste pour mémoire) L'ajout d'informations sur le fuseau horaire peut être une meilleure idée: stackoverflow.com/a/4530166/548696
Tadeck
7
Les objets datetime naïfs sont intrinsèquement ambigus et doivent donc être évités. Il est facile d'ajouter à la place
tzinfo
1
@Kylotan: UTC est un fuseau horaire dans ce contexte (représenté par la classe tzinfo). Regardez datetime.timezone.utcou pytz.utc. Par exemple, 1970-01-01 00:00:00est ambigu et vous devez ajouter un fuseau horaire à désambiguïser: 1970-01-01 00:00:00 UTC. Vous voyez, vous devez ajouter de nouvelles informations; l'horodatage en lui-même est ambigu.
jfs
1
@JFSebastian: Le problème est que cela utcnowne devrait pas renvoyer un objet naïf ou un horodatage sans fuseau horaire. D'après les documents, "Un objet conscient est utilisé pour représenter un moment précis qui n'est pas ouvert à l'interprétation". Toute heure en UTC remplit ce critère, par définition.
Kylotan
214

La bonne solution consiste à ajouter les informations de fuseau horaire, par exemple, pour obtenir l'heure actuelle en tant qu'objet datetime conscient dans Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

Sur les anciennes versions de Python, vous pouviez définir utcvous-même l'objet tzinfo (exemple de la documentation datetime):

from datetime import tzinfo, timedelta, datetime

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()

puis:

now = datetime.now(utc)
jfs
la source
10
Mieux que de supprimer le tz comme la réponse acceptée préconise à mon humble avis.
Shautieh
3
Voici une liste des fuseaux horaires python: stackoverflow.com/questions/13866926/…
comfytoday
61

Je sais que certaines personnes utilisent Django spécifiquement comme interface pour résumer ce type d'interaction avec la base de données. Django fournit des utilitaires qui peuvent être utilisés pour cela:

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

Vous devez configurer une infrastructure de paramètres Django de base, même si vous utilisez simplement ce type d'interface (dans les paramètres, vous devez inclure USE_TZ=Truepour obtenir une date / heure consciente).

En soi, cela est probablement loin d'être suffisant pour vous motiver à utiliser Django comme interface, mais il existe de nombreux autres avantages. D'un autre côté, si vous êtes tombé ici parce que vous manipuliez votre application Django (comme je l'ai fait), alors peut-être que cela aide ...

sauge
la source
1
vous avez besoin USE_TZ=True, pour obtenir un datetime conscient ici.
jfs
2
Oui - j'ai oublié de mentionner que vous devez configurer votre settings.py comme JFSebastian le décrit (je suppose que c'était une instance de «définir et oublier»).
sage
Et cela peut également être facilement converti en d'autres fuseaux horaires, comme + timedelta(hours=5, minutes=30)pour IST
ABcDexter
25

Ceci est une solution très simple et claire
Deux lignes de code

# First we obtain de timezone info o some datatime variable    

tz_info = your_timezone_aware_variable.tzinfo

# Now we can subtract two variables using the same time zone info
# For instance
# Lets obtain the Now() datetime but for the tz_info we got before

diff = datetime.datetime.now(tz_info)-your_timezone_aware_variable

Conclusion: vous devez gérer vos variables datetime avec les mêmes informations de temps

ePi272314
la source
Incorrect? Le code que j'ai écrit a été testé et je l'utilise dans un projet django. C'est beaucoup plus clair et simple
ePi272314
"incorrect" fait référence à la dernière phrase de votre réponse: "... doit ajouter ... pas UTC" - le fuseau horaire UTC fonctionne ici et par conséquent, la déclaration est incorrecte.
jfs
pour être clair, je voulais dire: diff = datetime.now(timezone.utc) - your_timezone_aware_variablefonctionne (et la (a - b)formule ci-dessus est l'explication pourquoi (a - b)peut fonctionner même si ce a.tzinfon'est pas le cas b.tzinfo).
jfs
6

Le module psycopg2 a ses propres définitions de fuseau horaire, j'ai donc fini par écrire mon propre wrapper autour d'utcnow:

def pg_utcnow():
    import psycopg2
    return datetime.utcnow().replace(
        tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=0, name=None))

et utilisez simplement pg_utcnowchaque fois que vous avez besoin de l'heure actuelle pour comparer avec un PostgreSQLtimestamptz

erjiang
la source
Tout objet tzinfo qui renvoie zéro décalage utc fera l'affaire, par exemple .
jfs
6

J'ai également rencontré le même problème. Ensuite, j'ai trouvé une solution après de nombreuses recherches.

Le problème était que lorsque nous obtenons l'objet datetime à partir d'un modèle ou d'une forme, il est conscient du décalage et si nous obtenons l'heure par le système, il est naïf .

Donc, ce que j'ai fait, c'est que j'ai obtenu l'heure actuelle en utilisant timezone.now () et en important le fuseau horaire à partir de django.utils import timezone et en mettant USE_TZ = True dans votre fichier de paramètres de projet.

Ashok Joshi
la source
2

J'ai trouvé une solution ultra simple:

import datetime

def calcEpochSec(dt):
    epochZero = datetime.datetime(1970,1,1,tzinfo = dt.tzinfo)
    return (dt - epochZero).total_seconds()

Il fonctionne avec des valeurs datetime sensibles au fuseau horaire et naïves au fuseau horaire. Et aucune bibliothèque ou solution de contournement de base de données supplémentaire n'est requise.

John L. Stanley
la source
1

Je l'ai trouvé timezone.make_aware(datetime.datetime.now())utile dans Django (je suis sur 1.9.1). Malheureusement, vous ne pouvez pas simplement rendre un datetimeobjet sensible à l'offset, alors timetz(). Vous devez faire un datetimeet faire des comparaisons sur la base de cela.

Joseph Coco
la source
1

Y a-t-il une raison urgente pour laquelle vous ne pouvez pas gérer le calcul de l'âge dans PostgreSQL lui-même? Quelque chose comme

select *, age(timeStampField) as timeStampAge from myTable
Nic Gibson
la source
2
Oui il y en a .. mais je demandais surtout parce que je veux éviter de faire tous les calculs dans postgre.
Ian
0

Je sais que c'est vieux, mais je pensais simplement ajouter ma solution au cas où quelqu'un la trouverait utile.

Je voulais comparer le datetime naïf local avec un datetime conscient d'un serveur de temps. J'ai essentiellement créé un nouvel objet datetime naïf en utilisant l'objet datetime conscient. C'est un peu un hack et n'a pas l'air très joli mais fait le travail.

import ntplib
import datetime
from datetime import timezone

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)    

try:
    ntpt = ntplib.NTPClient()
    response = ntpt.request('pool.ntp.org')
    date = utc_to_local(datetime.datetime.utcfromtimestamp(response.tx_time))
    sysdate = datetime.datetime.now()

... voici le fudge ...

    temp_date = datetime.datetime(int(str(date)[:4]),int(str(date)[5:7]),int(str(date)[8:10]),int(str(date)[11:13]),int(str(date)[14:16]),int(str(date)[17:19]))
    dt_delta = temp_date-sysdate
except Exception:
    print('Something went wrong :-(')
I_do_python
la source
Pour info, utc_to_local()ma réponse renvoie l'heure locale comme un objet datetime conscient (c'est du code Python 3.3+)
jfs
Ce que votre code essaie de faire n'est pas clair. Vous pouvez le remplacer par delta = response.tx_time - time.time().
jfs