Milisegundos vs segundos: la confusión de unidades que rompe cada app

El bug de timestamp más común es pasar milisegundos donde se esperan segundos, o al revés. Aprende la regla de 10 vs 13 dígitos, los valores por defecto de cada lenguaje, las implicaciones en bases de datos y los patrones de conversión seguros.

Por qué hay dos estándares

El tiempo Unix se definió originalmente en segundos: un entero parecía natural para un sistema que se incrementaba en cada tic de reloj. El objeto Date de JavaScript se introdujo en 1995 y eligió milisegundos para soportar la temporización de eventos por debajo del segundo en el navegador. Muchas bases de datos y lenguajes de backend mantuvieron los segundos Unix como valor por defecto. Hoy ambos estándares coexisten en todo código que cruza la frontera JavaScript-servidor, y por eso un valor puede parecer válido y aun así representar una fecha a decenas de miles de años.

Qué unidad usa cada lenguaje

La forma más segura de recordar la división es: las APIs Date del navegador suelen querer milisegundos, las APIs de servidor al estilo Unix suelen exponer segundos, y las librerías de tiempo más nuevas a menudo ofrecen ambos. No deduzcas la unidad solo por el lenguaje al leer la documentación de una API de terceros; comprueba la descripción del campo y los ejemplos.

  • Milliseconds: JavaScript Date.now(), Java System.currentTimeMillis(), Java Instant.toEpochMilli(), .NET ToUnixTimeMilliseconds()
  • Seconds: Python time.time() (float), PHP time(), Go time.Now().Unix(), Ruby Time.now.to_i, C time(NULL), PostgreSQL EXTRACT(EPOCH)
  • Both: Rust — duration.as_secs() for seconds, duration.as_millis() for milliseconds
  • Python note: time.time() returns a float, so milliseconds are available as int(time.time() * 1000)

Detectar la unidad a partir del valor

Una heurística fiable para fechas modernas: un número de 10 dígitos son segundos; uno de 13 dígitos son milisegundos. Los segundos Unix actuales rondan los 1700–2100 millones (10 dígitos) y no llegarán a 13 dígitos hasta el año 33658. Los milisegundos Unix actuales ya tienen 13 dígitos. La heurística es más fuerte para fechas desde los 2000 hasta dentro de varios miles de años; para fechas históricas, negativas o fixtures de prueba compactos, usa unidades explícitas en vez de adivinar.

  • valor de 10 dígitos → segundos (p. ej. 1700000000 = 2023-11-14 UTC)
  • valor de 13 dígitos → milisegundos (p. ej. 1700000000000 = 2023-11-14 UTC)
  • valor de 16 dígitos → microsegundos (divide entre 1,000,000 para segundos)
  • valor negativo → fecha anterior al 1 de enero de 1970 (segundos o milisegundos)

Patrones de conversión seguros

La conversión en sí es aritmética simple; lo difícil es elegir dónde debe ocurrir. Convierte en el límite del sistema, nombra el valor convertido y evita pasar números crudos ambiguos por varias capas.

  • Seconds to milliseconds: seconds * 1000
  • Milliseconds to whole seconds — JavaScript: Math.floor(ms / 1000)
  • Milliseconds to whole seconds — Python: ms // 1000
  • Milliseconds to whole seconds — Go: ms / 1000 (integer division)
  • Universal guard in JavaScript: const toMs = ts => ts < 1e11 ? ts * 1000 : ts

Cómo aparecen los bugs de unidad en producción

Un bug de segundos-vs-milisegundos suele sobrevivir a la validación porque ambos valores son solo números. Suele aparecer más tarde como una fecha imposible: enero de 1970 en JavaScript cuando se trataron segundos como milisegundos, o un año muy lejano cuando un backend trató milisegundos como segundos.

  • Fecha de 1970 en la UI de JavaScript → se pasaron segundos a new Date() sin multiplicar por 1000
  • Año 50000+ en Python, Go o PHP → se pasaron milisegundos a una API que esperaba segundos
  • Tokens caducados que nunca expiran → el timestamp de caducidad se guardó en la unidad equivocada
  • Entradas de caché que desaparecen al instante → milisegundos divididos dos veces o segundos multiplicados dos veces
  • Gráficos de analítica con rangos vacíos → los límites de la consulta usan una unidad distinta a la de los timestamps de eventos guardados

Convenciones de nombres en APIs y bases de datos

Una pequeña convención de nombres evita la mayoría de estos bugs. No publiques nunca un campo de API llamado timestamp salvo que la documentación sea excepcionalmente clara. Prefiere nombres de campo que incluyan el significado y la unidad.

  • createdAtMs — Unix milliseconds, best for JavaScript clients
  • createdAtSeconds — Unix seconds, common for backend services
  • createdAtIso — ISO 8601 string, useful for human-readable API responses
  • expiresAtUnixSeconds — explicit enough for auth tokens and signed URLs
  • event_time TIMESTAMPTZ — native database time, with conversion handled by the database

Preguntas frecuentes sobre milisegundos vs segundos

¿Un timestamp de 13 dígitos siempre son milisegundos?
Para los timestamps Unix reales modernos, sí: 13 dígitos suelen significar milisegundos. Los timestamps en segundos muy lejanos en el futuro también pueden alcanzar 13 dígitos, así que los sistemas críticos deberían llevar metadatos de unidad explícitos.
¿Debo almacenar segundos o milisegundos?
Almacena la unidad que tu sistema use de forma natural, pero documéntala y mantenla consistente. Los sistemas con mucho JavaScript suelen usar milisegundos; los backends al estilo Unix y muchas bases de datos suelen usar segundos o columnas datetime nativas.
¿Por qué usar Math.floor(ms / 1000) en vez de ms / 1000?
Los segundos Unix suelen ser segundos enteros. Math.floor elimina la parte fraccionaria para que las APIs que esperan segundos enteros no reciban un decimal.
¿Cómo convierto milisegundos a segundos?
Divide entre 1000 y descarta la fracción: Math.floor(ms / 1000) en JavaScript, ms // 1000 en Python, o ms / 1000 con división entera en Go. Para el otro sentido, multiplica los segundos por 1000.