Convertir timestamp Unix en JavaScript: Date.now, segundos, ISO 8601
Las fechas en JavaScript son fáciles hasta que mezclas milisegundos y segundos, o hasta que confundes un Date local con un ISO en UTC. Cualquiera de las dos te cuesta una tarde si no la pillas pronto.
Milisegundos contra segundos
Date.now() devuelve un timestamp Unix en milisegundos desde 1970-01-01 UTC. La mayor parte del mundo JavaScript trabaja en milisegundos porque es lo que la plataforma te da gratis.
El resto del mundo trabaja en segundos. time de Unix devuelve segundos. La mayoría de APIs (Stripe, Twitter, el claim exp de JWT, cada función unix_timestamp() de base de datos) devuelven segundos. Si copias un timestamp de un payload de webhook a new Date() directo te sale el año 1970, porque JavaScript lo lee como milisegundos.
El arreglo es una multiplicación. Math.floor(Date.now() / 1000) convierte a segundos cuando tienes que enviar a una API externa. new Date(ts * 1000) convierte de vuelta cuando recibes segundos. Aclara esta confusión una vez y la mayoría de bugs de timestamp desaparecen.
Date.parse y toISOString
Date.parse(str) devuelve milisegundos desde epoch, no segundos. El nombre sugiere simetría con segundos, pero la unidad es la misma que Date.now(). Siempre milisegundos.
new Date().toISOString() devuelve un string en formato ISO 8601 con una Z al final, que significa UTC. new Date(2024, 0, 15) crea un Date en hora local. Si llamas .toISOString() justo después, el offset de zona se cuece en la salida y un usuario en Madrid ve un string distinto al de un usuario en Tokio para la misma línea de código.
Si quieres construir en UTC explícitamente, usa Date.UTC(2024, 0, 15) que devuelve un número de timestamp, y envuélvelo con new Date(). El constructor sin UTC es local. La serialización es UTC. Ese mismatch es el bug de timestamp más común en JS.
Mostrar con Intl.DateTimeFormat
Para mostrar, no escribas tu propio string de formato. Intl.DateTimeFormat está integrado en cada runtime moderno y maneja locale, zona y calendario en una sola llamada:
new Intl.DateTimeFormat("es-ES", {
dateStyle: "full",
timeStyle: "short",
timeZone: "Europe/Madrid",
}).format(new Date(ts * 1000));
El string de locale controla el idioma. La opción timeZone controla qué reloj renderizas. Juntos cubren casi cualquier necesidad de producto sin librería. Si te encuentras escribiendo padStart para añadir un cero a la izquierda, ya te has equivocado.
Cuando la stdlib duele: dayjs y date-fns
La aritmética sobre Date en JS es dolorosa. Sumar un mes no es solo +30 días porque los meses no son 30 días. Restar dos fechas y obtener un conteo de días laborables no está en la librería estándar.
Dos librerías cubren la mayor parte sin traerte Moment entero. dayjs pesa 2 KB gzip y reutiliza una API tipo Moment. date-fns es tree-shakeable y expone funciones puras como addMonths(date, 1) y differenceInBusinessDays(a, b).
Si tu código solo necesita formatear y parsear, quédate en la stdlib. Si empiezas a escribir tu propio helper addBusinessDays, instala date-fns y deja de mantenerlo tú.
Ejemplo completo
javascript// Roundtrip: Date -> segundos Unix -> Date -> ISO 8601
const now = new Date();
// A segundos Unix (igual que Stripe, JWT exp, EXTRACT(epoch) de Postgres)
const unixSeconds = Math.floor(now.getTime() / 1000);
console.log(unixSeconds); // p.ej. 1715000000
// Vuelta a Date desde segundos (ojo al * 1000)
const reconstruido = new Date(unixSeconds * 1000);
// ISO 8601 en UTC, el formato seguro de intercambio
console.log(reconstruido.toISOString()); // "2026-05-10T12:00:00.000Z"
// Mostrar en un locale y zona específicos
const display = new Intl.DateTimeFormat("es-ES", {
dateStyle: "medium",
timeStyle: "short",
timeZone: "Europe/Madrid",
}).format(reconstruido);
console.log(display); ¿Solo necesitas el resultado?
Cuando tienes un timestamp Unix de un webhook o de una línea de log y quieres ver qué fecha representa en tu zona sin escribir dos líneas de código, el convertidor de timestamp en aldeacode.com renderiza el resultado al instante, soporta segundos y milisegundos, y corre en tu navegador sin enviar el valor a ningún sitio.
Abrir Convertidor de Timestamp Unix →Preguntas frecuentes
¿Por qué mi claim exp de JWT aparece en el año 56000 en la base de datos?
Casi siempre un bug de milisegundos contra segundos. El exp de JWT va en segundos (RFC 7519), pero Date.now() devuelve milisegundos. Guardar Date.now() directo como exp desplaza cada timestamp 1000 veces al futuro. Usa Math.floor(Date.now() / 1000) al emitir JWTs.
¿Uso new Date() o Date.now() para medir tiempo transcurrido?
Usa performance.now() para tiempo transcurrido dentro de una sesión, porque es monotónico y no le afectan los cambios de reloj. Usa Date.now() cuando necesitas un timestamp absoluto que enviar a un servidor. Mezclar los dos en el mismo cálculo da intervalos incorrectos si el usuario ajusta su reloj.
¿toISOString conserva los milisegundos?
Sí. El formato de salida es YYYY-MM-DDTHH:mm:ss.sssZ con tres decimales de milisegundos. Si necesitas microsegundos, Date de JavaScript no los soporta en absoluto y necesitas otra herramienta, normalmente un timestamp de servidor desde la base de datos.