¿Cómo obtengo un valor de datetime.today () en Python que es "consciente de la zona horaria"?

310

Estoy tratando de restar un valor de fecha del valor de datetime.today()para calcular cuánto tiempo hace que algo estaba. Pero se queja:

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

El valor datetime.today()no parece ser "consciente de la zona horaria", mientras que mi otro valor de fecha sí lo es. ¿Cómo obtengo un valor datetime.today()que tenga en cuenta la zona horaria?

En este momento, me está dando la hora en hora local, que es PST, es decir, UTC: 8 horas. En el peor de los casos, ¿hay alguna manera de ingresar manualmente un valor de zona horaria en el datetimeobjeto devuelto por datetime.today()y configurarlo en UTC-8?

Por supuesto, la solución ideal sería conocer automáticamente la zona horaria.

ladronzuelo
fuente
10
Parece que podemos usar datetime.now().astimezone()desde Python 3.6
johnchen902

Respuestas:

362

En la biblioteca estándar, no hay una forma multiplataforma para crear zonas horarias conscientes sin crear su propia clase de zona horaria.

En Windows, hay win32timezone.utcnow(), pero eso es parte de pywin32. Prefiero sugerir utilizar la biblioteca pytz , que tiene una base de datos actualizada constantemente de la mayoría de las zonas horarias.

Trabajar con zonas horarias locales puede ser muy complicado (consulte los enlaces de "Lecturas adicionales" a continuación), por lo que puede preferir utilizar UTC en toda su aplicación, especialmente para operaciones aritméticas como calcular la diferencia entre dos puntos de tiempo.

Puede obtener la fecha / hora actual de esta manera:

import pytz
from datetime import datetime
datetime.utcnow().replace(tzinfo=pytz.utc)

Tenga en cuenta eso datetime.today()y datetime.now()devuelva la hora local , no la hora UTC, por lo que solicitarla .replace(tzinfo=pytz.utc)no sería correcto.

Otra buena manera de hacerlo es:

datetime.now(pytz.utc)

que es un poco más corto y hace lo mismo.


Leer más / ver por qué preferir UTC en muchos casos:

AndiDog
fuente
75
¿Qué tal en datetime.now(pytz.utc)lugar de datetime.utcnow().replace(tzinfo = pytz.utc)?
eumiro
55
now(utc)no regresa hoy (a menos que sea medianoche en UTC), devuelve la hora actual en UTC. También necesita .replace(hour=0, minute=0, ...)obtener el comienzo del día (como datetime.today())
jfs
1
Los documentos dicen que today()devuelve la hora actual, no la medianoche. Si hay un caso de uso donde se requiere medianoche, sí, el reemplazo debe hacerse en consecuencia. Dado que la pregunta original era sobre la diferencia de fecha y hora, no creo que sea necesaria la medianoche.
AndiDog
1
@AndiDog: Mi comentario implica que pensé (incorrectamente) que datetime.today()es combine(date.today(), time()). datetimetiene ambos .now()y .today()métodos que (como has señalado correctamente) devuelven (casi) lo mismo. No hay date.now()método datey los datetimeobjetos no son intercambiables. Usar un objeto datetime en lugar de un dateobjeto puede producir errores sutiles; No veo ninguna razón para datetime.today()existir si es casi un duplicado de datetime.now().
jfs
66
Además de esta respuesta, si está usando django, use siempre en timezone.now()lugar de, datetime.now()ya que usará UTC automáticamente si USE_TZ = True. timezonese encuentra en django.utils.timezone, documentación: docs.djangoproject.com/en/1.11/topics/i18n/timezones
Ryan
107

Obtenga la hora actual, en una zona horaria específica:

