8 min

Formato y conversión de tiempo en JavaScript: errores comunes

Aprende a formatear y convertir tiempo en JavaScript sin sorpresas: timestamps, cadenas ISO, zonas horarias, horario de verano, reglas de parseo y patrones fiables.

Formato y conversión de tiempo en JavaScript: errores comunes

Qué suele salir mal con el tiempo en JavaScript

Los errores relacionados con el tiempo en JavaScript rara vez se manifiestan como “el reloj está mal”. Aparecen como pequeños desplazamientos confusos: una fecha que es correcta en tu portátil pero incorrecta en la máquina de un compañero, una respuesta de la API que parece bien hasta que se muestra en otra zona horaria, o un informe que está “desfasado en uno” en torno a un cambio estacional.

Los síntomas más comunes

Normalmente verás uno (o más) de estos:

  • Desfase de una hora: especialmente alrededor del horario de verano, o cuando un valor se convierte accidentalmente entre hora local y UTC.
  • Desfase de un día: un valor solo de fecha (por ejemplo, “2025-12-23”) aparece como el día anterior/siguiente según la zona horaria.
  • Zona horaria equivocada: las horas parecen correctas, pero el offset (p. ej., +02:00) es distinto del que esperabas.
  • Formateo inconsistente: “funciona en Chrome” pero se ve diferente en Safari, o el servidor y el navegador no coinciden al parsear una cadena.

Por qué sucede: “tiempo” puede significar cosas distintas

Una gran fuente de problemas es que la palabra tiempo puede referirse a conceptos distintos:

  • Un instante: un momento específico en todo el mundo (p. ej., "2025-12-23T10:00:00Z"). Esto es lo que normalmente quieres para logs, eventos y almacenamiento en APIs.
  • Una fecha del calendario: un día en el calendario sin zona horaria (p. ej., un cumpleaños, una fecha de factura). Tratarlo como un instante puede desplazarlo a través de límites de día.
  • Hora de reloj local: “9:00 AM en Berlín”, que depende de las reglas de zona horaria y de los cambios por horario de verano.

El Date incorporado en JavaScript intenta cubrir todos estos casos, pero principalmente representa un instante en el tiempo y a la vez te empuja hacia la visualización local, lo que facilita conversiones accidentales.

En qué se centra este artículo

Esta guía es intencionalmente práctica: cómo obtener conversiones previsibles entre navegadores y servidores, cómo elegir formatos más seguros (como ISO 8601) y cómo detectar las trampas clásicas (segundos vs milisegundos, UTC vs local y diferencias de parseo). El objetivo no es más teoría: es menos sorpresas de “¿por qué se desplazó?”.

Tipos de datos temporales: timestamp, Date y cadena

Los errores de tiempo en JavaScript a menudo empiezan por mezclar representaciones que parecen intercambiables, pero no lo son.

Las tres representaciones que verás con más frecuencia

1) Epoch en milisegundos (número)

Un número simple como 1735689600000 suele ser “milisegundos desde 1970-01-01T00:00:00Z”. Representa un instante en el tiempo sin formato ni zona horaria adjunta.

2) Objeto Date (envoltura de un instante)

Un Date almacena el mismo tipo de instante que un timestamp. La parte confusa: cuando imprimes un Date, JavaScript lo formatea usando las reglas locales del entorno a menos que pidas lo contrario.

3) Cadena formateada (para humanos)

Cadenas como "2025-01-01", "01/01/2025 10:00" o "2025-01-01T00:00:00Z" no son una sola cosa. Algunas son inequívocas (ISO 8601 con Z), otras dependen de la configuración regional y algunas no incluyen zona horaria.

“Instante en el tiempo” vs “hora para humanos”

  • Instante en el tiempo: “este momento exacto globalmente” (mejor almacenado como ms epoch o una ISO en UTC).
  • Hora para humanos: “lo que debe ver el usuario” (depende de la localización y la zona horaria).

El mismo instante puede mostrarse de forma distinta según la zona horaria:

const instant = new Date("2025-01-01T00:00:00Z");

instant.toLocaleString("en-US", { timeZone: "UTC" });
// "1/1/2025, 12:00:00 AM"

