¿Odias los anuncios? Ir Sin publicidad Hoy

Los horarios de zona son una mentira (y cómo manejarlos en el código)

Actualizado en

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.

Las zonas horarias son una mentira (y cómo manejarlas en el código) 1
ANUNCIO · ¿ELIMINAR?

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. TIMESTAMP MySQL:
  • es ingenuo (sin zona). Usa El DATETIME si 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 en TIMESTAMP es a menudo más seguro. VARCHAR SQLite: BIGINT No 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”.

  1. Usa nombres de zonas IANA, no desplazamientos. Almacena
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

¿Quieres eliminar publicidad? Adiós publicidad hoy

Instalar extensiones

Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas

añadir Extensión de Chrome añadir Extensión de borde añadir Extensión de Firefox añadir Extensión de Opera

¡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!

ANUNCIO · ¿ELIMINAR?
ANUNCIO · ¿ELIMINAR?
ANUNCIO · ¿ELIMINAR?

Noticias Aspectos técnicos clave

Involucrarse

Ayúdanos a seguir brindando valiosas herramientas gratuitas

Invítame a un café
ANUNCIO · ¿ELIMINAR?