import datetime
import pytz
my_date = datetime.datetime.now(pytz.timezone('US/Pacific'))
philfreo
fuente
2
Mira esto .
wim
1
NO debe usar la hora localizada excepto para la salida. Muchas cosas salen mal cuando se usa la fecha y hora basada en la zona horaria: un horario simple no tiene en cuenta el horario de verano a menos que esté en horario UTC para empezar. Utilice siempre la zona horaria según UTC. convertir a zona horaria local en la salida cuando sea necesario.
MrE
44
Para reiterar un desacuerdo con @MrE que he expresado previamente en los comentarios sobre la respuesta aceptada: hay razones perfectamente válidas para trabajar con fechas y horarios localizados y "NO debe usar el tiempo localizado excepto para la salida" es un consejo demasiado amplio. Suponga que está agregando 1 día a una fecha y hora unas horas antes de un límite de horario de verano en el que los relojes retroceden una hora. ¿Qué resultado quieres? Si cree que la hora debería ser la misma, use fechas y horas localizadas. Si crees que debería ser una hora antes, usa UTC o datetime ingenuo. Lo que tiene sentido depende del dominio.
Mark Amery
@MarkAmery, tanto como puedo estar de acuerdo en que es posible que desee AGREGAR o RESTAR varios días u horas y no preocuparse por los problemas de zona horaria (como su ejemplo), este comentario se refiere a pasar el tiempo corregido de zona horaria a un cliente. Dado que Python se usa principalmente para procesos de back-end, pasa los tiempos a un cliente. El servidor siempre debe pasar la fecha / hora en UTC y el cliente debe convertirlo a su propia fecha / hora / zona horaria local, de lo contrario suceden cosas malas: solo verifique la salida datetime.datetime(2016, 11, 5, 9, 43, 45, tzinfo=pytz.timezone('US/Pacific'))y vea si eso es lo que esperaba
MrE
"El servidor siempre debe pasar la fecha / hora en UTC y el cliente debe convertirlo a su propia fecha / hora / zona horaria local" - no, esto no es universalmente cierto. A veces, usar la zona horaria del cliente es inapropiado y la zona horaria apropiada debe transmitirse como parte de los datos. Si, como londinense, veo los horarios de reunión de un club de ajedrez de San Francisco en su sitio web, debería verlos en el horario de San Francisco, no en el horario de Londres.
Mark Amery
69

En Python 3, la biblioteca estándar hace que sea mucho más fácil especificar UTC como la zona horaria:

>>> import datetime
>>> datetime.datetime.now(datetime.timezone.utc)
datetime.datetime(2016, 8, 26, 14, 34, 34, 74823, tzinfo=datetime.timezone.utc)

Si desea una solución que use solo la biblioteca estándar y que funcione tanto en Python 2 como en Python 3, consulte la respuesta de jfs .

Si necesita la zona horaria local, no UTC, vea la respuesta de Mihai Capotă

Flimm
fuente
19

Aquí hay una solución stdlib que funciona tanto en Python 2 como en 3:

from datetime import datetime

now = datetime.now(utc) # Timezone-aware datetime.utcnow()
today = datetime(now.year, now.month, now.day, tzinfo=utc) # Midnight

donde todayes una instancia de fecha y hora consciente que representa el comienzo del día (medianoche) en UTC y utces un objeto tzinfo ( ejemplo de la documentación ):

from datetime import tzinfo, timedelta

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

Relacionado: comparación de rendimiento de varias formas de llegar a la medianoche (inicio de un día) para una hora UTC determinada . Nota: es más complejo obtener la medianoche para una zona horaria con un desplazamiento UTC no fijo .

jfs
fuente
16

Otro método para construir un objeto datetime con reconocimiento de zona horaria que representa la hora actual:

import datetime
import pytz

pytz.utc.localize( datetime.datetime.utcnow() )  
Dariusz Walczak
fuente
tenga en cuenta que pytz.utcy pytz.UTCambos están definidos (y son lo mismo)
drevicko
2
Esta respuesta es mejor que la aceptada, ya que es más universal: replace()la zona horaria ing es generalmente propensa a errores en la mayoría de los otros usos, mientras que localize()ing es la forma preferida de asignar la zona horaria a las marcas de tiempo ingenuas.
Antony Hatchkins
@AntonyHatchkins: el .localize()método falla para horas locales ambiguas (entrada no utc). La respuesta de @ philfreo que utiliza.now(pytz_timezone) continúa funcionando en tales casos.
jfs
Como se especifica en los documentos de Python, .now(pytz_timezone)hace exactamente lo mismo que localize(utcnow): primero genera la hora actual en UTC, luego le asigna una zona horaria: "<...> En este caso, el resultado es equivalente a tz.fromutc(datetime.utcnow().replace(tzinfo=tz))". Ambas respuestas son correctas y funcionan siempre.
Antony Hatchkins
1
El único momento ingenuo (no utc) que puede ser consciente de la zona horaria es ahora : se supone que el sistema subyacente debe conocer el valor UTC y, a pytztravés de OLSON db, debe saber cómo convertirlo a cualquier zona horaria del mundo. Hacer consciente cualquier otra zona horaria ingenua (no utc) es difícil debido a la ambigüedad durante los turnos de verano. Eso no es un problema de .localize(darle un is_dstvalor lo hace funcionar para cualquier fecha). Ese es un problema inherente a la práctica del horario de verano.
Antony Hatchkins
16

Una línea utilizando solo la biblioteca estándar funciona a partir de Python 3.3. Puede obtener un datetimeobjeto consciente de la zona horaria local utilizando astimezone(como lo sugiere johnchen902 ):

from datetime import datetime, timezone

aware_local_now = datetime.now(timezone.utc).astimezone()

print(aware_local_now)
# 2020-03-03 09:51:38.570162+01:00

