Fechas y hora
Unix timestamps — Segundos, milisegundos, Año 2038 y qué vigilar
Los Unix timestamps parecen simples y se rompen de forma sorprendente. Segundos vs milisegundos, el problema del 2038, y las reglas para que la aritmética no se coma tu app.
El tiempo Unix es lo más parecido a un reloj universal que tiene la computación. También es origen de algunos de los bugs más tozudos en pipelines de datos, porque cada lenguaje, BD y API traza la línea en un sitio ligeramente distinto. Esta guía cubre qué es Unix time, las tres trampas que causan la mayoría de bugs, y las reglas para mantener limpia la aritmética de tiempo.
Qué es un Unix timestamp en realidad
Un Unix timestamp es el número de segundos transcurridos desde 1970-01-01 00:00:00 UTC, sin contar leap seconds. Es un escalar. No lleva zona horaria porque siempre es UTC por definición.
0 → 1970-01-01 00:00:00 UTC
1000000000 → 2001-09-09 01:46:40 UTC (el "Billenium")
1700000000 → 2023-11-14 22:13:20 UTC
2147483647 → 2038-01-19 03:14:07 UTC (máx int 32-bit firmado)
Al ser un escalar en UTC, dos timestamps se comparan con <, se restan para obtener duración, y se guardan en cualquier columna entera sin anotación de zona. Ahí está todo el atractivo.
Para conversión interactiva entre timestamps y fechas legibles, el timestamp converter es lo más rápido; para renderizar un instante UTC en una zona concreta, el timezone converter hace esa conversión.
Segundos vs milisegundos — el problema de unidades
Stacks distintos tienen defaults distintos. Es el bug más común con Unix timestamps.
| Unidad | Dígitos (hoy) | Suele vivir en |
|---|---|---|
| Segundos | 10 | Unix date +%s, Python time.time() (truncado), epoch Postgres, kernel Linux |
| Milisegundos | 13 | JavaScript Date.now(), Java System.currentTimeMillis(), la mayoría de APIs móviles |
| Microsegundos | 16 | Python datetime.timestamp() * 1_000_000, Go time.UnixMicro |
| Nanosegundos | 19 | Go time.UnixNano, Linux clock_gettime(CLOCK_REALTIME) |
Truco de detección rápido: un timestamp de “ahora” está sobre 1.76e9 en segundos. Si tu valor es cuatro órdenes de magnitud mayor, está en milisegundos. Tres más, microsegundos. Seis más, nanosegundos.
// Detectar unidades por magnitud
function detectUnit(ts) {
if (ts < 1e11) return "seconds"; // ~año 5138
if (ts < 1e14) return "milliseconds"; // ~año 5138
if (ts < 1e17) return "microseconds";
return "nanoseconds";
}
// Normaliza cualquier cosa a milisegundos
function toMillis(ts) {
if (ts < 1e11) return ts * 1000;
if (ts < 1e14) return ts;
if (ts < 1e17) return Math.floor(ts / 1000);
return Math.floor(ts / 1_000_000);
}
Elige una unidad por API y documéntala. Si construyes una API pública, los números JSON solo garantizan precisión de entero hasta 2^53 - 1, así que los nanosegundos en JSON deberían ser strings, no números.
El problema del Año 2038
Un entero 32-bit firmado tope en 2.147.483.647. Interpretado como segundos desde 1970, eso es 2038-01-19 03:14:07 UTC. Un segundo después, un time_t de 32 bits ingenuo envuelve a -2.147.483.648, que es 1901-12-13 20:45:52 UTC.
Fue el equivalente Y2K del mundo Unix. En la mayoría de sistemas modernos ya está arreglado:
- Los sistemas operativos 64-bit usan
time_tde 64 bits desde aproximadamente mediados de los 2000 para userland Linux, 2017 para Windows. - Los sistemas Linux 32-bit obtuvieron
time_tde 64 bits en glibc 2.34 (2021) y kernel 5.6+ para nuevas syscalls. - Los sistemas embebidos son el riesgo restante. Controladores industriales, routers antiguos, dispositivos IoT con vida de 20 años seguirán fallando en 2038.
Dos consecuencias prácticas hoy:
- No diseñes sistemas nuevos alrededor de
time_tde 32 bits. Usa enteros 64-bit, o milisegundos, o strings ISO 8601. - Si importas datos de sistemas antiguos, chequea timestamps cerca de
2147483647. Un valor truncado o envuelto es la pista.
UTC vs hora local — no seas listo
La regla dura: guarda timestamps como enteros UTC. Renderízalos en la zona local del usuario al mostrar. Nunca guardes hora local. Nunca guardes “hora local más offset” salvo que construyas una app de calendario (y aún así, piénsatelo).
La versión de diez mil palabras de esta regla vive en la guía de manejo de timezones — es la regla más violada en backend.
Corolario rápido: cuando loggees un evento, logguea su Unix timestamp UTC. No 2026-04-17 14:33:11 en alguna zona local, que es ambiguo dos veces al año en transiciones DST.
Leap seconds y por qué la mayoría de apps los ignoran
El tiempo Unix, por definición explícita, no cuenta leap seconds. Un leap second insertado a las 23:59:60 UTC en 31 de diciembre repite el mismo Unix timestamp dos veces, o (según la estrategia de reloj del servidor) el reloj se desliza 1 segundo en una ventana alrededor de medianoche.
Para la mayoría de apps, la respuesta correcta es “no me importa”. Las duraciones menores de un día no se ven afectadas; las mayores han acumulado quizá 27 leap seconds totales desde 1972 (todos positivos), error de redondeo para la mayor parte de la lógica de negocio.
Donde sí importa:
- Sistemas financieros con orden regulatorio de timestamps.
- Experimentos físicos y astronomía.
- Protocolos de red con sincronización sub-segundo.
Si has visto un log con 23:59:60, es un leap second. El International Earth Rotation Service anunció en 2022 que el último leap second se insertará como muy tarde en 2035.
Ordenación, monotonicidad y “ahora”
Tres puntos sutiles:
- Los relojes del sistema pueden ir hacia atrás. NTP desliza y salta. Los contenedores pausan y resumen. Un timestamp que acabas de escribir puede ser “anterior” al escrito hace cinco minutos. Si necesitas IDs estrictamente monotónicos, usa un contador (o un UUID v7 — mira la pieza UUID v4 vs v7), no un timestamp.
- “Ahora” no es un valor único. Entre
const a = Date.now()yconst b = Date.now()el tiempo se movió. Si haces debounce o rate-limiting, calcula “ahora” una vez y reutiliza. - Comparar timestamps de relojes distintos no es seguro. Los sistemas distribuidos no tienen reloj global. Si el cliente manda su timestamp y el servidor tiene el suyo, no los restes tratando el resultado como duración real.
Convenciones de formato que sobreviven
Al serializar un timestamp a texto, tienes dos opciones razonables:
- Un entero en una unidad documentada (segundos o milisegundos), anotada en el nombre del campo:
created_at_ms. - Un string ISO 8601:
2026-04-17T14:33:11Z. LaZ(o+00:00) es obligatoria; un2026-04-17T14:33:11a secas es una hora sin zona y es ambiguo.
Cualquier otra cosa — 14/04/2026, April 17, 2026 2:33 PM, Unix seconds sin unidad en el nombre — es un bug futuro.
# Unix seconds ahora
date +%s
# 1776556391
# ISO 8601 UTC ahora
date -u +%Y-%m-%dT%H:%M:%SZ
# 2026-04-17T14:33:11Z
# Convertir Unix timestamp a ISO local
date -d @1776556391
# Fri Apr 17 16:33:11 CEST 2026
Conclusiones
Los Unix timestamps son segundos UTC (o ms, micros, nanos — chequea tu stack). Detecta la unidad por magnitud, normaliza en el borde, documenta en el nombre del campo. El Año 2038 está casi resuelto en sistemas 64-bit pero real en embebidos. Los leap seconds se pueden ignorar tranquilamente en casi toda app. Nunca guardes hora local. Para conversión interactiva usa el timestamp converter; para display por zona, el timezone converter. Para la filosofía completa de zonas, lee la guía de timezones en backend.