instant.toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
// "12/31/2024, 4:00:00 PM" (día anterior)

Elige una “fuente de verdad”

Elige una única representación interna (comúnmente milisegundos epoch o ISO 8601 en UTC) y úsala de forma consistente en toda tu app y APIs. Convierte a/desde Date y cadenas formateadas solo en los límites: parseo de entrada y visualización en la interfaz.

Timestamps: segundos vs milisegundos (fácil de confundir)

Un “timestamp” suele significar tiempo epoch (también llamado tiempo Unix): el recuento de tiempo desde 1970-01-01 00:00:00 UTC. La trampa: distintos sistemas cuentan en distintas unidades.

El Date de JavaScript es la fuente de la mayoría de confusiones porque usa milisegundos. Muchas APIs, bases de datos y logs usan segundos.

La regla práctica

  • Unix timestamp (segundos): 1704067200
  • Timestamp de JavaScript (milisegundos): 1704067200000

Mismo momento, pero la versión en milisegundos tiene tres dígitos adicionales.

Conversiones seguras (segundos ↔ milisegundos)

Usa multiplicación/división explícita para que la unidad sea obvia:

// seconds -> Date
const seconds = 1704067200;
const d1 = new Date(seconds * 1000);

// milliseconds -> Date
const ms = 1704067200000;
const d2 = new Date(ms);

// Date -> seconds
const secondsOut = Math.floor(d2.getTime() / 1000);

// Date -> milliseconds
const msOut = d2.getTime();

El bug clásico: pasar segundos a Date()

Esto parece razonable, pero está mal cuando ts está en segundos:

const ts = 1704067200;      // seconds
const d = new Date(ts);     // WRONG: treated as milliseconds

El resultado será una fecha en 1970, porque 1,704,067,200 milisegundos son solo unos 19 días después del epoch.

Comprobaciones rápidas para validar y depurar

Cuando no estés seguro de la unidad que tienes, añade guardas rápidas:

function asDateFromUnknownEpoch(x) {
  // crude heuristic: seconds are ~1e9-1e10, milliseconds are ~1e12-1e13
  if (x < 1e11) return new Date(x * 1000); // assume seconds
  return new Date(x);                      // assume milliseconds
}

const input = Number(valueFromApi);
console.log({ input, digits: String(Math.trunc(input)).length });
console.log('as ISO:', asDateFromUnknownEpoch(input).toISOString());

Si el recuento de “dígitos” es ~10, probablemente son segundos. Si es ~13, probablemente son milisegundos. También imprime toISOString() mientras depuras: es inequívoco y te ayuda a detectar errores de unidad inmediatamente.

Hora local vs UTC: por qué tu salida cambia

El Date de JavaScript puede ser confuso porque almacena un único instante en el tiempo, pero puede representar ese instante en distintas zonas horarias.

Internamente, un Date es esencialmente “milisegundos desde el epoch Unix (1970-01-01T00:00:00Z)”. Ese número representa un momento en UTC. El “desplazamiento” se produce cuando le pides a JavaScript formatear ese momento como hora local (según la configuración del equipo/servidor) frente a UTC.

Getters locales vs getters UTC

Muchas APIs de Date tienen variantes locales y UTC. Devuelven números distintos para el mismo instante:

const d = new Date('2025-01-01T00:30:00Z');

d.getHours();      // hour in *local* time zone
d.getUTCHours();   // hour in UTC

d.toString();      // local time string
d.toISOString();   // UTC (always ends with Z)

Si tu máquina está en Nueva York (UTC-5), esa hora UTC puede aparecer como “19:30” del día anterior localmente. En un servidor configurado en UTC aparecerá como “00:30”. Mismo instante, diferente visualización.

Por qué los logs parecen “incorrectos”

Los logs a menudo usan Date#toString() o interpolan un Date implícitamente, lo que usa la zona horaria local del entorno. Eso significa que el mismo código puede imprimir timestamps distintos en tu portátil, en CI y en producción.

Orientación práctica

Almacena y transmite el tiempo como UTC (p. ej., milisegundos epoch o ISO 8601 con Z). Convierte a la zona del usuario solo al mostrar:

  • Para APIs: prefiere toISOString() o envía milisegundos epoch
  • Para la UI: formatea en la zona horaria del usuario usando Intl.DateTimeFormat