print(repr(aware_local_now))
# datetime.datetime(2020, 3, 3, 9, 51, 38, 570162, tzinfo=datetime.timezone(datetime.timedelta(0, 3600), 'CET'))
Mihai Capotă
fuente
2
La documentación es de gran ayuda, se encuentra aquí: docs.python.org/3.8/library/… . Esta pieza de funcionalidad increíblemente básica está enterrada en un párrafo oscuro de los documentos, por lo que esta respuesta de stackoverflow es efectivamente el único lugar en Internet con esta información. En la documentación, también se puede ver que, desde Python 3.6, datetime.now()se puede invocar sin ningún argumento y devolver el resultado local correcto ( datetimese presume que los ingenuos se encuentran en la zona horaria local).
atimholt
8

Si está utilizando Django , puede establecer fechas que no sean conscientes de tz (solo UTC ).

Comente la siguiente línea en settings.py:

USE_TZ = True
laffuste
fuente
8
¿Dónde viste a Django mencionado en esta pregunta?
vonPetrushev
1
Una especie de alma eliminó mi comentario anterior, disculpa aquí, así que nuevamente: lástima de mí, respuesta incorrecta ya que la pregunta no es específica de Django. Lo dejé porque podría ayudar a algunos usuarios de todos modos, pero lo eliminaré cuando la puntuación se acerque a 0. Si esta respuesta es inapropiada, siéntase libre de votar.
laffuste
6

pytz es una biblioteca de Python que permite cálculos de zona horaria precisos y multiplataforma con Python 2.3 o superior.

Con stdlib, esto no es posible.

Vea una pregunta similar sobre SO .

usuario225312
fuente
6

Aquí hay una forma de generarlo con stdlib:

import time
from datetime import datetime

FORMAT='%Y-%m-%dT%H:%M:%S%z'
date=datetime.strptime(time.strftime(FORMAT, time.localtime()),FORMAT)

date almacenará la fecha local y el desplazamiento desde UTC , no la fecha en la zona horaria UTC, por lo que puede usar esta solución si necesita identificar en qué zona horaria se genera la fecha . En este ejemplo y en mi zona horaria local:

date
datetime.datetime(2017, 8, 1, 12, 15, 44, tzinfo=datetime.timezone(datetime.timedelta(0, 7200)))

date.tzname()
'UTC+02:00'

La clave es agregar la %zdirectiva a la representación FORMAT, para indicar el desplazamiento UTC de la estructura de tiempo generada. Se pueden consultar otros formatos de representación en los documentos del módulo de fecha y hora

Si necesita la fecha en la zona horaria UTC, puede reemplazar time.localtime () con time.gmtime ()

date=datetime.strptime(time.strftime(FORMAT, time.gmtime()),FORMAT)

date    
datetime.datetime(2017, 8, 1, 10, 23, 51, tzinfo=datetime.timezone.utc)

date.tzname()
'UTC'

Editar

Esto solo funciona en python3 . La directiva z no está disponible en el código de python 2 _strptime.py

jcazor
fuente
ValueError: 'z' es una directiva incorrecta en formato '% Y-% m-% dT% H:% M:% S% z'
jno
Estás en Python 2, ¿verdad? Desafortunadamente, parece que la directiva z no está disponible en python 2. _strptime.py code
jcazor
6

Use dateutil como se describe en Python datetime.datetime.now () que conoce la zona horaria :

from dateutil.tz import tzlocal
# Get the current date/time with the timezone.
now = datetime.datetime.now(tzlocal())
G. Führ
fuente
1
Vea esta respuesta de JF Sebastian para una situación en la que esto da un resultado incorrecto.
Antony Hatchkins
2
Creo que el error en la otra publicación solo es relevante en casos de uso específicos. La tzlocal()función sigue siendo una de las soluciones más simples y definitivamente debería mencionarse aquí.
user8162
2

Obtener una fecha con utcreconocimiento de la zona horaria en la zona horaria es suficiente para que funcione la resta de fechas.

Pero si desea una fecha que reconozca la zona horaria en su zona horaria actual, este tzlocales el camino a seguir:

from tzlocal import get_localzone  # pip install tzlocal
from datetime import datetime
datetime.now(get_localzone())

PS dateutiltiene una función similar ( dateutil.tz.tzlocal). Pero a pesar de compartir el nombre, tiene una base de código completamente diferente, que, como señaló JF Sebastian, puede dar resultados incorrectos.

Antony Hatchkins
fuente
Python se usa generalmente en el servidor. La zona horaria local en un servidor generalmente no tiene sentido y siempre debe establecerse en UTC. Establecer datetime tzinfo de esta manera falla en algunos casos. mejor use UTC, luego localice a la zona horaria deseada solo en la salida. cualquier cálculo de timedelta, por ejemplo, no considera el horario de verano, por lo que debe hacerse en UTC, luego localizado.
MrE
@MrE ¿Incorrecto, fuera de tema, ejemplos?
Antony Hatchkins
intente usar un objeto de fecha y hora localizado en una zona horaria que observe el horario de verano, agregue varios días para cambiar el estado de horario de verano, y verá que la operación en los objetos de fecha y hora en la zona horaria local falla y no respetará el horario de verano. De ahí mi comentario de que SIEMPRE debe hacer cualquier operación de fecha y hora en hora UTC.
MrE
el punto es: no haga esto, realice sus operaciones en UTC y luego use datetime.astimezone (zona horaria) para convertir a la zona de hora local en la salida.
MrE
2

