2011-09-19 16 views
9

Sto analizzando il feed di avvisi del Servizio meteo nazionale in un'applicazione Web. Vorrei eliminare gli avvisi quando hanno raggiunto la loro scadenza. Mi piacerebbe anche visualizzare il tempo di scadenza nel formato orario locale per l'area geografica a cui appartengono.Conversione della stringa di data sensibile al fuso orario in UTC e ritorno in Python

Gli avvisi coprono l'intero USA, quindi penso che l'approccio migliore sia archiviare e confrontare le ore in timestamp UTC. Il tempo di scadenza arriva nel feed come una stringa come questa: 2011-09-09T22:12:00-04:00.

sto usando le Labix dateutils pacchetto per analizzare la stringa in modo fuso orario-aware:

>>> from dateutil.parser import parse 
>>> d = parse("2011-09-18T15:52:00-04:00") 
>>> d 
datetime.datetime(2011, 9, 18, 15, 52, tzinfo=tzoffset(None, -14400)) 

Sono anche in grado di catturare l'offset in ore UTC:

>>> offset_hours = (d.utcoffset().days * 86400 + d.utcoffset().seconds)/3600 
>>> offset_hours 
-4 

Utilizzando le datetime.utctimetuple() e time.mktime() metodi, sono in grado di convertire la data analizzato per un timestamp UTC:

>>> import time 
>>> expiration_utc_ts = time.mktime(d.utctimetuple()) 
>>> expiration_utc_ts 
1316393520.0 

A questo punto, mi sento abbastanza bene che sono in grado di convertire le stringhe non elaborate in un timestamp che rappresenta il tempo di scadenza in UTC. Sono in grado di confrontare l'ora corrente come un timestamp UTC della scadenza e determinare se ha bisogno di essere eliminati:

>>> now_utc_ts = time.mktime(time.gmtime()) 
>>> now_utc_ts 
1316398744.0 
>>> now_utc_ts >= expiration_tc_ts 
True 

La difficoltà che sto avendo sta cercando di convertire il mio timestamp UTC memorizzati nuovo all'originale formato localizzato. Ho le ore di offset memorizzati dalla conversione originale e una stringa ho analizzati per memorizzare l'etichetta fuso orario:

>>> print offset_hours 
-4 
>>> print timezone 
EDT 

mi piacerebbe convertire il timestamp UTC di nuovo ad un tempo formattato a livello locale, ma la conversione di nuovo a un datetime non sembra funzionare:

>>> import datetime 
>>> datetime.datetime.fromtimestamp(expiration_utc_ts) + datetime.timedelta(hours=offset_hours) 
datetime.datetime(2011, 9, 18, 16, 52) # The hour is 16 but it should be 15 

sembra come se fosse fuori di un'ora. Non sono sicuro di dove è stato introdotto l'errore? Ho messo insieme un altro test e ho ottenuto risultati simili:

>>> # Running this at 21:29pm EDT 
>>> utc_now = datetime.datetime.utcnow() 
>>> utc_now_ts = time.mktime(right_now.utctimetuple()) 
>>> datetime.datetime.fromtimestamp(utc_now_ts) 
datetime.datetime(2011, 9, 18, 22, 29, 47) # Off by 1 hour 

Qualcuno può aiutarmi a trovare il mio errore? Non sono sicuro che si tratti di un problema di risparmio giornaliero? Mi sono imbattuto in alcune cose che mi portano a credere che potrebbe essere il tentativo di localizzare le mie date e orari, ma a questo punto sono abbastanza perplesso. Speravo di fare tutti questi calcoli/confronti in un modo agnostico dal fuso orario.

+0

possibile duplicato di [Problema con Python/pytz Conversione da fuso orario locale UTC poi di nuovo] (http://stackoverflow.com/questions/7465181/issue-with-python-pytz-converting- from-local-timezone-to-utc-then-back) – agf

+0

Avrei pensato di poter fare tutto questo con oggetti datetime e non convertirli in tuple o timestamp. Se si crea un oggetto "ora" datetime time-aware, basta "se x

+0

Speravo anch'io, ma PostgreSQL continuava a voler localizzare il valore datetime. (Sono sicuro che c'è un modo per aggirare questo, ma ho pensato che sarebbe stato più semplice adesso memorizzare solo i timestamp UTC) – Scott