Si estás construyendo una app rápidamente (por ejemplo con un flujo de trabajo de generación de código en Koder.ai), ayuda definir esto en tus contratos de API desde el principio: nombra campos claramente (createdAtMs, createdAtIso) y mantén el servidor (Go + PostgreSQL) y el cliente (React) consistentes en lo que representa cada campo.

Cadenas ISO 8601: el formato más seguro para APIs

Si necesitas enviar fechas/horas entre navegador, servidor y base de datos, las cadenas ISO 8601 son la opción por defecto más segura. Son explícitas, están muy soportadas y (lo más importante) llevan información de zona horaria.

Usa UTC explícito o un offset explícito

Dos buenos formatos de intercambio:

  • Hora UTC (recomendado cuando no importa la zona local): 2025-03-04T12:30:00Z
  • Hora local con offset (recomendado cuando importa la hora de reloj local): 2025-03-04T12:30:00+02:00

¿Qué significa “Z”?

Z significa Zulu time, otro nombre para UTC. Así que 2025-03-04T12:30:00Z es “12:30 en UTC”.

¿Cuándo importan offsets como +02:00?

Los offsets son cruciales cuando un evento está ligado a un contexto de zona horaria local (citas, reservas, horarios de apertura). 2025-03-04T12:30:00+02:00 describe un momento que está dos horas por delante de UTC, y no es el mismo instante que 2025-03-04T12:30:00Z.

Evita cadenas ambiguas

Cadenas como 03/04/2025 son una trampa: ¿es 4 de marzo o 3 de abril? Diferentes usuarios y entornos lo interpretan distinto. Prefiere 2025-03-04 (fecha ISO) o un datetime ISO completo.

Round-trip seguro (cadena → Date → cadena)

const iso = "2025-03-04T12:30:00Z";
const d = new Date(iso);
const back = d.toISOString();

console.log(iso);  // 2025-03-04T12:30:00Z
console.log(back); // 2025-03-04T12:30:00.000Z

Ese comportamiento de “round-trip” es exactamente lo que quieres para APIs: consistente, predecible y consciente de la zona horaria.

Peligros al parsear: Date.parse y diferencias entre navegadores

Planifica tu contrato de datos temporales
Usa Planning Mode para definir contratos temporales antes de generar código.

Date.parse() parece conveniente: le pasas una cadena y obtienes un timestamp. El problema es que, para cualquier cosa que no sea claramente ISO 8601, el parseo puede depender de heurísticas del navegador. Esas heurísticas han diferido entre motores y versiones, lo que significa que la misma entrada puede parsearse distinto (o no parsearse) según dónde se ejecute tu código.

Por qué Date.parse() puede variar

JavaScript solo estandariza de forma fiable el parseo para cadenas estilo ISO 8601 (y aun así, detalles como la zona horaria importan). Para formatos “amigables”, como "03/04/2025", "March 4, 2025" o "2025-3-4", los navegadores pueden interpretar:

  • Mes/día vs día/mes según suposiciones de localización
  • Falta de zona horaria como hora local en un motor, pero rechazada en otro
  • Cadenas ligeramente malformadas como “suficientemente cercanas”… hasta que no lo son

Si no puedes predecir la forma exacta de la cadena, no puedes predecir el resultado.

El caso sorprendente: YYYY-MM-DD

Una trampa común es el formulario de fecha simple "YYYY-MM-DD" (por ejemplo, "2025-01-15"). Muchos desarrolladores esperan que se interprete como medianoche local. En la práctica, algunos entornos tratan esta forma como medianoche UTC.

Esa diferencia importa: la medianoche UTC convertida a hora local puede convertirse en el día anterior en zonas con offset negativo (p. ej., América) o desplazar la hora inesperadamente. Es una forma fácil de obtener errores de “¿por qué mi fecha está desplazada un día?”.

Lista de verificación de parseo: entrada de usuario vs entrada del servidor

