Marcos Tozzi
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 la zona horaria sin saberlo 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é una zona horaria:
>>> 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 terriblemente sorprendente que haya fallado, ya que en realidad está tratando de hacer una conversión. Reemplazar parecía una mejor opción (según ¿Cómo obtengo un valor de datetime.today() en Python que es “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, replace parece establecer el tzinfo, pero no hace que el objeto sea consciente. Me estoy preparando para volver a manipular 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 debo 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 de una sola vez de los datos heredados no es una opción por varias razones comerciales de BS. Si bien, en general, no me gusta la idea de codificar una zona horaria predeterminada, en este caso parece ser 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 no hacerlo en este caso.
unutbu
En general, para hacer que una fecha y hora ingenua reconozca la zona horaria, use 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 usar localize
ya que no hay cálculo de horario de verano para manejar:
now_aware = unaware.replace(tzinfo=pytz.UTC)
obras. (.replace
devuelve una nueva fecha y hora; no modifica unaware
.)
-
Bueno, me siento tonto. Reemplazar devuelve una nueva fecha y hora. También dice eso allí mismo en los documentos, y me lo perdí por completo. Gracias, eso es exactamente lo que estaba buscando.
–Mark Tozzi
15 de agosto de 2011 a las 14:24
-
“Reemplazar devuelve una nueva fecha y hora”. Sí. La pista que le da REPL es que le muestra el valor devuelto. 🙂
– Karl Knechtel
15 de agosto de 2011 a las 14:33
-
si la zona horaria no es UTC, entonces no use el constructor directamente:
aware = datetime(..., tz)
usar.localize()
en cambio.– jfs
8 de marzo de 2014 a las 21:07
-
Vale la pena mencionar que la hora local puede ser ambigua.
tz.localize(..., is_dst=None)
afirma que no lo es.– jfs
08/03/2014 a las 21:10
-
putz tiene un error que establece la zona horaria de Ámsterdam a + 20 minutos de UTC, una zona horaria arcaica de 1937. Tenías un trabajo pytz.
– Borís
18 de febrero de 2020 a las 16:09
kang
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 usar esto con python3 y python2, también puede usarlo para la importación de la 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()
-
Muy buena respuesta para prevenir el
pytz
¡Me alegro de haberme desplazado un poco hacia abajo! No quería abordar conpytz
en mis servidores remotos de hecho 🙂– Tregoreg
1 de febrero de 2017 a las 20:20
-
Tenga en cuenta que
from datetime import timezone
funciona en py3 pero no en py2.7.– 7yl4r
20 de febrero de 2017 a las 16:44
-
Debes tener en cuenta que
dt.replace(tzinfo=timezone.utc)
devuelve una nueva fecha y hora, no modificadt
en su lugar. (Voy a editar para mostrar esto).– Blairg23
28 de marzo de 2017 a las 0:58
-
¿Cómo podría, en lugar de usar timezone.utc, proporcionar una zona horaria diferente como una cadena (por ejemplo, “América/Chicago”)?
– pueblerino
26/09/2017 a las 18:36
-
@bumpkin más vale tarde que nunca, supongo:
tz = pytz.timezone('America/Chicago')
– Florián
13 de febrero de 2019 a las 11:40
sergio
Escribí este script de Python 2 en 2011, pero nunca verifiqué si funciona en Python 3.
Me había mudado 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)
-
podrías usar
localtz.localize(dt_unware, is_dst=None)
para generar una excepción sidt_unware
representa la hora local inexistente o ambigua (nota: no hubo tal problema en la revisión anterior de su respuesta dondelocaltz
era UTC porque UTC no tiene transiciones de horario de verano– jfs
15 mayo 2014 a las 19:55
-
@JF Sebastian, primer comentario aplicado
– Sérgio
15 mayo 2014 a las 20:59
-
Le agradezco que muestre ambas direcciones de la conversión.
– Cristiano largo
24 de junio de 2016 a las 1:08
-
@Sérgio y cuando pones ese argumento en .replace y obtienes “TypeError: replace() obtuvo un argumento de palabra clave inesperado ‘tzinfo'”? ¿Qué se puede hacer para este problema?
– Gustavo Rottgering
22 de junio de 2020 a las 19:36
Gúgol
Uso esta declaración en Django para convertir un tiempo inconsciente en uno consciente:
from django.utils import timezone
dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())
xjcl
Python 3.9 agrega el zoneinfo
módulo ¡así que ahora solo se necesita la biblioteca estándar!
from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)
Adjunte una zona horaria:
>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'
Adjunte la zona horaria local del sistema:
>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'
Posteriormente se convierte correctamente a otras zonas horarias:
>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'
Lista de Wikipedia de zonas horarias disponibles
ventanas no tiene base de datos de zona horaria del sistema, por lo que aquí se necesita un paquete adicional:
pip install tzdata
Hay un backport para permitir el uso de zoneinfo
en Pitón 3.6 a 3.8:
pip install backports.zoneinfo
Entonces:
from backports.zoneinfo import ZoneInfo
-
en Windows, también necesita
pip install tzdata
– FObersteiner
11 de junio de 2020 a las 17:19
-
@MrFuppes ¡Gracias por el consejo! Voy a probar esto mañana y a mi respuesta. ¿Sabes cuál es la situación en Mac?
– xjcl
11 de junio de 2020 a las 22:32
-
@xjcl Necesitas
pip install tzdata
en cualquier plataforma donde el sistema operativo no proporcione una base de datos de zonas horarias. Las Mac deberían funcionar fuera de la caja. No dolerá instalartzdata
incondicionalmente (ya que los datos del sistema tienen prioridad sobretzdata
) si su aplicación necesita datos de zona horaria.– Pablo
21 de septiembre de 2020 a las 19:29
-
@xjcl tzdata es efectivamente parte de la biblioteca estándar (lo llamamos “paquete propio”), solo necesita instalarlo por pip porque tiene una cadencia de lanzamiento mucho más rápida que CPython.
– Pablo
27 de septiembre de 2020 a las 0:18
-
Quería mencionar que es muy fácil agregar esto a requirements.txt condicionalmente solo para Windows:
tzdata; sys_platform == "win32"
(de: stackoverflow.com/a/35614580/705296)–Joe Sadoski
31 de mayo de 2022 a las 14:37
Estoy de acuerdo con las respuestas anteriores, y está bien si está bien para comenzar en UTC. Pero creo que también es un escenario común para las personas trabajar con un valor consciente de tz que tiene un datetime que tiene una zona horaria local no UTC.
Si solo tuviera que ir por nombre, uno probablemente inferiría que replace() será aplicable y producirá el objeto consciente de fecha y hora correcto. Este no es el caso.
el replace( tzinfo=… ) parece ser aleatorio en su comportamiento. Por lo tanto, es inútil. ¡No uses esto!
localizar es la función correcta a utilizar. Ejemplo:
localdatetime_aware = tz.localize(datetime_nonaware)
O un ejemplo más completo:
import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())
me da un valor de fecha y hora consciente de la zona horaria de la hora local actual:
datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)
-
en Windows, también necesita
pip install tzdata
– FObersteiner
11 de junio de 2020 a las 17:19
-
@MrFuppes ¡Gracias por el consejo! Voy a probar esto mañana y a mi respuesta. ¿Sabes cuál es la situación en Mac?
– xjcl
11 de junio de 2020 a las 22:32
-
@xjcl Necesitas
pip install tzdata
en cualquier plataforma donde el sistema operativo no proporcione una base de datos de zonas horarias. Las Mac deberían funcionar fuera de la caja. No dolerá instalartzdata
incondicionalmente (ya que los datos del sistema tienen prioridad sobretzdata
) si su aplicación necesita datos de zona horaria.– Pablo
21 de septiembre de 2020 a las 19:29
-
@xjcl tzdata es efectivamente parte de la biblioteca estándar (lo llamamos “paquete propio”), solo necesita instalarlo por pip porque tiene una cadencia de lanzamiento mucho más rápida que CPython.
– Pablo
27 de septiembre de 2020 a las 0:18
-
Quería mencionar que es muy fácil agregar esto a requirements.txt condicionalmente solo para Windows:
tzdata; sys_platform == "win32"
(de: stackoverflow.com/a/35614580/705296)–Joe Sadoski
31 de mayo de 2022 a las 14:37
Daniel
Usar dateutil.tz.tzlocal()
para obtener la zona horaria en su uso de datetime.datetime.now()
y datetime.datetime.astimezone()
:
from datetime import datetime
from dateutil import tz
unlocalisedDatetime = datetime.now()
localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())
Tenga en cuenta que datetime.astimezone
primero convertirá su datetime
objeto a UTC y luego a la zona horaria, que es lo mismo que llamar datetime.replace
siendo la información original de la zona horaria None
.
-
Si quieres que sea UTC:
.replace(tzinfo=dateutil.tz.UTC)
– Martín Tomas
19 de agosto de 2019 a las 6:08
-
Una importación menos y justa:
.replace(tzinfo=datetime.timezone.utc)
– kubanczyk
9 de abril de 2020 a las 15:16
unaware.replace()
volveríaNone
si estuviera modificandounaware
objeto en su lugar. El REPL muestra que.replace()
devuelve un nuevodatetime
objeto aquí.– jfs
15 de agosto de 2011 a las 16:40
Lo que necesitaba cuando vine aquí:
import datetime; datetime.datetime.now(datetime.timezone.utc)
– Martín Tomas
16 de enero de 2018 a las 9:32
@MartinThoma usaría el nombre
tz
arg para que sea más legible:datetime.datetime.now(tz=datetime.timezone.utc)
– Asclepio
14 de octubre de 2019 a las 2:45
astimezone()
ahora (comenzando con 3.6) puede llamarse en un objeto ingenuo, y su parámetro puede (comenzando con 3.3) omitirse, por lo que la solución es tan simple comounaware.astimezone()
– vashekcz
25 de febrero de 2021 a las 21:49
Encontré el truco: Europa/París, Berlín, CET, … tienen errores completos en pytz y estuve en un lío de inestabilidad durante meses debido a eso. ¡Reemplacé todo eso por dateutil.tz.gettz(…) y ahora mi código es estable y funciona! Mi consejo: ¡abandona por completo a pytz!
– hervé-guerin
29/09/2022 a las 10:35