risposta

3

Il problema è che l'ora legale viene applicata due volte.

Un esempio banale:

>>> time_tuple = datetime(2011,3,13,2,1,1).utctimetuple() 
time.struct_time(tm_year=2011, tm_mon=3, tm_mday=13, tm_hour=2, tm_min=1, tm_sec=1, tm_wday=6, tm_yday=72, tm_isdst=0) 
>>> datetime.fromtimestamp(time.mktime(time_tuple)) 
datetime.datetime(2011, 3, 13, 3, 1, 1) 

Sono abbastanza certo che la colpa è all'interno time.mktime(). Come dice nel suo documentation:

Questa è la funzione inversa di localtime(). L'argomento è struct_time o full 9-tuple (poiché il flag dst è necessario; usa -1 come flag dst se è sconosciuto) che esprime l'ora in ora locale, non in UTC.Restituisce un numero in virgola mobile, per la compatibilità con con time(). Se il valore di input non può essere rappresentato come un tempo valido, verrà sollevato OverflowError o ValueError (il quale dipende dal fatto che il valore non valido venga catturato da Python o dalle librerie C sottostanti di ). La prima data per la quale è possibile generare un orario dipende dalla piattaforma.

Quando si passa una tupla tempo per time.mktime(), si aspetta una bandiera sul fatto che il tempo è in ora legale o meno. Come potete vedere sopra, utctimetuple() restituisce una tupla con quella bandiera segnato 0, come si dice di fare nella sua documentation:

Se datetime esempio d è ingenuo, questo è lo stesso di d.timetuple() tranne che tm_isdst è forzato a 0 indipendentemente da ciò che d.dst() restituisce . L'ora legale non è mai attiva per l'ora UTC.

Se d è consapevole, d è normalizzata tempo UTC, sottraendo d.utcoffset(), e un time.struct_time per il tempo normalizzato è restituito. tm_isdst è forzato a 0. Si noti che il membro tm_year del risultato può essere MINYEAR-1 o MAXYEAR + 1, se d.year era MINYEAR o MAXYEAR e spargimenti di regolazione UTC su un limite di un anno.

Dal momento che avete detto time.mktime() che il tempo non è l'ora legale, e il suo compito è quello di convertire tutti i tempi in ora locale, ed è attualmente l'ora legale nella vostra zona, si aggiunge un'ora per renderlo ora legale. Da qui il risultato.


Mentre io non ho il posto a portata di mano, mi sono imbattuto in un metodo un paio di giorni fa per convertire datetimes timezone-aware in quelli ingenui nel vostro tempo locale. Questo potrebbe funzionare molto meglio per la vostra applicazione di quello che si sta facendo (utilizza il modulo di eccellente pytz):

import pytz 
def convert_to_local_time(dt_aware): 
    tz = pytz.timezone('America/Los_Angeles') # Replace this with your time zone string 
    dt_my_tz = dt_aware.astimezone(tz) 
    dt_naive = dt_my_tz.replace(tzinfo=None) 
    return dt_naive 

replace 'America/LosAngeles' con il proprio stringa di fuso orario, che potete trovare da qualche parte in pytz.all_timezones.

+0

Non è corretto. 'mktime()' si aspetta * ora locale * mentre OP supera l'ora UTC. Fallisce se il fuso orario locale non è UTC. Non usare 'naive_datetime_object.utctimetuple()' - non lo converte in UTC (OP usa 'aware_datetime.object.utctimetuple()' funziona come previsto). Potresti aver bisogno della chiamata 'tz.normalize()' dopo '.astimezone()' se 'aware_dt' non è in UTC. – jfs

0

datetime.fromtimestamp() è il metodo corretto per ottenere l'ora locale dal timestamp POSIX. Il problema nella tua domanda è che tu converti un oggetto datetime consapevole in timestamp POSIX usando time.mktime() che non è corretto. Ecco one of correct ways to do it:

expiration_utc_ts = (d - datetime(1970, 1, 1, tzinfo=utc)).total_seconds() 
local_dt = datetime.fromtimestamp(expiration_utc_ts) 
Problemi correlati