Para entrada de servidor/API:

  • Prefiere ISO 8601 completo con zona horaria explícita, p. ej. 2025-01-15T13:45:00Z o 2025-01-15T13:45:00+02:00.
  • Trata los valores solo de fecha como datos, no como un momento en el tiempo. Si es un cumpleaños o una fecha de vencimiento, mantenlo como una cadena simple ("YYYY-MM-DD") y evita convertirlo a Date salvo que también definas la zona horaria prevista.

Para entrada de usuario:

  • No aceptes formatos ambiguos como 03/04/2025 a menos que tu UI fuerce el significado.
  • Prefiere inputs controlados (selectores de fecha) que produzcan un formato conocido.
  • Si debes parsear texto libre, define y aplica los formatos aceptados desde el principio.

Usa reglas explícitas (no heurísticas)

En lugar de confiar en Date.parse() para “resolverlo”, elige uno de estos patrones:

  • Aceptar solo ISO 8601 desde servidores; rechazar cualquier otra cosa.
  • Parsear manualmente formatos conocidos (dividir la cadena y usar new Date(year, monthIndex, day) para fechas locales).
  • Almacenar y transmitir timestamps (milisegundos epoch) para instantes precisos, y formatear para la visualización más tarde.

Cuando los datos de tiempo son críticos, “parsea en mi máquina” no es suficiente: haz explícitas y consistentes tus reglas de parseo.

Formateo para humanos con Intl.DateTimeFormat

Si tu objetivo es “mostrar una fecha/hora de la manera que la gente espera”, la mejor herramienta en JavaScript es Intl.DateTimeFormat. Usa las reglas de localización del usuario (orden, separadores, nombres de meses) y evita el enfoque frágil de montar cadenas manualmente como month + '/' + day.

Por qué supera a construir cadenas manualmente

Formatear manualmente suele codificar por defecto el formato estadounidense, olvida ceros a la izquierda o produce resultados confusos de 24/12 horas. Intl.DateTimeFormat además hace explícita la zona horaria en la que estás mostrando—crítico cuando tus datos se almacenan en UTC pero la UI debe reflejar la hora local del usuario.

Opciones comunes que realmente usarás

Para “formatearlo bien”, dateStyle y timeStyle son lo más sencillo:

const d = new Date('2025-01-05T16:30:00Z');

// User’s locale + user’s local time zone
console.log(new Intl.DateTimeFormat(undefined, {
  dateStyle: 'medium',
  timeStyle: 'short'
}).format(d));

// Force a specific time zone (great for event times)
console.log(new Intl.DateTimeFormat('en-GB', {
  dateStyle: 'full',
  timeStyle: 'short',
  timeZone: 'UTC'
}).format(d));

Si necesitas ciclos horarios consistentes (p. ej., un ajuste en la configuración), usa hour12:

console.log(new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: '2-digit',
  hour12: true
}).format(d));

Un patrón práctico de UI

Elige una función de formateo por cada “tipo” de timestamp en tu UI (hora de mensaje, entrada de log, inicio de evento) y mantén la decisión de timeZone intencional:

  • Usa hora local para “cuando pasó para mí”.
  • Usa una zona fija (a menudo UTC) para logs de auditoría, eventos de servidor o coordinación entre equipos.

Esto te da una salida consistente y amigable con la localización sin mantener un conjunto frágil de cadenas de formato personalizadas.

Horario de verano: el bug oculto de la hora menos

Crea una API segura frente al tiempo
Genera endpoints React + Go con campos createdAtMs claros y payloads en ISO 8601.

El horario de verano (DST) es cuando una zona horaria cambia su offset respecto a UTC (típicamente una hora) en fechas específicas. Lo complicado: el DST no solo “cambia el offset”—cambia la existencia de ciertas horas locales.

Horas de reloj ausentes y duplicadas

Cuando los relojes adelantan, hay un rango de horas locales que nunca ocurre. Por ejemplo, en muchas regiones el reloj salta de 01:59 a 03:00, así que las 02:30 hora local están "ausentes".

Cuando los relojes atrasan, hay un rango de horas locales que ocurre dos veces. Por ejemplo, la 01:30 puede ocurrir una vez antes del cambio y otra vez después, lo que significa que la misma hora de reloj puede referirse a dos instantes distintos.

Sumar 24 horas vs “misma hora local mañana”

