Los horarios de zona son una mentira (y cómo manejarlos en el código)
Las zonas horarias parecen simples desplazamientos UTC. No lo son. Este guía explica por qué las zonas horarias rompen el código — los huecos de la hora de verano, desplazamientos de media hora, fechas "naivas" — y cómo manejarlas correctamente en JavaScript, Python, PHP y SQL.
Preguntaste a tu servidor qué hora era. Respondió 14:00. Lo almacenaste. Lo consultaste de nuevo. Ahora dice 16:00. No hiciste nada. Bienvenido a los horarios de zona.
Las zonas horarias son uno de los problemas más engañosamente complejos en software. A primera vista, parecen una simple diferencia respecto a UTC — simplemente sumar o restar algunas horas. En el fondo, son una mezcla caótica de decisiones políticas, accidentes históricos, desplazamientos de media hora y reglas de hora de verano que cambian sin aviso. Este artículo desglosa por qué las zonas horarias son tan dolorosas y, más importante aún, cómo manejarlas correctamente en el código.
¿Por qué “Sólo usa UTC” es solo la mitad de la respuesta?
La consejo más común que escucharás es: almacenar todo en UTC. Esa recomendación es correcta — pero es incompleta. Almacenar en UTC soluciona una problema (almacenamiento consistente) mientras deja sin resolver un problema más grande: la presentación y la entrada.
Un usuario en Tokio programa una reunión a las 9 de la mañana en su hora local. Lo almacenas como UTC. Más tarde, un usuario en Nueva York abre el mismo evento. Lo muestra en su hora local. Pero, ¿cuál es la hora local cuando el usuario viaja? Cuando actualiza su reloj del sistema? Cuando entra en hora de verano? “Almacenar UTC, mostrar local” es una política sólida — es la ejecución que rompe a la mayoría de los equipos.
Los problemas reales con las zonas horarias
Antes de llegar a las soluciones, ayuda nombrar lo que realmente estás enfrentando.
1. Los desplazamientos UTC no son zonas horarias
UTC+5:30 No es “hora de India”. Es un desplazamiento estático. La hora estándar de India es Asia/Kolkata — una zona horaria con nombre que utiliza +5:30 y que nunca ha observado el horario de verano. Estos son cosas diferentes. Si hardcodes un desplazamiento, estás almacenando un número. Si almacenas una zona con nombre, estás almacenando intención.
Las zonas con nombre existen en la Base de datos de zonas horarias IANA (también conocida como tzdata o la base de datos de Olson). Cada lenguaje y sistema operativo incluye una copia de ella. Usa esta. Siempre prefiere America/New_York sobre UTC-5.
2. El horario de verano mueve los relojes (de forma imprevisible)
Las transiciones de horario de verano crean dos casos extremadamente peligrosos:
- El “avance de primavera”: Los relojes saltan de las 2:00 a las 3:00. La hora 2:30 literalmente no existe. Si intentas programar algo a las 2:30 en ese día, obtendrás un error o un comportamiento silencioso dependiendo de tu biblioteca.
- El “regreso de otoño” (ambigüedad): Los relojes van de las 2:00 a las 1:00. La hora 1:30 ahora ocurre dos veces. Sin contexto adicional (la bandera de doble ocurrencia), no puedes saber cuál de las dos ocurrencias significa.
Y las reglas de horario de verano cambian. Países y estados de EE. UU. han cambiado, eliminado o modificado sus reglas en el recuerdo. El comportamiento de tu código depende de tener una versión actualizada de tzdata — eso es una preocupación de operaciones, no solo de desarrollo.
3. No todas las diferencias son horas completas
India es UTC+5:30. Nepal es UTC+5:45. Partes de Australia son UTC+9:30. Irán es UTC+3:30 en invierno y UTC+4:30 en verano. Si tu sistema asume que las zonas horarias siempre están en la hora, corromperá silenciosamente los timestamps para millones de usuarios.
4. “Hora local” en un servidor no tiene sentido
Los servidores tienen relojes de sistema. Esos relojes tienen una zona horaria configurada — a menudo UTC, a veces lo que haya establecido el proveedor de alojamiento por defecto, o lo que un administrador de sistemas configuró hace años. El código que llama a new Date() o datetime.now() sin especificar una zona está implicitamente dependiendo de esa configuración del servidor. Diferentes entornos darán resultados diferentes. Esto es un error esperado en cada despliegue.
El enfoque correcto, por lenguaje
JavaScript / TypeScript
El objeto nativo Date en JavaScript es un envoltorio delimitado al un timestamp en milisegundos UTC. Parece amigable; no lo es. Evita formatearlo manualmente — usa la Intl.DateTimeFormat API para la presentación, o busca una biblioteca.
La norma moderna es Temporal — una propuesta de TC39 que llegó a Chrome 121 y que llegará a todos los entornos principales. Tiene soporte primero de clases para zonas horarias y es la respuesta correcta a largo plazo:
// Store an instant (UTC-equivalent)
const meeting = Temporal.Instant.from("2025-06-15T14:00:00Z");
// Display in a specific zone
const nyTime = meeting.toZonedDateTimeISO("America/New_York");
console.log(nyTime.toString()); // 2025-06-15T10:00:00-04:00[America/New_York]
// Convert to Tokyo time
const tokyoTime = meeting.toZonedDateTimeISO("Asia/Tokyo");
console.log(tokyoTime.toString()); // 2025-06-16T23:00:00+09:00[Asia/Tokyo]
Si aún no puedes usar Temporal, date-fns-tz en combinación con date-fns es una opción confiable. Luxon es otra opción sólida. Moment.js es completo en funcionalidades pero ya no se mantiene — migra de él.
Pitón
Python’s datetime El módulo distingue entre “fechas ingenuas” (sin información de zona horaria) y “fechas conscientes” (con tzinfo). Las fechas ingenuas son una trampa — parecen fechas válidas, pero no tienen significado entre sistemas.
Siempre usa fechas conscientes. Usa el módulo zoneinfo en Python 3.9+ para soporte de zonas horarias IANA: Para Python 3.8 y versiones anteriores, usa la biblioteca
from datetime import datetime
from zoneinfo import ZoneInfo
# Aware datetime — always do this
utc_time = datetime(2025, 6, 15, 14, 0, 0, tzinfo=ZoneInfo("UTC"))
# Convert to New York time
ny_time = utc_time.astimezone(ZoneInfo("America/New_York"))
print(ny_time) # 2025-06-15 10:00:00-04:00
# Never do this — naive datetime, meaningless
bad = datetime(2025, 6, 15, 14, 0, 0) # what zone is this?
— pero ten cuidado con la API localize/normalize de pytz que tiene trampas que pytzevita. zoneinfo Las clases soportan zonas IANA nativamente mediante
PHP
PHP’s DateTime y DateTimeImmutable . Prefiere DateTimeZone— es más seguro porque las modificaciones devuelven objetos nuevos en lugar de modificar en lugar. DateTimeImmutable Base de datos / SQL
$utc = new DateTimeImmutable('2025-06-15T14:00:00', new DateTimeZone('UTC'));
// Convert to Sydney time
$sydney = $utc->setTimezone(new DateTimeZone('Australia/Sydney'));
echo $sydney->format('Y-m-d H:i:s T'); // 2025-06-16 00:00:00 AEST
// Store timestamps as ISO 8601 strings or Unix timestamps
echo $utc->getTimestamp(); // 1749996000
El manejo de zonas horarias en bases de datos es un campo propio.
PostgreSQL:
- (timestamp with time zone) — almacena todo en UTC y lo convierte en salida. Nunca uses Usa
TIMESTAMPTZ(sin zona horaria) para datos de usuarios; almacena lo que le des sin conversión.TIMESTAMPMySQL: - es ingenuo (sin zona). Usa El
DATETIMEsi deseas almacenar en UTC, pero ten en cuenta que su rango se limita a 2038. Para nuevos esquemas, almacenar en formato ISO 8601 o como un timestamp Unix enTIMESTAMPes a menudo más seguro.VARCHARSQLite:BIGINTNo tiene tipo nativo de zona horaria. Almacena en formato de texto ISO 8601 ( - ) o como un entero Unix. Maneja la conversión en el código de la aplicación. Cualquier base de datos que uses, establece explícitamente la zona horaria de sesión en lugar de depender de valores por defecto.
2025-06-15T14:00:00ZReglas prácticas que realmente funcionan
Después de analizar la teoría, aquí están las reglas concretas que previenen la mayoría de los errores de zonas horarias:
Almacena UTC en todos lados.
Cada timestamp en tu base de datos debe ser UTC. No hay excepciones por “solo se usa internamente”.
- Usa nombres de zonas IANA, no desplazamientos. Almacena
- junto con un timestamp UTC cuando necesitas reconstruir la hora local original. El desplazamiento solo no es recuperable tras una transición de horario de verano. Nunca llames a “ahora” sin una zona.
America/Chicago— siempre pasa un argumento explícito de zona o inmediatamente lo adjunta. - Convierte a hora local en el momento final.
datetime.now(),new Date(),time()Haz toda la matemática de fechas en UTC. Solo conviertas a la hora local de un usuario justo antes de mostrarlo. - Mantén tzdata actualizado. Las reglas de zonas horarias cambian. Haz que las actualizaciones de tzdata sean parte de tu gestión regular de dependencias.
- Prueba en torno a transiciones de horario de verano. Las dos fechas peligrosas cada año (avance de primavera, regreso de otoño) deben estar en tu conjunto de pruebas si manejas programaciones o datos sensibles al tiempo.
- Pide explícitamente a los usuarios su zona horaria. No confíes en la geolocalización por IP o en la suposición del navegador para nada importante. Muestra un selector de zonas horarias; almacena el resultado.
- Una palabra sobre los timestamps Unix Los timestamps Unix — segundos desde 1970-01-01T00:00:00Z — son independientes de zonas horarias por definición. Son un formato válido de almacenamiento y especialmente útiles en registros, APIs y cachés donde deseas un número único y sin ambigüedad.
La cuestión: los timestamps Unix no llevan la intención original del usuario
. Si alguien reserva un vuelo para “mañana temprano en Londres” y almacenas solo un timestamp Unix, has perdido el hecho de que quería Londres. Cuando el vuelo se reprograma tres meses después y Londres cambia de horario de verano, podrías mostrar la hora local incorrecta. Almacena la zona junto al timestamp cada vez que la intención del usuario importe.
El caso más difícil: eventos recurrentes Los eventos recurrentes — reuniones semanales, ciclos mensuales de facturación, recordatorios diarios — exponen cada caso límite de zonas horarias al mismo tiempo.Considera una regla “cada lunes a las 9 de la mañana” para un usuario en Los Ángeles. ¿Almacenas las 9 de la mañana en Pacífico? ¿Qué pasa cuando viaja a Tokio? ¿Qué pasa cuando los relojes avancen y ese lunes caiga en la fecha de transición?
El modelo correcto es:
Almacena la regla de recurrencia como hora de reloj + nombre de zona IANA: “lunes 09:00 America/Los_Angeles”
Calcula la próxima ocurrencia en UTC en el momento de programar, no al momento de crear la regla
Recalcula tras cualquier transición de horario de verano que ocurra dentro del intervalo de recurrencia
- Las bibliotecas como
- (JS) y
- (Python) manejan esto correctamente cuando se les proporciona el contexto de zona horaria. Implementar manualmente esta lógica es un camino fiable hacia errores sutiles que aparecen meses después.
Las zonas horarias son una mentira (y cómo manejarlas en el código) 2 rrule.js Las zonas horarias son una mentira (y cómo manejarlas en el código) 1 dateutil.rrule (Python) manejar esto correctamente cuando se proporciona un contexto adecuado de zona horaria. Implementar manualmente esta lógica es un camino confiable para errores sutiles que aparecen meses después.
También te puede interesar
Instalar extensiones
Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas
恵 ¡El marcador ha llegado!
Marcador es una forma divertida de llevar un registro de tus juegos, todos los datos se almacenan en tu navegador. ¡Próximamente habrá más funciones!
Herramientas clave
Ver todo Los recién llegados
Ver todoActualizar: Nuestro última herramienta fue agregado el 18 de mayo de 26
