Marcas de tiempo Unix, segundos intercalares y el problema de 2038
Publicado el 20/5/2025 · 18 min de lectura · Herramientas para desarrolladores
Daniel Okonkwo — Desarrollador front-end y redactor de Tecnología en OneKitly
Rendimiento web · Formatos de archivo
Verificado con 7 fuentes
El tiempo Unix es el número de segundos desde 1970-01-01T00:00:00Z, calculado como si cada día contuviera exactamente 86 400 segundos. Los segundos intercalares quedan excluidos, así que no es un recuento de segundos físicos transcurridos: 86 400 segundos Unix separan 2016-12-31T00:00:00Z de 2017-01-01T00:00:00Z aunque de verdad pasaran 86 401 segundos SI, porque UTC insertó 23:59:60 entre medias. Ese es el trato deliberado: el contador pierde el acuerdo con la física y gana la propiedad de que dividir entre 86 400 siempre da la fecha UTC correcta, sin ninguna tabla de segundos intercalares. Se han insertado veintisiete segundos intercalares desde 1972, el último el 2016-12-31, y un reloj POSIX no puede representar :60, así que repite o congela un segundo. El problema de 2038 es aparte: un contador de 32 bits con signo tope en 2 147 483 647, que es 2038-01-19T03:14:07Z. Un segundo después salta a −2 147 483 648, o 1901-12-13T20:45:52Z. Un time_t de 64 bits llega a 9 223 372 036 854 775 807 segundos, unos 292 000 millones de años, y cierra el asunto para siempre. El fallo más común no es 2038 sino confundir segundos con milisegundos: para 2026-08-15, el valor en segundos leído como milisegundos da 1970-01-21 y el valor en milisegundos leído como segundos da el año 58589.
El tiempo Unix cuenta segundos desde 1970 ignorando deliberadamente los segundos intercalares, por lo que no es un recuento de segundos físicos transcurridos. Esto es lo que compra ese trato, lo que un segundo intercalar le hace al contador, el instante exacto en que desborda un contador de 32 bits con signo, y el fallo del factor 1000 que te deja en 1970 o en el año 58589.
Qué cuenta realmente el contador
El tiempo Unix, o tiempo POSIX, se define como el número de segundos transcurridos desde 1970-01-01T00:00:00Z bajo el supuesto de que cada día contiene exactamente 86 400 segundos. La definición es aritmética, no observacional: la especificación da una fórmula construida a partir del año, el día del año, la hora, el minuto y el segundo, y esa fórmula no tiene ningún término para segundos intercalares, porque dentro de ella no existen.
La consecuencia merece decirse sin rodeos, porque ahí empieza casi toda la confusión: una marca de tiempo Unix no es un recuento de segundos físicos. Es una representación comprimida de una fecha y hora del calendario UTC. Dos marcas separadas por 86 400 están exactamente a un día UTC de distancia por construcción, siempre, sea lo que sea que midiera un reloj de cesio en ese intervalo.
Un matiz, porque la propiedad se exagera a menudo. Se cumple en UTC, no en hora local. Medido sobre datos reales de zonas horarias, de medianoche local a medianoche local en Europe/Paris hubo 82 800 segundos Unix el 2026-03-29 y 90 000 el 2026-10-25, frente a 86 400 en un día corriente; America/New_York dio 82 800 el 2026-03-08. El horario de verano no cambia el contador — cambia cuántos tics caben en un día natural local, que es justo por lo que las marcas de tiempo deben guardarse como instantes UTC y convertirse solo al mostrarlas.
El trato: sin segundos intercalares, sin tabla
Excluir los segundos intercalares parece un defecto hasta que se mira la alternativa. Si el contador siguiera segundos físicos, convertir una marca en fecha exigiría conocer cada segundo intercalar insertado entre la época y esa marca. Los segundos intercalares los anuncia el IERS con unos meses de antelación, a partir de mediciones de la rotación terrestre, así que esa tabla no se puede calcular, solo distribuir. Cada dispositivo la necesitaría, al día, y cualquiera con una tabla caducada calcularía una fecha distinta a partir del mismo número.
El diseño eligió determinismo antes que exactitud física, y la recompensa es que la aritmética de marcas funciona en todas partes sin estado compartido. Un teléfono que nunca ha tenido conexión calcula exactamente la misma fecha a partir de 1786752000 que un reloj de centro de datos. Dividir entre 86 400 es correcto. Sumar un día es sumar 86 400. Ordenar por marca ordena por tiempo. Nada de eso sobreviviría a una definición en segundos físicos.
El precio se paga en un solo sitio: todo cálculo de duración física que cruce un segundo intercalar se equivoca en el número de segundos intercalares cruzados. Entre 2016-12-31T00:00:00Z y 2017-01-01T00:00:00Z la diferencia Unix es 86 400, mientras que de verdad transcurrieron 86 401 segundos SI. Para casi cualquier aplicación ese error es irrelevante. Para telemetría de satélites, secuenciación de operaciones financieras con granularidad de menos de un segundo y metrología física no lo es, y esos campos usan TAI o tiempo GPS — escalas monótonas sin ningún segundo intercalar.
Qué le hace un segundo intercalar al contador
El UTC inserta un segundo intercalar permitiendo que un minuto contenga 61 segundos, con el segundo extra etiquetado 23:59:60. Se han insertado veintisiete desde 1972 — TAI menos UTC valía 10 segundos el 1972-01-01 y vale 37 desde el 2017-01-01, y la diferencia es exactamente la cuenta. Todos fueron positivos; un segundo intercalar negativo, que quite un segundo, lo permite la norma y nunca se ha usado.
Un reloj POSIX no tiene representación para el segundo numerado 60, así que algo tiene que ceder. El comportamiento clásico es repetir: el contador emite dos veces el mismo valor, un segundo no es monótono, y todo código que suponga marcas estrictamente crecientes ve un duplicado. Algunos sistemas congelan el contador un segundo en su lugar. Ambos son visibles para las aplicaciones, y ambos han causado caídas reales en sistemas que trataban una marca como clave única o como número de secuencia estrictamente creciente.
La respuesta pragmática a la que convergieron los grandes operadores es el suavizado: repartir el segundo extra en una ventana de horas para que ningún reloj repita ni se detenga, a cambio de que todos los relojes de la ventana estén ligeramente mal. Esto importa menos cada año, porque en noviembre de 2022 la 27.ª Conferencia General de Pesas y Medidas resolvió dejar de insertar segundos intercalares en 2035 o antes, permitiendo que el UTC se aparte del tiempo solar más allá del límite actual. El mecanismo aún no está decidido, pero la dirección sí: el segundo intercalar se jubila.
2038, calculado con exactitud
Un entero de 32 bits con signo va de −2 147 483 648 a 2 147 483 647. Interpretado como segundos desde la época, el máximo es 2038-01-19T03:14:07Z. Guardar 2 147 483 648 en una casilla real de 32 bits con signo y releerla da −2 147 483 648, que es 1901-12-13T20:45:52Z. Guardar 2 147 483 649 da 1901-12-13T20:45:53Z. El fallo no es una caída ni un error: es una fecha 136 años en el pasado, entregada en silencio y usada.
Dos hechos vecinos merecen conocerse. Un contador de 32 bits sin signo alcanza 4 294 967 295, que es 2106-02-07T06:28:15Z — un apaño común en firmware embebido, que solo aplaza el problema y además hace irrepresentables las fechas anteriores a 1970. Y un time_t de 64 bits con signo alcanza 9 223 372 036 854 775 807 segundos, unos 292 000 millones de años, alrededor de veintiuna veces la edad actual del universo. Eso no es un aplazamiento: es una corrección permanente.
La fecha que preocupa no es 2038 sino hoy, porque los primeros sistemas en romperse son los que calculan instantes futuros. Desde el 2026-08-15 el desbordamiento está a 360 731 647 segundos — 4 175 días, o 11,43 años. Un horizonte de diez años desde esa fecha acaba el 2036-08-14 y aún cabe. Uno de quince acaba el 2041-08-14 y no cabe. Todo lo que guarde un vencimiento, un cuadro de amortización, una ventana de retención o una valide de certificado de más de unos once años y medio ya produce valores que un campo de 32 bits no puede contener.
Qué está y qué no está en riesgo en 2026
Los sistemas operativos de 64 bits mayoritarios están bien, y desde hace años. En la máquina usada para este artículo, sizeof(time_t) vale 8 bytes y la orden date -u -r 2147483648 imprime Tue Jan 19 03:14:08 UTC 2038 sin desbordamiento alguno. Lo mismo vale para cualquier Linux, macOS y Windows de 64 bits actual. JavaScript nunca estuvo expuesto: un Date guarda un recuento float64 de milisegundos y la especificación acota su alcance a más o menos 8 640 000 000 000 000 milisegundos, que va de −271821-04-20 a +275760-09-13.
La exposición que queda es estrecha pero real, y está sobre todo en los campos, no en las CPU. Los tipos de columna con rango de 32 bits documentado son los más comunes: el TIMESTAMP de MySQL está especificado para acabar el 2038-01-19 03:14:07 UTC, mientras que su tipo DATETIME no se ve afectado. Los formatos de red y de archivo que especifican un campo de 32 bits no se pueden ensanchar sin subir de versión. El firmware embebido en microcontroladores de 32 bits usa a menudo un contador de 32 bits a propósito, por memoria. Y cualquier base de código que guarde un segundo de época en un tipo explícitamente de 32 bits — una columna int32, un registro binario de ancho fijo, una struct de C compilada para un objetivo de 32 bits — lleva el límite sea cual sea el sistema operativo debajo.
La auditoría práctica es corta. Busca columnas int32 e INTEGER que guarden segundos de época; comprueba el rango documentado de cada tipo de columna temporal que uses; localiza registros binarios de ancho fijo y cualquier struct compilada para un objetivo de 32 bits; y prueba con el valor 2147483648 en vez de esperar. Si un campo no se puede ensanchar, guardar una cadena ISO 8601 o un recuento de milisegundos de 64 bits son alternativas válidas, y ambas cuestan más bytes de los que ahorran discusiones.
Segundos o milisegundos: el fallo del factor 1000
Es con diferencia el fallo de marcas más común, y es puramente un problema de unidades. Las herramientas Unix, la mayoría de las API y la definición POSIX usan segundos. JavaScript, Java y muchísimas API web usan milisegundos. Ambos son el mismo número escalado por 1 000, y ninguno lleva etiqueta en el formato de transporte, así que un desajuste es invisible hasta que se muestra una fecha.
Ambos sentidos producen un resultado absurdo, y esa es la buena noticia. Toma 2026-08-15T00:00:00Z: en segundos es 1786752000, en milisegundos 1786752000000. Da el valor en segundos a algo que espera milisegundos y obtienes 1970-01-21T16:19:12Z — tres semanas después de la época, porque 1790 millones de milisegundos son solo unos veinte días. Da el valor en milisegundos a algo que espera segundos y obtienes el año 58589, en concreto +058589-12-01T00:00:00Z. Ambos están tan lejos de lo plausible que una sola comprobación los caza.
La heurística que funciona: una marca actual en segundos tiene diez dígitos, y en milisegundos trece. Diez dígitos seguirán siendo correctos hasta 2286. Mejor que una heurística es nombrar el campo para que la unidad sea imposible de malinterpretar — expiresAtSeconds en vez de expiresAt — o pasar una cadena RFC 3339 por la frontera y analizarla al llegar, lo que es autodescriptivo y cuesta unas decenas de bytes.
Guardar el tiempo para que sobreviva
Guarda el instante, no la representación. Un instante es un punto de la línea temporal y queda totalmente especificado por una marca UTC con anchura suficiente — un segundo de época de 64 bits, un recuento de milisegundos de 64 bits, o una cadena RFC 3339 terminada en Z. Una representación es lo que vería una persona en un lugar concreto, y depende de reglas de zona horaria que los gobiernos cambian con pocas semanas de aviso. Guardar la representación es guardar una respuesta que puede volverse falsa retroactivamente.
Una excepción merece nombrarse, porque guardar en UTC se vende a menudo como consejo universal. Una cita futura en un lugar con nombre no es un instante: es una hora de reloj de pared en una jurisdicción, y si esa jurisdicción mueve sus relojes, el instante correcto cambia. Una reunión a las 09:00 en Berlín el próximo noviembre debe guardarse como fecha local, hora local e identificador de zona IANA, y resolverse a instante solo cuando haga falta. Guarda UTC para lo que ocurrió, y hora local más nombre de zona para lo que está programado.
Más allá, tres hábitos quitan casi todo el dolor restante. Da a cada campo de marca una unidad en su nombre para que un lector nunca tenga que adivinar entre segundos y milisegundos. Usa un tipo de 64 bits en todas partes, incluida la columna de base de datos, para que 2038 sea una curiosidad histórica y no un plazo. Y nunca trates una marca como identificador único ni número de secuencia monótono, porque el manejo de segundos intercalares, las correcciones de reloj y las migraciones de máquinas virtuales pueden hacer que el mismo valor aparezca dos veces o que el tiempo retroceda un momento.
| Representación | Unidad | Más antigua | Más reciente | Dónde se sigue encontrando |
|---|---|---|---|---|
| time_t de 32 bits con signo | Segundos | 1901-12-13T20:45:52Z | 2038-01-19T03:14:07Z | Objetivos embebidos de 32 bits, columnas int32, registros binarios de ancho fijo |
| Contador de 32 bits sin signo | Segundos | 1970-01-01T00:00:00Z | 2106-02-07T06:28:15Z | Apaños de firmware; ninguna fecha anterior a 1970 posible |
| time_t de 64 bits con signo | Segundos | Unos 292 000 millones de años antes de 1970 | Año 292 277 026 596 (2^63 − 1 segundos) | Toda versión actual de 64 bits de Linux, macOS y Windows |
| Date de JavaScript | Milisegundos (float64) | −271821-04-20 | +275760-09-13 (±8 640 000 000 000 000 ms) | Navegadores y Node; nunca tuvieron problema de 2038 |
| TIMESTAMP de MySQL | Segundos | 1970-01-01 00:00:01 UTC | 2038-01-19 03:14:07 UTC | Muy desplegado; DATETIME es la alternativa no afectada |
| Cadena RFC 3339 | Texto, autodescriptivo | Sin cota inferior en el formato | Sin cota superior en el formato | API y registros; cuesta bytes, elimina la ambigüedad segundos/milisegundos |
Preguntas frecuentes
- ¿Una marca de tiempo Unix está en UTC o en mi zona local?
- No es ninguna de las dos, en rigor, y esa es la forma útil de pensarlo. Una marca Unix identifica un punto de la línea temporal. No lleva zona horaria alguna, porque no la necesita — el número 1786752000 se refiere al mismo momento en toda la Tierra. Lo cierto es que convertirla a fecha legible exige una zona, y que la época está anclada en 1970-01-01T00:00:00 UTC, así que convertir sin zona indicada te da UTC. Por eso una marca es lo correcto para guardar y transmitir y lo incorrecto para mostrar: la capa de almacenamiento necesita un instante inequívoco, y la de presentación necesita zona, configuración regional y calendario. El fallo habitual es convertir a hora local en mitad de una tubería y luego guardar el resultado, lo que fija la zona de una máquina y desplaza en silencio cada valor por su desfase. Convierte una vez, en el último momento posible, en la interfaz.
- ¿Qué pasa exactamente a las 03:14:07 del 19 de enero de 2038?
- En cualquier sistema que guarde la marca en un entero de 32 bits con signo, el contador llega a 2 147 483 647 y el siguiente incremento desborda. Simulado en una casilla real de 32 bits con signo, guardar 2 147 483 648 se relee como −2 147 483 648, que convierte a 1901-12-13T20:45:52Z. El comportamiento no es una excepción ni una caída: el valor simplemente pasa a ser una fecha 136 años en el pasado y se usa. Lo que eso provoque depende del código de encima. Las ordenaciones se invierten. Los cálculos de edad y duración se vuelven enormemente negativos. Certificados y sesiones parecen caducados hace mucho, o no caducar nunca. Los planificadores tipo cron disparan sin parar o se paran. Los registros caen en la partición equivocada. El peligro viene justo de que nada lanza un error: cada capa recibe un número bien formado y se comporta correctamente para el número que le dieron. Por eso también probarlo es fácil — pon 2147483648 en un campo hoy y reléelo, en vez de esperar a la fecha.
- Mis servidores son de 64 bits. ¿Estoy a salvo del problema de 2038?
- Tu sistema operativo lo está, tu aplicación quizá no. En una máquina moderna de 64 bits sizeof(time_t) vale 8 bytes — comprobado aquí — y el intérprete imprime Tue Jan 19 03:14:08 UTC 2038 para el valor 2147483648, sin desbordar. Pero el núcleo rara vez es donde vive el límite. La exposición está en los campos que elegiste: una columna INTEGER o int32 con segundos de época, un protocolo o formato de archivo con campo de marca fijo de 32 bits, una struct compilada para un objetivo embebido de 32 bits, un registro binario de ancho fijo escrito hace años. El tipo de columna TIMESTAMP de MySQL es un ejemplo documentado, especificado para acabar el 2038-01-19 03:14:07 UTC con independencia de cuántos bits tenga el servidor, mientras que su tipo DATETIME no se ve afectado. Una segunda exposición, menos obvia, es cualquier dispositivo de terceros del parque — impresoras, cámaras, controladores, sensores — cuyo firmware es de 32 bits por diseño y quizá nunca se actualice. La auditoría conviene hacerla ahora y no en 2037, porque los fallos empiezan por los valores futuros y 2026 ya está dentro de la ventana de once años y medio.
- ¿Cómo sé si un número está en segundos o en milisegundos?
- Cuenta los dígitos. Una marca actual en segundos tiene diez dígitos y los seguirá teniendo hasta 2286; el mismo instante en milisegundos tiene trece. Para 2026-08-15T00:00:00Z los dos valores son 1786752000 y 1786752000000. Si dudas, convierte y mira el resultado, porque ambos errores producen algo obviamente absurdo: leer el valor en segundos como milisegundos da 1970-01-21T16:19:12Z, tres semanas después de la época, y leer el valor en milisegundos como segundos da el año 58589. Una fecha de enero de 1970 o de un futuro lejano casi siempre es este fallo y no datos malos. El arreglo duradero no es detectar sino nombrar. Llama al campo expiresAtSeconds o createdAtMillis para que la unidad viaje con el valor, o envía una cadena RFC 3339 como 2026-08-15T00:00:00Z entre servicios — es autodescriptiva, se ordena bien como texto, sobrevive a un pegado en un registro y cuesta unos veinte bytes.
- ¿Los segundos intercalares hacen que mis cálculos de duración estén mal?
- Técnicamente sí, y en la práctica casi nunca lo bastante para importar. Restar dos marcas Unix da la diferencia del contador, que omite todo segundo intercalar intermedio. Alrededor del segundo intercalar de 2016, el contador dice que pasaron 86 400 segundos entre 2016-12-31T00:00:00Z y 2017-01-01T00:00:00Z, mientras transcurrieron 86 401 segundos SI. Como solo se han insertado veintisiete segundos intercalares, el error máximo posible en una duración que abarque todo el periodo desde 1972 es de veintisiete segundos — irrelevante para facturación, duración de sesión, caducidad de caché, latencia al milisegundo o cualquier medida de negocio. Sí importa en telemetría de satélites, física de alta precisión y secuenciación financiera por debajo del segundo, y esos campos usan TAI o tiempo GPS, sin ningún segundo intercalar. Hay, sin embargo, un peligro práctico mucho mayor: medir tiempo transcurrido con el reloj de pared. Correcciones NTP, migraciones de máquinas virtuales y cambios manuales pueden mover un reloj de pared bastante más de un segundo, adelante o atrás. Para medir duraciones usa un reloj monótono — el que tu lenguaje expone como algo tipo performance.now o un reloj estable — y reserva las marcas de reloj de pared para registrar cuándo pasó algo.
- ¿Debo guardar las marcas en UTC o con zona horaria?
- Depende de si registras algo que ocurrió o programas algo que ocurrirá. Para lo pasado — una línea de registro, un pedido, un pago, un asiento de auditoría — guarda el instante en UTC y convierte solo al mostrar. El instante es un hecho y no cambia nunca; cómo se representa en la hora local de un usuario es un asunto de presentación que se puede recalcular en cualquier momento. Para lo programado en el futuro, UTC es la elección equivocada, y es el caso que más se falla. Una reunión a las 09:00 en Berlín el próximo noviembre no es un instante: es una hora de reloj de pared en una jurisdicción. Si Alemania cambia sus reglas horarias de aquí a entonces, el instante correcto se mueve, y un valor UTC guardado hoy se convertiría en una cita a la hora local equivocada. Guarda la fecha local, la hora local y el identificador de zona IANA como Europe/Berlin, y resuelve a instante cuando lo necesites. Los gobiernos sí cambian las reglas de zona, normalmente con pocas semanas de aviso, y la base tz se actualiza varias veces al año para seguirlas.
Artículos que podrían interesarte
Todas las guías →Herramientas relacionadas
Fuentes
- The Open Group / IEEE — POSIX Base Specifications — Seconds Since the Epoch (the formula that excludes leap seconds)
- IETF — RFC 3339 — Date and Time on the Internet: Timestamps
- IERS — International Earth Rotation and Reference Systems Service — Bulletin C, leap second announcements and the TAI−UTC value
- BIPM — 27th General Conference on Weights and Measures (2022), Resolution 4 on the future of the leap second
- ITU — Recommendation ITU-R TF.460 — Standard-frequency and time-signal emissions, the definition of UTC and 23:59:60
- Oracle — MySQL Reference Manual — The DATE, DATETIME, and TIMESTAMP Types (TIMESTAMP ends 2038-01-19 03:14:07 UTC)
- MDN Web Docs — Date — the ±8,640,000,000,000,000 millisecond range of a JavaScript Date
¿Has detectado un error en este artículo?