Cómo hacer que un objeto de fecha y hora sea consciente (no ingenuo)
Lo que necesito hacer
Tengo un objeto de fecha y hora que no reconoce la zona horaria, al que necesito agregar una zona horaria para poder compararlo con otros objetos de fecha y hora que reconocen la zona horaria. No quiero convertir toda mi aplicación a zona horaria sin tener en cuenta para este caso heredado.
Lo que he probado
Primero, para demostrar el problema:
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
Primero, probé astimezone:
>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>
No es muy sorprendente que esto haya fallado, ya que en realidad está intentando realizar una conversión. Reemplazar parecía una mejor opción (según ¿ Cómo obtengo un valor de datetime.today() en Python que sea "consciente de la zona horaria"? ):
>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>>
Pero como puede ver, reemplazar parece configurar tzinfo, pero no hace que el objeto sea consciente. Me estoy preparando para volver a modificar la cadena de entrada para tener una zona horaria antes de analizarla (estoy usando dateutil para analizar, si eso importa), pero eso parece increíblemente torpe.
Además, probé esto tanto en Python 2.6 como en Python 2.7, con los mismos resultados.
Contexto
Estoy escribiendo un analizador para algunos archivos de datos. Hay un formato antiguo que necesito admitir donde la cadena de fecha no tiene un indicador de zona horaria. Ya arreglé la fuente de datos, pero aún necesito admitir el formato de datos heredado. Una conversión única de los datos heredados no es una opción por diversos motivos empresariales. Si bien en general no me gusta la idea de codificar una zona horaria predeterminada, en este caso parece la mejor opción. Sé con razonable confianza que todos los datos heredados en cuestión están en UTC, por lo que estoy preparado para aceptar el riesgo de utilizar eso de forma predeterminada en este caso.
En general, para que una fecha y hora ingenua tenga en cuenta la zona horaria, utilice el método de localización :
import datetime
import pytz
unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)
now_aware = pytz.utc.localize(unaware)
assert aware == now_aware
Para la zona horaria UTC, no es realmente necesario usarlo localize
ya que no hay que manejar ningún cálculo de horario de verano:
now_aware = unaware.replace(tzinfo=pytz.UTC)
obras. ( .replace
devuelve una nueva fecha y hora; no se modifica unaware
).
Todos estos ejemplos usan un módulo externo, pero puede lograr el mismo resultado usando solo el módulo de fecha y hora, como también se presenta en esta respuesta SO :
from datetime import datetime, timezone
dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)
print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'
Menos dependencias y sin problemas de pytz.
NOTA: Si desea utilizar esto con python3 y python2, también puede utilizarlo para la importación de zona horaria (codificada para UTC):
try:
from datetime import timezone
utc = timezone.utc
except ImportError:
#Hi there python2 user
class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return timedelta(0)
utc = UTC()
Escribí este script de Python 2 en 2011, pero nunca verifiqué si funciona en Python 3.
Me mudé de dt_aware a dt_unaware:
dt_unaware = dt_aware.replace(tzinfo=None)
y dt_unware a dt_aware:
from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)