Estos no son equivalentes alrededor de los límites de DST:

  • Sumar 24 horas: “exactamente 24 * 60 * 60 segundos después”
  • Mismo horario local al día siguiente: “mañana a las 9:00 AM en esta zona horaria”

Si el DST empieza esta noche, “mañana a las 9:00” podría estar a solo 23 horas de distancia. Si el DST termina esta noche, podría estar 25 horas de distancia.

// Scenario: schedule “same local time tomorrow”
const d = new Date(2025, 2, 8, 9, 0); // Mar 8, 9:00 local

const plus24h = new Date(d.getTime() + 24 * 60 * 60 * 1000);
const nextDaySameLocal = new Date(d);
nextDaySameLocal.setDate(d.getDate() + 1);

// Around DST, plus24h and nextDaySameLocal can differ by 1 hour.

Por qué setHours puede sorprenderte

Si haces algo como date.setHours(2, 30, 0, 0) en un día de “adelanto de reloj”, JavaScript puede normalizarlo a una hora válida distinta (a menudo 03:30), porque 02:30 no existe en la hora local.

Enfoques más seguros

  • Haz la aritmética en UTC para tiempos transcurridos (duraciones): usa milisegundos epoch y métodos UTC.
  • Para programación local, sé explícito con la intención: “mismo horario local mañana” debería usar operaciones de calendario (setDate) en lugar de sumar milisegundos.
  • Al intercambiar tiempos vía APIs, prefiere ISO 8601 con offset o Z para que el instante sea inequívoco.

Duraciones vs Fechas: no uses Date para un temporizador

Una fuente común de errores es usar Date para representar algo que no es un momento del calendario.

Un timestamp responde “¿cuándo pasó esto?” (un instante específico como 2025-12-23T10:00:00Z). Una duración responde “¿cuánto duró?” (como “3 minutos 12 segundos”). Son conceptos distintos, y mezclarlos conduce a cálculos confusos y efectos inesperados por zona horaria/DST.

Por qué Date es la herramienta equivocada para duraciones

Date siempre representa un punto en la línea temporal relativo a un epoch. Si almacenas “90 segundos” como un Date, en realidad estás almacenando “1970-01-01 más 90 segundos” en una zona horaria concreta. Formatearlo puede mostrar 01:01:30, desplazarse una hora o mostrar una fecha que no pretendías.

Para duraciones, prefiere números sencillos:

  • Almacena duraciones en segundos o milisegundos (elige una unidad y mantenla).
  • Haz la aritmética con números.
  • Convierte a una cadena para mostrar solo al final.

Convertir segundos a HH:mm:ss

Aquí hay un formateador simple que funciona para temporizadores regresivos y duraciones de medios:

function formatHMS(totalSeconds) {
  const s = Math.max(0, Math.floor(totalSeconds));
  const hh = String(Math.floor(s / 3600)).padStart(2, "0");
  const mm = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
  const ss = String(s % 60).padStart(2, "0");
  return `${hh}:${mm}:${ss}`;
}

formatHMS(75);    // "00:01:15" (countdown timer)
formatHMS(5423);  // "01:30:23" (media duration)

Si conviertes desde minutos, multiplica primero (minutes * 60) y mantén el valor en forma numérica hasta que lo renders.

Comparar, ordenar y rangos sin sorpresas

Cuando comparas tiempos en JavaScript, el enfoque más seguro es comparar números, no texto formateado. Un objeto Date es esencialmente una envoltura alrededor de un timestamp numérico (milisegundos epoch), así que quieres que las comparaciones acaben como “número vs número”.

Comparaciones seguras (los timestamps ganan)

Usa getTime() (o Date.valueOf(), que devuelve el mismo número) para comparar de forma fiable:

const a = new Date('2025-01-10T12:00:00Z');
const b = new Date('2025-01-10T12:00:01Z');

if (a.getTime() < b.getTime()) {
  // a is earlier
}

// Also works:
if (+a < +b) {
  // unary + calls valueOf()
}

Evita comparar cadenas formateadas como "1/10/2025, 12:00 PM": dependen de la localización y no ordenarán correctamente. La excepción principal son las cadenas ISO 8601 en el mismo formato y zona horaria (p. ej., todas ...Z), que son ordenables lexicográficamente.

