Marcas de tiempo, zonas horarias y la hora que ocurre dos veces

Qué cuenta en realidad la hora Unix, en qué se diferencian los desfases de ISO 8601 de las zonas horarias de IANA, qué le hace el horario de verano a las alarmas y a las duraciones, y dónde guardar en UTC es la respuesta equivocada.

Una marca de tiempo responde a una de dos preguntas, y la mayoría de los fallos con fechas vienen de confundirlas. Nombra o bien un instante, un punto de la línea temporal en el que coinciden todos los observadores, o bien una lectura de reloj de pared, que no significa nada hasta que sabes de qué pared. Un pago ocurrió en un instante; una cita con el dentista es una lectura de reloj de pared. Guarda una como si fuera la otra y se romperá más adelante, exactamente en una hora, un domingo por la mañana.

La hora Unix cuenta, no da la hora

La hora Unix es el número de segundos transcurridos desde 1970-01-01 00:00:00 UTC. Ese UTC dice dónde empezó la cuenta; el valor en sí no lleva ninguna zona horaria. El mismo instante es el mismo número en Ámsterdam, Auckland y Lima.

El problema es la unidad, así que cuenta los dígitos.

DígitosEjemploUnidad
101756080000segundos
131756080000000milisegundos

Cualquier fecha actual tiene 10 dígitos en segundos y 13 en milisegundos, y así seguirá hasta 2286. Las dos malas lecturas son escandalosas: unos milisegundos leídos como segundos caen en el año 57626, y unos segundos leídos como milisegundos caen en enero de 1970. Un conversor de marcas de tiempo que muestre la fecha interpretada lo deja claro.

El otro límite es el contenedor. Un contador de segundos de 32 bits con signo termina en 2147483647, que son las 03:14:07 UTC del 19 de enero de 2038, y después da la vuelta a negativo y se lee como diciembre de 1901. Cualquier cosa que guarde un vencimiento a 30 años ya calcula más allá de esa fecha. Con 32 bits sin signo se llega a 2106, pero se pierden todas las fechas anteriores a 1970; con 64 bits no hay techo práctico.

ISO 8601 y RFC 3339

La forma que vale la pena memorizar es 2026-08-25T14:30:00Z: fecha, una T, hora y después un designador de zona. El RFC 3339 es un perfil más estricto de ISO 8601 para protocolos de internet, y exige ese designador. La propia ISO 8601 también admite fechas por semana, una forma sin separadores como 20260825T143000Z y marcas de tiempo sin desfase, así que una API que promete «ISO 8601» normalmente se refiere al subconjunto del RFC 3339.

Z significa un desfase de cero. +02:00 significa que el reloj local va dos horas por delante de UTC, así que el UTC se obtiene restando; invertir eso es un error habitual de dos horas.

CadenaQué nombra
2026-08-25T14:30:00ZUn instante, sin ambigüedad
2026-08-25T16:30:00+02:00El mismo instante, en un reloj que va dos horas por delante
2026-08-25T14:30:00Una lectura de reloj de pared, sin instante
2026-08-25Una fecha, de 23, 24 o 25 horas de duración

Una cadena que no lleva ni Z ni un desfase es ambigua, y los analizadores no se ponen de acuerdo: unos asumen UTC y otros la zona del sistema. Por eso un valor que es solo fecha, como un cumpleaños, se muestra a menudo un día antes al oeste de Greenwich.

Un desfase no es una zona horaria

+02:00 es un hecho sobre un instante en un lugar. Europe/Amsterdam es un conjunto de reglas: +01:00 en invierno, +02:00 en verano y otra forma distinta en décadas anteriores. Guardar el desfase conserva la respuesta para el instante en el que se midió y renuncia a poder calcular cualquier otra.

Los desfases tampoco son únicos. Una tarde de verano, +02:00 abarca Ámsterdam, Lagos y Johannesburgo, así que solo con el desfase no hay camino de vuelta a las reglas ni manera de saber qué marcará ese reloj el próximo diciembre. Las abreviaturas son todavía peores, porque CST nombra tres zonas distintas. Guarda el identificador de IANA en la forma Area/Location.

La hora que no existe y la hora que ocurre dos veces