Aquí hay una solución que utiliza una zona horaria legible y que funciona con today ():

from pytz import timezone

datetime.now(timezone('Europe/Berlin'))
datetime.now(timezone('Europe/Berlin')).today()

Puede enumerar todas las zonas horarias de la siguiente manera:

import pytz

pytz.all_timezones
pytz.common_timezones # or
JohnAndrews
fuente
1

Especialmente para zonas horarias no UTC:

La única zona horaria que tiene su propio método es timezone.utc, pero puede evitar una zona horaria con cualquier desplazamiento UTC si lo necesita usando timedelta& timezoney forzándolo a usar .replace.

In [1]: from datetime import datetime, timezone, timedelta

In [2]: def force_timezone(dt, utc_offset=0):
   ...:     return dt.replace(tzinfo=timezone(timedelta(hours=utc_offset)))
   ...:

In [3]: dt = datetime(2011,8,15,8,15,12,0)

In [4]: str(dt)
Out[4]: '2011-08-15 08:15:12'

In [5]: str(force_timezone(dt, -8))
Out[5]: '2011-08-15 08:15:12-08:00'

Usar timezone(timedelta(hours=n))como zona horaria es la verdadera bala de plata aquí, y tiene muchas otras aplicaciones útiles.

código tmck
fuente
0

Si obtiene la hora y fecha actuales en python, importe la fecha y la hora, paquete pytz en python después de que obtendrá la fecha y hora actuales como ..

from datetime import datetime
import pytz
import time
str(datetime.strftime(datetime.now(pytz.utc),"%Y-%m-%d %H:%M:%S%t"))
jigar vagadiya
fuente
0

Otra alternativa, en mi opinión una mejor, está usando en Pendulumlugar de pytz. Considere el siguiente código simple:

>>> import pendulum

>>> dt = pendulum.now().to_iso8601_string()
>>> print (dt)
2018-03-27T13:59:49+03:00
>>>

Para instalar Pendulum y ver su documentación, vaya aquí . Tiene toneladas de opciones (como ISO8601 simple, RFC3339 y muchos otros formatos compatibles), un mejor rendimiento y tiende a producir un código más simple.

ng10
fuente
No estoy seguro de por qué votar aquí, este código funciona en varios programas que funcionan 7/24 para mí :). No es que me importe otra opinión, pero dígame por qué no funciona para usted, para permitirme verificarlo. Gracias de antemano
ng10
0

Use la zona horaria como se muestra a continuación para obtener una fecha y hora que tenga en cuenta la zona horaria. El valor predeterminado es UTC:

from django.utils import timezone
today = timezone.now()
Anupama V Iyengar
fuente
0

Tyler de 'howchoo' hizo un artículo realmente genial que me ayudó a tener una mejor idea de los objetos de fecha y hora, enlace a continuación

Trabajando con fecha y hora

esencialmente, acabo de agregar lo siguiente al final de mis dos objetos de fecha y hora

.replace(tzinfo=pytz.utc)

Ejemplo:

import pytz
import datetime from datetime

date = datetime.now().replace(tzinfo=pytz.utc)
Jose
fuente
0

intente pnp_datetime , todo el tiempo que se ha utilizado y devuelto es con la zona horaria, y no causará ningún problema ingenuo y consciente del desplazamiento.

>>> from pnp_datetime.pnp_datetime import Pnp_Datetime
>>>
>>> Pnp_Datetime.utcnow()
datetime.datetime(2020, 6, 5, 12, 26, 18, 958779, tzinfo=<UTC>)
nubosidad
fuente
0

Debe enfatizarse que desde Python 3.6, solo necesita la lib estándar para obtener un objeto datetime con reconocimiento de zona horaria que represente la hora local (la configuración de su sistema operativo). Usando astimezone ()

import datetime

datetime.datetime(2010, 12, 25, 10, 59).astimezone()
# e.g.
# datetime.datetime(2010, 12, 25, 10, 59, tzinfo=datetime.timezone(datetime.timedelta(seconds=3600), 'Mitteleuropäische Zeit'))

datetime.datetime(2010, 12, 25, 12, 59).astimezone().isoformat()
# e.g.
# '2010-12-25T12:59:00+01:00'

# I'm on CET/CEST

(Ver el comentario de @ johnchen902).

MrFuppes
fuente