Ordenar y filtrar por rango

Ordenar por tiempo es sencillo si ordenas por milisegundos epoch:

items.sort((x, y) => new Date(x.createdAt).getTime() - new Date(y.createdAt).getTime());

Filtrar elementos dentro de un rango es la misma idea:

const start = new Date('2025-01-01T00:00:00Z').getTime();
const end   = new Date('2025-02-01T00:00:00Z').getTime();

const inRange = items.filter(i => {
  const t = new Date(i.createdAt).getTime();
  return t >= start && t < end;
});

“Inicio/fin del día” (local vs UTC)

El “inicio del día” depende de si te refieres a la hora local o a UTC:

// Local start/end of day
const d = new Date(2025, 0, 10); // Jan 10 in local time
const localStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
const localEnd   = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);

// UTC start/end of day
const utcStart = new Date(Date.UTC(2025, 0, 10, 0, 0, 0, 0));
const utcEnd   = new Date(Date.UTC(2025, 0, 10, 23, 59, 59, 999));

Elige una definición pronto y mantenla consistentemente en tus comparaciones y lógica de rangos.

Lista de depuración: cómo diagnosticar un bug de conversión de tiempo

Prueba conversiones en una app en vivo
Genera, despliega y aloja un visor de zonas horarias para validar el formateo de extremo a extremo.

Los bugs de tiempo parecen aleatorios hasta que identificas qué tienes (timestamp? cadena? Date?) y dónde se introduce el desplazamiento (parseo, conversión de zona horaria, formateo).

1) Captura las “tres vistas” del mismo momento

Empieza registrando el mismo valor de tres formas distintas. Esto revela rápidamente si el problema es segundos vs milisegundos, local vs UTC o parseo de cadenas.

console.log('raw input:', input);

const d = new Date(input);
console.log('toISOString (UTC):', d.toISOString());
console.log('toString (local):', d.toString());
console.log('timezone offset (min):', d.getTimezoneOffset());

Qué buscar:

  • Si toISOString() está muy errada (p. ej., año 1970 o un futuro lejano), sospecha segundos vs milisegundos.
  • Si toISOString() parece correcta pero toString() está “desplazada”, estás viendo un problema de visualización en zona horaria local.
  • Si getTimezoneOffset() cambia según la fecha, estás cruzando horario de verano.

2) Verifica el entorno: zona horaria y localización

Muchas veces de “funciona en mi máquina” son simplemente distintos valores por defecto de entorno.

  • Navegador: verifica la configuración de zona horaria del SO y el idioma del navegador. Luego registra:
console.log(Intl.DateTimeFormat().resolvedOptions());
  • Node.js / servidor: confirma la zona horaria del proceso:
console.log('TZ:', process.env.TZ);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);

Si tu servidor corre en UTC pero tu portátil en una zona local, la salida formateada diferirá a menos que especifiques timeZone explícitamente.

3) Añade tests donde el tiempo falla más a menudo

Crea tests unitarios alrededor de los límites de DST y tiempos “borde”:

  • Una hora antes y después del cambio DST en la zona relevante
  • Fin de mes/año y cruces 23:3000:30
  • Varias zonas horarias si tu producto las soporta

Si iteras rápido, considera incluir estos tests en tu scaffolding. Por ejemplo, cuando generas una app React + Go en Koder.ai, puedes añadir una pequeña suite de “contratos de tiempo” desde el inicio (ejemplos de payloads de API + aserciones de parseo/formateo) para que las regresiones se detecten antes del despliegue.

Lista rápida pre-lanzamiento

  • Las entradas tienen un contrato claro: milisegundos epoch o ISO 8601 con offset.
  • No hay cadenas ambiguas como "2025-03-02 10:00".
  • El formateo siempre especifica locale y (cuando hace falta) timeZone.
  • Las pruebas cubren límites de DST para las regiones objetivo.

Patrones recomendados para manejar el tiempo de forma fiable

El manejo fiable del tiempo en JavaScript consiste en elegir una “fuente de verdad” y ser consistente desde el almacenamiento hasta la visualización.

Un conjunto simple de buenas prácticas