En toda la UE los relojes se adelantan el último domingo de marzo y se atrasan el último domingo de octubre. En Ámsterdam, en primavera las 02:00 pasan a ser las 03:00, así que ninguna hora local entre las 02:00:00 y las 02:59:59 ocurre ese día. En otoño las 03:00 pasan a ser las 02:00, así que las 02:30 ocurren dos veces, una en +02:00 y otra una hora más tarde en +01:00.

  • Alarmas recurrentes. Una tarea programada para las 02:30 locales no tiene hora válida el domingo de primavera. Los planificadores difieren: la saltan, la desplazan a las 03:00 o la desplazan a la 01:30. El domingo de otoño esa misma tarea puede dispararse una vez o dos, así que comprueba las siguientes ejecuciones con un analizador de cron.
  • Archivos de registro. Una hora de marcas de tiempo locales sin desfase no se puede ordenar, y dos eventos separados por una hora llevan un texto idéntico. Registra en UTC, o pon el desfase en cada línea.
  • Duraciones. De las 20:00 del 24 de octubre de 2026 a las 20:00 del día siguiente, en hora local de Ámsterdam, hay 25 horas. «Sumar un día» y «sumar 24 horas» son operaciones distintas, y una diferencia de fechas en días naturales responde a otra pregunta que un conversor de duraciones trabajando en horas.

Las reglas cambian, así que el almacenamiento tiene que sobrevivirlas

La base de datos de zonas horarias de IANA, tzdata, guarda estas reglas y se publica varias veces al año, porque las reglas son legislación: los gobiernos abolen el horario de verano, lo adoptan o mueven las fechas del cambio, a veces con semanas de aviso. Una imagen de contenedor fijada, un navegador y el entorno de ejecución de un lenguaje llevan cada uno su propia copia y pueden discrepar dentro de una misma máquina. Convertir una hora local futura en un instante es una predicción, que solo queda cerrada cuando llega el momento.

Tipo de valorQué guardarMotivo
Algo que ya ocurrióEl instante en UTCEl pasado no se puede volver a legislar
Cita futura en un reloj de paredFecha y hora locales más el identificador de zona de IANALa intención es la lectura del reloj
Instante futuro, fijo en todas las zonasUTCLa intención es el instante
Fecha sin horaFecha simple, sin zona y sin medianocheAdjuntarle una zona hace que se desplace

Por eso «guarda simplemente UTC» es incorrecto para una reunión del año que viene. Convierte hoy a UTC las 09:00 del 30 de marzo de 2027 en Europe/Amsterdam y la respuesta es 07:00Z. Si la fecha del cambio se mueve antes de entonces, la reunión sigue siendo a las 09:00 locales, pero 07:00Z se muestra ahora como las 08:00: el valor guardado congeló una predicción y descartó la intención. Mantén como fuente de verdad la hora local más la zona; cualquier columna UTC que haya al lado es una caché.

Mostrar fechas a las personas

03/04 no se puede desambiguar a partir de la cadena. Para casi todo el mundo es el 3 de abril y en Estados Unidos es el 4 de marzo; 03/04/05 añade una tercera incógnita. Escribe el mes con letras cuando la salida sea para una persona (4 Apr 2026) y usa ISO para el intercambio entre máquinas. Una etiqueta de marca de tiempo de Discord esquiva esto en el chat porque se muestra en la zona y en el idioma de quien la lee: guarda un valor sin ambigüedad y da formato en el borde.

Los números de semana tienen su propia regla. Las semanas ISO empiezan en lunes, y la semana 1 es la que contiene el primer jueves de enero, o equivalentemente la que contiene el 4 de enero. Así que el 1 de enero puede caer en la semana 52 o 53 del año ISO anterior, y el 29 de diciembre puede caer en la semana 1 del siguiente. Una convención habitual en Norteamérica numera en cambio desde la semana que empieza en domingo y contiene el 1 de enero, así que una misma fecha recibe dos números, y conviene contrastar la semana ISO de una calculadora de días de la semana con una hoja de cálculo.

Duraciones, meses y años bisiestos

«Un mes después» no es un número fijo de días; son 28, 29, 30 o 31. Sumar un mes al 31 de enero no tiene respuesta correcta, y las bibliotecas suelen recortar hasta el final de febrero, lo que hace que la operación no sea reversible: suma un mes, réstalo otra vez, y el 31 de enero se ha convertido en el 28 de enero. El redondeo es la misma trampa en miniatura: truncar una hora al cuarto de hora más cercano y redondearla a él se diferencian hasta en un intervalo completo.

La regla del año bisiesto al completo: un año es bisiesto si es divisible entre 4, salvo que los años divisibles entre 100 no lo son, a menos que también sean divisibles entre 400. Así que 1900 no lo fue, 2000 sí y 2100 no lo será, lo que da un año medio de 365,2425 días. El código que comprueba solo la divisibilidad entre 4 es correcto para todos los años que cualquiera puede recordar, que es por lo que sobrevive hasta 2100. Un aniversario del 29 de febrero es la molestia relacionada: el año siguiente ofrece el 28 de febrero y el 1 de marzo, y ninguna regla elige entre los dos, así que elige uno a propósito.