When se habilita el soporte para zonas horarias, Django almacena la información de fechas y horas en UTC en la base de datos, utiliza objetos de fecha y hora conscientes de la zona horaria internamente y los convierte a la zona horaria del usuario final en las formas. Los templates utilizarán la zona horaria predeterminada, pero esto se puede actualizar a la zona horaria del usuario final mediante el uso de filtros y etiquetas.
Esto es útil si tus usuarios viven en más de una zona horaria y quieres mostrar información de fechas y horas según cada reloj de pared del usuario.
Incluso si tu sitio web está disponible solo en una zona horaria, sigue siendo buena práctica almacenar los datos en UTC en tu base de datos. La razón principal es el ahorro de energía (DST). Muchos países tienen un sistema de DST, donde los relojes se adelantan en la primavera y se atrasan en el otoño. Si estás trabajando con hora local, probablemente te encontrarás con errores dos veces al año, durante las transiciones. Esto probablemente no importa para tu blog, pero es un problema si sobrecargas o subcobras a tus clientes por una hora, dos veces al año, cada año. La solución a este problema es utilizar UTC en el código y utilizar hora local solo cuando interactúes con los usuarios finales.
El soporte para zonas horarias está habilitado por defecto. Para deshabilitarlo, establece USE_TZ = False en tu archivo de configuración.
El soporte para zonas horarias utiliza zoneinfo, que es parte de la biblioteca estándar de Python desde Python 3.9.
Si estás luchando con un problema particular, comienza con el FAQ sobre zonas horarias.
Los objetos de fecha y hora de Python tienen un atributo tzinfo que se puede utilizar para almacenar información de zona horaria, representada como una instancia de una subclase de datetime.tzinfo. Cuando este atributo está configurado y describe un desplazamiento, un objeto de fecha y hora es consciente. De lo contrario, es ingenuo.
Puedes utilizar las funciones is_aware() y is_naive() para determinar si las fechas y horas son conscientes o ingenuas.
When el soporte de zona horaria está deshabilitado, Django utiliza objetos datetime ingenuos en tiempo local. Esto es suficiente para muchos casos de uso. En este modo, para obtener la hora actual, escribirías:
import datetime
now = datetime.datetime.now()
Cuando el soporte de zona horaria está habilitado (USE_TZ=True), Django utiliza objetos datetime conscientes de la zona horaria. Si tu código crea objetos datetime, deben ser conscientes también. En este modo, el ejemplo anterior se convierte en:
from django.utils import timezone
now = timezone.now()
Advertencia
Trabajar con objetos datetime conscientes no es siempre intuitivo. Por ejemplo, el argumento tzinfo del constructor estándar de datetime no funciona de manera fiable para las zonas horarias con DST. Utilizar UTC es generalmente seguro; si estás utilizando otras zonas horarias, debes revisar la documentación de zoneinfo con cuidado.
Nota
Los objetos tiempo de Python (datetime.time) también tienen un atributo tzinfo, y PostgreSQL tiene un tipo correspondiente time with time zone. Sin embargo, como lo explica los docs de PostgreSQL, este tipo «exhibe propiedades que llevan a una utilidad cuestionable».
Django solo admite objetos tiempo ingenuos y levantará una excepción si intentas guardar un objeto tiempo consciente, ya que una zona horaria para un tiempo sin fecha asociada no tiene sentido.
Cuando USE_TZ es True, Django aún acepta objetos datetime ingenuos, con el fin de preservar la compatibilidad hacia atrás. Cuando la capa de base de datos recibe uno, intenta hacerlo consciente interpretándolo en la zona horaria predeterminada y levanta una advertencia.
Desafortunadamente, durante las transiciones de DST, algunas fechas no existen o son ambiguas. Eso es por qué debes crear siempre objetos datetime conscientes cuando el soporte de zona horaria está habilitado. (Ver la sección Using ZoneInfo del docs de zoneinfo para ejemplos que utilizan el atributo fold para especificar el desplazamiento que debe aplicarse a una fecha durante una transición de DST.)
En la práctica, esto es raramente un problema. Django te da objetos datetime conscientes en los modelos y formularios, y la mayoría de las veces, nuevos objetos datetime se crean desde existentes mediante aritmética con timedelta. El único objeto datetime que a menudo se crea en el código de aplicación es la hora actual, y timezone.now() hace lo correcto automáticamente.
The zona horaria por defecto es la zona horaria definida por la configuración TIME_ZONE.
La zona horaria actual es la zona horaria utilizada para renderizar.
Debes establecer la zona horaria actual en la zona horaria real del usuario con activate(). De lo contrario, se utiliza la zona horaria por defecto.
Nota
Como se explica en la documentación de TIME_ZONE, Django establece variables de entorno para que su proceso corra en la zona horaria por defecto. Esto ocurre sin importar el valor de USE_TZ y de la zona horaria actual.
Cuando USE_TZ es True, esto es útil para preservar la compatibilidad hacia atrás con aplicaciones que aún dependen del tiempo local. Sin embargo, como se explica arriba <naive-datetime-objects>, esto no es del todo fiable, y siempre debes trabajar con fechas y horas conscientes en UTC en tu propio código. Por ejemplo, utiliza fromtimestamp() y establece el parámetro tz a utc.
La zona horaria actual es equivalente a la zona horaria actual del locale para traducciones. Sin embargo, no hay equivalente al encabezado HTTP Accept-Language que Django podría utilizar para determinar automáticamente la zona horaria del usuario. En su lugar, Django proporciona funciones de selección de zona horaria. Utilízalas para construir la lógica de selección de zona horaria que tenga sentido para ti.
La mayoría de los sitios web que se preocupan por las zonas horarias preguntan a los usuarios en qué zona horaria viven y almacenan esta información en el perfil del usuario. Para usuarios anónimos, utilizan la zona horaria de su audiencia principal o UTC. zoneinfo.available_timezones() proporciona un conjunto de zonas horarias disponibles que puedes utilizar para construir una mapa desde ubicaciones probables a zonas horarias.
Aquí tienes un ejemplo que almacena la zona horaria actual en la sesión. (Se omite el manejo de errores por completo por simplicidad.)
Añade el siguiente middleware a MIDDLEWARE:
import zoneinfo
from django.utils import timezone
class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
tzname = request.session.get("django_timezone")
if tzname:
timezone.activate(zoneinfo.ZoneInfo(tzname))
else:
timezone.deactivate()
return self.get_response(request)
Create a view that can set the current timezone:
from django.shortcuts import redirect, render
# Prepare a map of common locations to timezone choices you wish to offer.
common_timezones = {
"London": "Europe/London",
"Paris": "Europe/Paris",
"New York": "America/New_York",
}
def set_timezone(request):
if request.method == "POST":
request.session["django_timezone"] = request.POST["timezone"]
return redirect("/")
else:
return render(request, "template.html", {"timezones": common_timezones})
Crea una vista que pueda establecer la zona horaria actual.
{% load tz %}
{% get_current_timezone as TIME_ZONE %}
<form action="{% url 'set_timezone' %}" method="POST">
{% csrf_token %}
<label for="timezone">Time zone:</label>
<select name="timezone">
{% for city, tz in timezones.items %}
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
{% endfor %}
</select>
<input type="submit" value="Set">
</form>
template.html que se enviará mediante POST a esta vista.¶Entrada de zona horaria en los formularios
Cuando habilitas el soporte de zona horaria, Django interpreta los datetimes introducidos en formularios en la zona horaria actual y devuelve objetos datetime conscientes en cleaned_data.
Salida de zona horaria en plantillas
Advertencia
Cuando habilitas el soporte de zona horaria, Django convierte los objetos datetime conscientes a la zona horaria actual cuando se renderizan en plantillas. Esto comporta muy mucho como localización de formato.
Django no convierte objetos datetime ingenuos porque podrían ser ambiguos y porque tu código nunca debería producir objetos datetime ingenuos cuando el soporte de zona horaria está habilitado. Sin embargo, puedes forzar la conversión con los filtros de plantilla descritos a continuación.
Estos filtros aceptan tanto objetos datetime conscientes como ingenuos. Para fines de conversión, suponen que los objetos datetime ingenuos están en la zona horaria por defecto. Siempre devuelven objetos datetime conscientes.
tz, te permiten controlar las conversiones de zona horaria.¶Convierte una sola valor a la zona horaria actual.
Por ejemplo:
{% load tz %}
{{ value|localtime }}
utc¶Convierte un solo valor a UTC.
Por ejemplo:
{% load tz %}
{{ value|utc }}
timezone¶Convierte un solo valor a una zona horaria arbitraria.
El argumento debe ser una instancia de una subclase de tzinfo o el nombre de una zona horaria.
Por ejemplo:
{% load tz %}
{{ value|timezone:"Europe/Paris" }}
Aquí tienes cómo migrar un proyecto que se inició antes de que Django soportara zonas horarias.
La base de datos PostgreSQL almacena fechas y horas como timestamp with time zone. En la práctica, esto significa que convierte las fechas y horas desde la zona horaria de la conexión a UTC en el almacenamiento, y de UTC a la zona horaria de la conexión en la recuperación.
Los cambios en la configuración de zona horaria no afectan el rendimiento.
Configuración de zona horaria
La zona horaria configurada para la conexión en la configuración de bases de datos es distinta de la zona horaria general.
Los backends restantes almacenan fechas y horas sin información sobre zona horaria. Si cambias a USE_TZ = True, debes convertir tus datos desde la hora local a UTC – lo cual no es determinista si tu hora local tiene DST.
El primer paso es agregar USE_TZ = True a tu archivo de configuración. En este punto, las cosas deberían funcionar casi correctamente. Si creas objetos datetime ingenuos en tu código, Django los hace conscientes cuando sea necesario.
Sin embargo, estas conversiones pueden fallar durante las transiciones DST, lo que significa que no estás aprovechando al máximo el soporte de zona horaria. Además, es probable que te encuentres con algunos problemas porque es imposible comparar un objeto datetime ingenuo con uno consciente. Dado que Django ahora te da objetos datetime conscientes, obtendrás excepciones en todos los lugares donde compares una fecha y hora que proviene de un modelo o formulario con un objeto datetime ingenuo que has creado en tu código.
Por lo tanto, el segundo paso es refactorizar tu código en todas las partes donde instancias objetos datetime para hacerlos conscientes. Esto se puede hacer de manera incremental. django.utils.timezone define algunos ayudantes útiles para el código compatible: now(), is_aware(), is_naive(), make_aware(), y make_naive().
Finalmente, con el fin de ayudarte a localizar el código que necesita actualizarse, Django lanza una advertencia cuando intentas guardar un objeto datetime ingenuo en la base de datos:
RuntimeWarning: DateTimeField ModelName.field_name received a naive
datetime (2012-01-01 00:00:00) while time zone support is active.
Durante el desarrollo, puedes convertir tales advertencias en excepciones y obtener un seguimiento de pila agregando lo siguiente a tu archivo de configuración:
import warnings
warnings.filterwarnings(
"error",
r"DateTimeField .* received a naive datetime",
RuntimeWarning,
r"django\.db\.models\.fields",
)
Al serializar una fecha consciente del huso horario, se incluye el desplazamiento UTC, como se muestra a continuación:
"2011-09-01T13:20:30+03:00"
Mientras que para una fecha ingenua no es así:
"2011-09-01T13:20:30"
Para modelos con DateTimeFields, esta diferencia hace imposible escribir un conjunto de datos que funcione tanto con como sin soporte de zona horaria.
Los conjuntos de datos generados con USE_TZ = False, o antes de Django 1.4, utilizan el formato «ingenuo». Si tu proyecto contiene tales conjuntos de datos, después de habilitar el soporte de zona horaria, verás RuntimeWarnings cuando los cargues. Para deshacerte de las advertencias, debes convertir tus conjuntos de datos al formato «consciente».
Puedes regenerar conjuntos de datos con loaddata luego dumpdata. O, si son lo suficientemente pequeños, puedes editarlos para agregar el desplazamiento UTC que coincide con tu TIME_ZONE a cada fecha serializada.
No necesito múltiples zonas horarias. ¿Debo habilitar el soporte de zona horaria?
Sí. Cuando se habilita el soporte de zona horaria, Django utiliza un modelo más preciso del tiempo local. Esto te protege contra bugs sutiles e irreproducibles alrededor de las transiciones de horario de verano (DST).
When habilitas el soporte de zona horaria, encontrarás algunos errores porque estás utilizando fechas y horas no informadas donde Django espera fechas y horas informadas. Estos errores aparecen cuando se ejecutan las pruebas. Aprenderás rápidamente a evitar operaciones inválidas.
Por otro lado, los bugs causados por la falta de soporte de zona horaria son mucho más difíciles de prevenir, diagnosticar y corregir. Cualquier cosa que involucre tareas programadas o aritmética con fechas y horas es un candidato para bugs sutiles que te morderán solo una vez al año.
Por estas razones, el soporte de zona horaria está habilitado por defecto en nuevos proyectos, y debes mantenerlo a menos que tengas una muy buena razón para no hacerlo.
He habilitado el soporte de zona horaria. ¿Estoy seguro?
Quizás. Estás mejor protegido contra bugs relacionados con la hora de verano, pero todavía puedes lastimarte los pies al convertir cuidadosamente fechas y horas no informadas en fechas y horas informadas, y viceversa.
Si tu aplicación se conecta a otros sistemas – por ejemplo, si consulta un servicio web – asegúrate de que las fechas y horas estén especificadas correctamente. Para transmitir fechas y horas de manera segura, su representación debe incluir el desplazamiento horario, o sus valores deben estar en UTC (o ambos!).
Finalmente, nuestro sistema de calendario contiene casos interesantes de borde. Por ejemplo, no siempre puedes restar un año directamente de una fecha dada:
>>> import datetime
>>> def one_year_before(value): # Wrong example.
... return value.replace(year=value.year - 1)
...
>>> one_year_before(datetime.datetime(2012, 3, 1, 10, 0))
datetime.datetime(2011, 3, 1, 10, 0)
>>> one_year_before(datetime.datetime(2012, 2, 29, 10, 0))
Traceback (most recent call last):
...
ValueError: day is out of range for month
Para implementar correctamente tal función, debes decidir si 2012-02-29 menos un año es 2011-02-28 o 2011-03-01, lo que depende de los requisitos de tu negocio.
¿Cómo interactúo con una base de datos que almacena fechas y horas en hora local?
Establece la opción TIME_ZONE a la zona horaria adecuada para esta base de datos en la configuración DATABASES.
Este es el resultado de la traducción:
Mi aplicación se cae con TypeError: can't compare offset-naive y datetimes offset-aware – ¿qué pasa?
Vamos a reproducir este error comparando una fecha y hora naiva y otra consciente de la zona horaria:
>>> from django.utils import timezone
>>> aware = timezone.now()
>>> naive = timezone.make_naive(aware)
>>> naive == aware
Traceback (most recent call last):
...
TypeError: can't compare offset-naive and offset-aware datetimes
Si encuentras este error, es probable que tu código esté comparando estas dos cosas:
una fecha y hora proporcionada por Django – por ejemplo, un valor leído de un formulario o un campo de modelo. Dado que has habilitado el soporte para zonas horarias, es consciente.
una fecha y hora generada por tu código, que es naiva (o no estarías leyendo esto).
Generalmente, la solución correcta es cambiar tu código para utilizar una fecha y hora consciente de la zona horaria en lugar de naiva.
Si estás escribiendo una aplicación pluggable que se espera que funcione independientemente del valor de USE_TZ, puede resultarte útil django.utils.timezone.now(). Esta función devuelve la fecha y hora actual como una fecha y hora naiva cuando USE_TZ = False y como una fecha y hora consciente de la zona horaria cuando USE_TZ = True. Puedes sumar o restar datetime.timedelta según sea necesario.
Veo muchos RuntimeWarning: DateTimeField recibió una fecha y hora naiva (YYYY-MM-DD HH:MM:SS) mientras que el soporte para zonas horarias está activo – ¿es eso malo?
Cuando se habilita el soporte de zona horaria, la capa del motor de base de datos espera recibir solo fechas y horas conscientes de la zona horaria desde tu código. Este aviso ocurre cuando recibe una fecha y hora ingenua. Esto indica que no has terminado de portar tu código para el soporte de zona horaria. Por favor, consulta la guía de migración <time-zones-migration-guide> para obtener consejos sobre este proceso.
Mientras tanto, por compatibilidad hacia atrás, la fecha y hora se considera estar en la zona horaria predeterminada, lo que generalmente es lo que esperas.
now.date() es ayer (o mañana)
Si siempre has utilizado fechas y horas ingenuas, probablemente creas que puedes convertir una fecha y hora a una fecha llamando al método date() de esta. También consideras que un date es mucho como una datetime, excepto que es menos preciso.
Ninguno de esto es cierto en un entorno consciente de zona horaria:
>>> import datetime
>>> import zoneinfo
>>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris")
>>> new_york_tz = zoneinfo.ZoneInfo("America/New_York")
>>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz)
# This is the correct way to convert between time zones.
>>> new_york = paris.astimezone(new_york_tz)
>>> paris == new_york, paris.date() == new_york.date()
(True, False)
>>> paris - new_york, paris.date() - new_york.date()
(datetime.timedelta(0), datetime.timedelta(1))
>>> paris
datetime.datetime(2012, 3, 3, 1, 30, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
>>> new_york
datetime.datetime(2012, 3, 2, 19, 30, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))
Como muestra este ejemplo, la misma fecha y hora tiene una fecha diferente, dependiendo de la zona horaria en la que se representa. Pero el problema real es más fundamental.
Una fecha y hora representa un punto en tiempo. Es absoluto: no depende de nada. Por el contrario, una fecha es un concepto calendario. Es un período de tiempo cuyos límites dependen de la zona horaria en la que se considera la fecha. Como puedes ver, estos dos conceptos son fundamentalmente diferentes, y convertir una fecha y hora a una fecha no es una operación determinista.
¿Qué significa esto en la práctica?
Generalmente, debes evitar convertir una datetime a date. Por ejemplo, puedes utilizar el filtro de plantilla date para mostrar solo la parte de fecha de una fecha y hora. Este filtro convertirá la fecha y hora en la zona horaria actual antes de formatearla, asegurando que los resultados aparezcan correctamente.
Si realmente necesitas hacer la conversión tú mismo, debes asegurarte de que la fecha y hora se convierta a la zona horaria adecuada primero. Normalmente, esto será la zona horaria actual:
>>> from django.utils import timezone
>>> timezone.activate(zoneinfo.ZoneInfo("Asia/Singapore"))
# For this example, we set the time zone to Singapore, but here's how
# you would obtain the current time zone in the general case.
>>> current_tz = timezone.get_current_timezone()
>>> local = paris.astimezone(current_tz)
>>> local
datetime.datetime(2012, 3, 3, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='Asia/Singapore'))
>>> local.date()
datetime.date(2012, 3, 3)
Me sale un error «¿Están definidas las definiciones de zona horaria para tu base de datos?»
Si estás utilizando MySQL, consulta la sección Definiciones de zona horaria de los notas de MySQL para obtener instrucciones sobre cómo cargar definiciones de zona horaria.
Tengo una cadena "2012-02-21 10:28:45" y sé que está en la "Europe/Helsinki" zona horaria. ¿Cómo puedo convertirla en un datetime consciente de la zona horaria?
Aquí debes crear el instancia requerida ZoneInfo y adjuntarla al datetime ingenuo:
>>> import zoneinfo
>>> from django.utils.dateparse import parse_datetime
>>> naive = parse_datetime("2012-02-21 10:28:45")
>>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki"))
datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))
¿Cómo puedo obtener la hora local en la zona horaria actual?
Bueno, la primera pregunta es: ¿realmente lo necesitas?
Deberías utilizar solo la hora local cuando interactúes con humanos, y el capa de plantillas proporciona filtros y etiquetas para convertir fechas a la zona horaria de tu elección.
Además, Python sabe cómo comparar fechas conscientes de la zona horaria, teniendo en cuenta los desplazamientos UTC cuando sea necesario. Es mucho más fácil (y posiblemente más rápido) escribir todo tu código de modelo y vista en UTC. Por lo tanto, en la mayoría de las circunstancias, la fecha en UTC devuelta por django.utils.timezone.now() será suficiente.
Por el bien de la completitud, aunque, si realmente quieres obtener la hora local en la zona horaria actual, aquí está cómo puedes hacerlo:
>>> from django.utils import timezone
>>> timezone.localtime(timezone.now())
datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
En este ejemplo, la zona horaria actual es "Europe/Paris".
¿Cómo puedo ver todas las zonas horarias disponibles?
La función zoneinfo.available_timezones() proporciona el conjunto de todas las claves válidas para las zonas horarias IANA disponibles en su sistema. Consulte la documentación para consideraciones sobre el uso.
may 31, 2026