Almacena y calcula en UTC. Trata la hora local del usuario como un detalle de presentación.

Transmite fechas entre sistemas como cadenas ISO 8601 con un offset explícito (preferiblemente Z). Si debes enviar epochs numéricos, documenta la unidad y manténla consistente (milisegundos es el defecto común en JS).

Formatea para humanos con Intl.DateTimeFormat (o toLocaleString), y pasa timeZone explícito cuando necesites salida determinista (por ejemplo, mostrar siempre en UTC o en una región de negocio específica).

Guía de decisiones: DB vs API vs UI

  • Base de datos: almacena un instante en UTC (milisegundos epoch o un tipo datetime en UTC). Evita datetimes “locales” a menos que tu dominio realmente almacene horas de reloj (como “la tienda abre a las 09:00”).
  • APIs: prefiere ISO 8601 con Z (p. ej., 2025-12-23T10:15:00Z). Si usas epochs, incluye un nombre de campo como createdAtMs para dejar claro la unidad.
  • UI: toma el instante UTC almacenado y formátalo para la localización del usuario. Para UIs de programación, etiqueta claramente la zona horaria y mantén las conversiones explícitas.

Cuándo vale la pena una librería

Considera una librería dedicada si necesitas eventos recurrentes, reglas complejas de zonas horarias, aritmética segura respecto a DST (“misma hora local mañana”) o mucho parseo de entradas inconsistentes. El valor está en APIs más claras y menos bugs en casos límite.

Si quieres profundizar, explora más guías relacionadas con el tiempo en /blog. Si estás evaluando herramientas u opciones de soporte, consulta /pricing.

Preguntas frecuentes

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

Usa milisegundos desde el epoch o una cadena ISO 8601 en UTC como formato interno. Convierte a un formato local solo cuando muestres el valor a una persona.

¿Por qué mi marca de tiempo Unix se convierte en una fecha de 1970?

JavaScript Date espera milisegundos. Multiplica por 1000 una marca de tiempo Unix en segundos antes de pasarla a new Date().

¿Por qué la misma fecha muestra horas distintas en diferentes equipos?

Un Date almacena un instante, pero JavaScript suele mostrarlo en la zona horaria local del equipo. Compara toISOString() con toString() para ver si cambiaron los datos o solo la visualización.

¿Cómo evito que una fecha se desplace un día?

Conserva un valor que solo contiene la fecha, como una fecha de nacimiento o de factura, como YYYY-MM-DD. No lo conviertas en un Date a menos que también definas la zona horaria prevista.

¿Qué formato de fecha es más seguro para una API?

Envía un valor ISO 8601 completo con Z para UTC, como 2025-03-04T12:30:00Z, o incluye un desfase explícito. Evita cadenas como 03/04/2025 porque las personas y los navegadores pueden interpretarlas de forma distinta.

¿Cómo debo dar formato a las fechas para los usuarios?

Usa Intl.DateTimeFormat y especifica una timeZone cuando la pantalla necesite una zona fija. Da formato a las fechas según la configuración regional del usuario sin construir cadenas manualmente.

¿Debo usar Date.parse para fechas introducidas por usuarios?

No te bases en él para cadenas informales o ambiguas. Acepta un formato ISO conocido de las API o analiza la entrada controlada del usuario con reglas que definas tú mismo.

¿Por qué debo evitar usar Date para una cuenta regresiva o una duración?

Un Date indica un momento del calendario, mientras que una duración solo es una cantidad de tiempo. Guarda las duraciones en segundos o milisegundos y luego da formato al número al mostrarlo.

¿Cómo afecta el horario de verano a las fechas de JavaScript?

Usa milisegundos desde el epoch para calcular tiempo transcurrido. Para horarios como «mañana a las 9:00 AM» en una zona con nombre, usa operaciones basadas en el calendario y prueba las fechas de transición del horario de verano.

¿Cuál es la forma más rápida de depurar un error de tiempo en JavaScript?

Registra la entrada sin procesar, toISOString(), toString() y getTimezoneOffset(). Un valor ISO incorrecto suele indicar un problema de análisis o de unidades, mientras que una cadena local diferente suele indicar una diferencia en la visualización de la zona horaria.

Related posts