JSON Toolkit

¿Qué formato usar para las marcas de tiempo en JSON?

En resumen: Los números epoch y las cadenas ISO tienen cada uno sus inconvenientes, pero mezclarlos es lo que realmente rompe las cosas. Usa ISO 8601 con desplazamiento, almacena en UTC y muestra en la zona horaria del usuario.

Principiante~3 min de lecturaPublicado el 2026-10-30Actualizado el 2026-10-30
Elegir un formato de marca de tiempo para API JSON

En resumen: usa ISO 8601 con un desplazamiento explícito en el cable, almacena UTC con una Z final y convierte solo al mostrar; si en cambio eliges números epoch, fija segundos frente a milisegundos de forma global y pon la unidad en el nombre del campo.

Por qué 2026-10-30 14:30:00 no basta

Esa cadena no lleva zona. Las 14:30 en Shanghái y las 14:30 en Londres están a ocho horas de distancia, y no hay forma de saber cuál se quiere decir. new Date('2026-10-30 14:30:00') analiza en la zona local del runtime, así que el servidor y el navegador derivan dos instantes distintos de una misma carga útil: el tipo de error que nunca se reproduce.

Un 2026-10-30 desnudo no es un instante en absoluto. Está bien para cumpleaños y periodos de facturación, mal para una hora de creación.

Dos formatos viables

Cadenas ISO 8601

{
  "createdAt": "2026-10-30T14:30:00+08:00",
  "updatedAt": "2026-10-30T06:30:00Z"
}

Legibles, ordenables cuando el formato es coherente, con precisión de milisegundos si hace falta, y autodescriptivas.

Números epoch

{ "createdAt": 1793519400 }

Compactos e inequívocos, pero ilegibles, y segundos frente a milisegundos es un peligro permanente. Si eliges epoch, estandariza la unidad y ponla en el nombre del campo: createdAtMs es mucho más seguro que createdAt.

Convenciones que conviene fijar pronto

Elemento Convención
Formato en el cable ISO 8601 con Z o +08:00
Zona de almacenamiento UTC, para que el horario de verano nunca entre en escena
Precisión Los milisegundos bastan
Nombres de campo createdAt, updatedAt, expiresAt
Fechas puras Un campo date separado, p. ej. birthday: "1990-01-01"
Duraciones Entero más una unidad, p. ej. timeoutMs: 3000

Renderizado

new Intl.DateTimeFormat('en-GB', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'Europe/London',
}).format(new Date('2026-10-30T06:30:00Z'));

Especifica timeZone explícitamente cuando la visualización no deba seguir al dispositivo. Confiar en el valor predeterminado del navegador es correcto para horas orientadas al usuario y erróneo para informes.

Tres errores recurrentes

Las fechas desnudas se analizan como medianoche UTC. Según la especificación ECMAScript, las cadenas solo de fecha son UTC mientras que las cadenas de fecha y hora son locales. Esa inconsistencia es una fuente fiable de errores de un día de diferencia.

La zona de la base de datos no coincide con el serializador. El datetime de MySQL no tiene zona; escribe local, lee como UTC, y todo se desplaza por el offset.

Horario de verano. Almacenar horas locales futuras crea instantes que no existen o ocurren dos veces. UTC no tiene esos huecos.

FAQ

¿Cómo debo elegir entre ISO 8601 y números epoch?

Usa ISO 8601 cuando las personas leen la carga útil, cuando quieres orden lexicográfico o cuando el valor debe llevar su propia zona; el coste es aproximadamente una docena de bytes extra y una biblioteca de análisis. Usa números epoch cuando la compacidad y el manejo trivial entre lenguajes importen más, pero fija segundos frente a milisegundos de forma global y di qué unidad guarda el campo.

¿Deberían una fecha desnuda y una marca de tiempo compartir el mismo campo?

No. Un 2026-10-30 desnudo es un día de calendario sin hora y sin zona, mientras que se espera que un campo de marca de tiempo lleve un instante exacto. Mantén cumpleaños y periodos de facturación en un campo date separado, y usa un campo ISO 8601 At para horas de creación o actualización.

Lectura adicional