Impossibile sottrarre i periodi di dati ingenui e consapevoli dell'offset


305

Ho un timestamptzcampo sensibile al fuso orario in PostgreSQL. Quando estraggo i dati dalla tabella, desidero quindi sottrarre l'ora in questo momento in modo da poter ottenere l'età.

Il problema che sto riscontrando è che entrambi datetime.datetime.now()e datetime.datetime.utcnow()sembrano restituire timestamp inconsapevoli del fuso orario, il che mi fa ottenere questo errore:

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

C'è un modo per evitarlo (preferibilmente senza l'utilizzo di un modulo di terze parti).

EDIT: Grazie per i suggerimenti, tuttavia cercare di regolare il fuso orario sembra darmi errori .. quindi userò timestamp inconsapevoli di fuso orario in PG e inserirò sempre usando:

NOW() AT TIME ZONE 'UTC'

In questo modo tutti i miei timestamp sono UTC per impostazione predefinita (anche se è più fastidioso farlo).

Risposte:


316

hai provato a rimuovere la consapevolezza del fuso orario?

da http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

potrebbe essere necessario aggiungere anche la conversione del fuso orario.

modifica: si prega di essere consapevoli dell'età di questa risposta. Una risposta di Python 3 è di seguito.


32
Questo sembra essere l'unico modo per farlo. Sembra piuttosto zoppo che Python abbia un supporto così scadente per i fusi orari che ha bisogno di un modulo di terze parti per funzionare correttamente con i timestamp ..
Ian,

33
(Solo per la cronaca) In realtà l'aggiunta di informazioni sul fuso orario può essere un'idea migliore: stackoverflow.com/a/4530166/548696
Tadeck

7
oggetti datetime ingenui sono intrinsecamente ambigui e quindi dovrebbero essere evitati. È invece facile aggiungere tzinfo
jfs

1
@Kylotan: UTC è un fuso orario in questo contesto (come rappresentato dalla classe tzinfo). Guarda datetime.timezone.utco pytz.utc. Ad esempio, 1970-01-01 00:00:00è ambiguo e si deve aggiungere un fuso orario per disambiguare: 1970-01-01 00:00:00 UTC. Vedi, devi aggiungere nuove informazioni; il timestamp da solo è ambiguo.
jfs

1
@JFSebastian: il problema è che utcnownon dovrebbe essere restituito un oggetto ingenuo o un timestamp senza fuso orario. Dai documenti, "Un oggetto consapevole viene utilizzato per rappresentare un momento specifico nel tempo che non è aperto all'interpretazione". Ogni momento in UTC soddisfa questo criterio, per definizione.
Kylotan,

214

La soluzione corretta è aggiungere le informazioni sul fuso orario, ad esempio, per ottenere l'ora corrente come oggetto datetime consapevole in Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

Nelle versioni precedenti di Python, potresti definire utctu stesso l'oggetto tzinfo (esempio dai documenti 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()

poi:

now = datetime.now(utc)

10
Meglio che rimuovere la tz poiché la risposta accettata sostiene l'IMHO.
Shautieh,

3
Ecco un elenco dei fusi orari pitone: stackoverflow.com/questions/13866926/...
comfytoday

61

So che alcune persone usano Django specificamente come interfaccia per astrarre questo tipo di interazione con il database. Django fornisce utility che possono essere utilizzate per questo:

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

È necessario configurare un'infrastruttura di impostazioni Django di base, anche se si sta semplicemente utilizzando questo tipo di interfaccia (nelle impostazioni, è necessario includere USE_TZ=Trueper ottenere un datetime consapevole).

Di per sé, questo probabilmente non è abbastanza vicino per motivarti a usare Django come interfaccia, ma ci sono molti altri vantaggi. D'altra parte, se ti sei imbattuto qui perché stavi distruggendo la tua app Django (come ho fatto io), allora forse questo aiuta ...


1
è necessario USE_TZ=True, per ottenere un datetime consapevole qui.
jfs,

2
Sì, ho dimenticato di menzionare che è necessario configurare settings.py come descrive JFSebastian (immagino che si tratti di un'istanza di "imposta e dimentica").
saggio

E questo può anche essere facilmente convertito in altri fusi orari, come + timedelta(hours=5, minutes=30)per IST
ABcDexter

25

Questa è una soluzione molto semplice e chiara
Due righe di codice

# 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

Conclusione: è necessario gestire le variabili datetime con le stesse informazioni sull'ora


Non corretto? Il codice che ho scritto è stato testato e lo sto usando in un progetto Django. È molto più chiaro e semplice
ePi272314,

"errato" si riferisce all'ultima frase nella tua risposta: "... deve aggiungere ... non UTC" - il fuso orario UTC funziona qui e quindi l'istruzione non è corretta.
jfs,

per essere chiari intendevo: diff = datetime.now(timezone.utc) - your_timezone_aware_variablefunziona (e la (a - b)formula sopra è la spiegazione del perché (a - b)può funzionare anche se a.tzinfonon lo è b.tzinfo).
jfs,

6

Il modulo psycopg2 ha le sue definizioni di fuso orario, quindi ho finito per scrivere il mio wrapper su utcnow:

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

e basta usare pg_utcnowogni volta che è necessario il tempo corrente per confrontare con un PostgreSQLtimestamptz


Qualsiasi oggetto tzinfo che restituisce zero Offset UTC farà, per esempio .
jfs,

6

Ho anche affrontato lo stesso problema. Poi ho trovato una soluzione dopo molte ricerche.

Il problema era che quando otteniamo l'oggetto datetime dal modello o dal modulo, viene rilevato l'offset e se otteniamo il tempo dal sistema, esso viene compensato ingenuo .

Quindi quello che ho fatto è stato ottenere l'ora corrente usando timezone.now () e importare il fuso orario da django.utils importare il fuso orario e inserire USE_TZ = True nel file delle impostazioni del progetto.


2

Ho trovato una soluzione semplicissima:

import datetime

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

Funziona con valori datetime sia in base al fuso orario che ingenui. E non sono necessarie ulteriori librerie o soluzioni alternative al database.


1

Ho trovato timezone.make_aware(datetime.datetime.now())utile in Django (sono in 1.9.1). Sfortunatamente non puoi semplicemente rendere un datetimeoggetto sensibile all'offset, quindi timetz(). Devi fare un datetimee fare confronti basati su quello.


1

C'è qualche motivo urgente per cui non puoi gestire il calcolo dell'età in PostgreSQL stesso? Qualcosa di simile a

select *, age(timeStampField) as timeStampAge from myTable

2
Sì, c'è .. ma lo chiedevo principalmente perché voglio evitare di fare tutti i calcoli in postgre.
Ian,

0

So che è vecchio, ma ho pensato di aggiungere la mia soluzione nel caso in cui qualcuno la trovasse utile.

Volevo confrontare il datetime ingenuo locale con un datetime consapevole di un timeserver. Fondamentalmente ho creato un nuovo oggetto datetime ingenuo usando l'oggetto datetime consapevole. È un po 'un trucco e non sembra molto carino ma fa il lavoro.

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

... ecco che arriva il fondente ...

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

Cordiali saluti, utc_to_local()dalla mia risposta restituisce l'ora locale come oggetto datetime consapevole (è il codice Python 3.3+)
jfs

Non è chiaro cosa stia cercando di fare il codice. Potresti sostituirlo con delta = response.tx_time - time.time().
jfs,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.