2011-09-19 8 views
9

Estoy analizando las alertas del Servicio Meteorológico Nacional en una aplicación web. Me gustaría depurar las alertas cuando lleguen a su hora de vencimiento. También me gustaría mostrar la hora de caducidad en el formato de hora local para el área geográfica a la que pertenecen.Convirtiendo la cadena de fecha de reconocimiento de zona horaria en UTC y de vuelta en Python

Las alertas cubren todo EE. UU., Por lo que creo que el mejor enfoque es almacenar y comparar las horas en las marcas de tiempo UTC. El tiempo de caducidad llega al feed como una cadena como esta: 2011-09-09T22:12:00-04:00.

estoy usando los dateutils Labix paquete para analizar la cadena de zona horaria de una manera consciente:

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

también soy capaz de capturar el desplazamiento en hora UTC:

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

Utilizando los métodos datetime.utctimetuple() y time.mktime(), soy capaz de convertir la fecha analizada a una marca de hora UTC:

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

En este punto, me siento bastante bien de poder convertir las cadenas sin formato en una marca de tiempo que representa el tiempo de caducidad en UTC. Soy capaz de comparar el tiempo actual como una marca de hora UTC del vencimiento y determinar si necesita ser purgado:

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

La dificultad que estoy teniendo está tratando de convertir mi marca de hora UTC almacenada nuevo a la original formato localizado. Tengo las horas de compensación almacenados de la conversión original y una cadena que analizan para almacenar la etiqueta zona horaria:

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

me gustaría convertir la marca de hora UTC de nuevo a la vez formateado localmente, pero convertir de nuevo a un datetime no parece estar funcionando:

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

parece que está fuera por una hora. No estoy seguro de dónde se introdujo el error? Reuní otra prueba y obtuve resultados similares:

>>> # 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 

¿Alguien me puede ayudar a encontrar mi error? No estoy seguro si es un problema de ahorro de luz diurna? Me encontré con algunas cosas que me llevan a creer que podría estar tratando de localizar mis fechas y horas, pero en este punto estoy bastante perplejo. Tenía la esperanza de hacer todos estos cálculos/comparaciones en una forma de tiempo-agnóstica.

+0

posible duplicado de [Problema con Python/pytz La conversión de zona horaria local para luego volver UTC] (http://stackoverflow.com/questions/7465181/issue-with-python-pytz-converting- from-local-timezone-to-utc-then-back) – agf

+0

Hubiera pensado que podría hacer todo esto con objetos datetime, y no convertir a tuplas o marcas de tiempo. Si crea un objeto datetime "now" compatible con la zona horaria, simplemente haga "if x

+0

Yo también lo esperaba, pero PostgreSQL no quiso localizar el valor de fecha y hora. (Estoy seguro de que hay una forma de evitarlo, pero pensé que sería más fácil ahora solo almacenar las marcas de tiempo UTC) – Scott

Respuesta

3

El problema es que el horario de verano se está aplicando dos veces.

Un ejemplo trivial:

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

estoy bastante seguro de que el fallo se encuentra dentro de time.mktime(). Como dice en su documentation:

Esta es la función inversa de localtime(). Su argumento es struct_time o 9-tuple completo (ya que el indicador dst es necesario, use -1 como indicador dst si se desconoce) que expresa el tiempo en hora local, no UTC.Devuelve un número de punto flotante, para compatibilidad con con time(). Si el valor de entrada no puede representarse como un tiempo válido, se generará OverflowError o ValueError (que depende de si Python o las bibliotecas C subyacentes de capturan el valor no válido). La fecha más temprana para la que puede generar un tiempo depende de la plataforma.

Cuando se pasa una tupla tiempo para time.mktime(), se espera un indicador de si el tiempo es en el horario de verano o no. Como se puede ver arriba, utctimetuple() devuelve una tupla con esa bandera marcada 0, ya que dice que hará en su documentation:

Si la fecha y hora instancia d es ingenuo, esto es lo mismo que d.timetuple() excepto que tm_isdst se fuerza a 0 independientemente de lo que devuelva d.dst() . DST nunca se aplica durante un tiempo UTC.

Si d es consciente, d se normaliza a la hora UTC, restando d.utcoffset(), y un time.struct_time para el tiempo normalizado es devueltos. tm_isdst está forzado a 0. Tenga en cuenta que el miembro tt_year del resultado puede ser MINYEAR-1 o MAXYEAR + 1, si d.year fue MINYEAR o MAXYEAR y el ajuste UTC se derrama en un límite de un año.

Puesto que usted ha dicho time.mktime() que su tiempo no es el horario de verano, y su trabajo consiste en convertir todos los tiempos en la hora local, y es actualmente el horario de verano en su área, que añade una hora para que sea el horario de verano. De ahí el resultado.


Si bien no tengo la mano puesto, me encontré con un método hace un par de días para convertir datetimes zona horaria consciente en otras más ingenuos en su hora local. Esto podría funcionar mucho mejor para su aplicación de lo que está haciendo actualmente (utiliza el excelente módulo 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 

sustituir 'América/LosAngeles' con su propia cadena de zona horaria, que se puede encontrar en algún lugar de pytz.all_timezones.

+0

Es incorrecta. 'mktime()' espera * hora local * mientras OP pasa la hora UTC. Falla si la zona horaria local no es UTC. No use 'naive_datetime_object.utctimetuple()' - no lo convierta en UTC (OP utiliza 'aware_datetime.object.utctimetuple()' que funciona como se esperaba). Es posible que necesite 'tz.normalize()' llamar después de '.astimezone()' si 'aware_dt' no está en UTC. – jfs

0

datetime.fromtimestamp() es el método correcto para obtener la hora local de POSIX timestamp. El problema en su pregunta es que convierta un objeto de fecha y hora consciente a la marca de tiempo de POSIX usando time.mktime() que es incorrecto. Aquí es 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) 
Cuestiones relacionadas