Desarrollo web

Temporal ya es nativo en Chrome, Edge, Firefox y Safari 27.3 beta: la migración desde Date es planificable, pero el polyfill aún hace falta.

Chrome y Edge lo traen desde la versión 144, Firefox desde la 139 y la beta de Safari 27.3 lo incorpora. Eso no significa que puedas eliminar el polyfill mañana, pero sí que la migración dejó de ser experimental y se puede planificar sin adivinar.

Qué es Temporal y por qué reemplaza a Date

Date arrastra problemas que solo duelen cuando el sistema lleva meses en producción: es mutable (un método puede cambiar el objeto que otra parte del código está leyendo), los meses van de 0 a 11, el parseo de cadenas que no son ISO 8601 quedó definido por la implementación y no por el estándar, y no existe un tipo para "fecha sin hora". Además, sumar un día a una fecha local no es lo mismo que sumar 24 horas, y con Date esa distinción se pierde.

Temporal resuelve eso separando responsabilidades en tipos inmutables: un instante exacto, una fecha de calendario, una hora de reloj y una fecha con zona horaria son cosas distintas y ahora tienen clases distintas. La aritmética es explícita, el calendario es un parámetro y la zona horaria nunca es implícita. La referencia completa de la API está en MDN.

Safari 27.3 y el estado real del soporte en 2026

Las notas de versión de Apple confirman que Safari 27.3 beta incorpora Temporal y también la declaración using para gestión explícita de recursos (developer.apple.com). Con eso, los tres motores principales ya lo implementan.

El dato que importa para decidir es el agregado: Can I Use reporta 72,14 % de usuarios globales con soporte nativo (caniuse.com/temporal). Ese 28 % restante incluye navegadores viejos, dispositivos sin actualizar y Safari en versiones anteriores a la 27.3 estable. Con esa cifra, la regla práctica es simple: Temporal nativo en código nuevo, polyfill obligatorio si el tráfico long tail te importa.

Detectar soporte y cargar el polyfill solo donde haga falta

No conviene importar el polyfill en el bundle principal solo para navegadores que no lo necesitan. Un módulo contenedor con import dinámico resuelve el problema sin tocar cada archivo:

// temporal.js
const nativo = globalThis.Temporal;
export const Temporal =
nativo ??
(await import('@js-temporal/polyfill')).Temporal;

Al importar desde ./temporal.js en lugar del global, el bundler puede separar el polyfill en un chunk aparte y solo descargarlo cuando la promesa se ejecuta. Verifica una capacidad real y no solo la existencia del objeto: typeof globalThis.Temporal?.PlainDate === 'function' es una comprobación más honesta que 'Temporal' in window.

Dos advertencias de empaquetado. Primero, si usas un bundler con target moderno, revisa que no reemplace globalThis.Temporal por una referencia estática que rompa el árbol de dependencias. Segundo, el polyfill mantiene su propio registro de zonas horarias; en Node o en entornos sin Intl completo puede necesitar datos adicionales. Prueba con una zona real antes de dar por cerrada la integración.

Los tipos de Temporal que vas a usar

Ocho tipos cubren casi todo:

  • Temporal.Instant: un punto exacto en la línea de tiempo, sin zona ni calendario.
  • Temporal.ZonedDateTime: instante más zona horaria y calendario. Es el tipo para agendas.
  • Temporal.PlainDate: año, mes y día, sin hora ni zona.
  • Temporal.PlainTime: hora del día sin fecha.
  • Temporal.PlainDateTime: fecha y hora sin zona, útil para "las 9:00 del 20 de noviembre" cuando la zona se decide después.
  • Temporal.PlainYearMonth y Temporal.PlainMonthDay: facturación mensual y cumpleaños.
  • Temporal.Duration: una cantidad de tiempo con unidades separadas.

PlainDate, ZonedDateTime e Instant en la práctica

La decisión se reduce a tres preguntas: ¿el valor representa un momento físico, una fecha de calendario o una cita en una zona concreta?

// Fecha de nacimiento o vencimiento: no tiene hora ni zona
const vence = Temporal.PlainDate.from('2026-11-30');
// Cita médica: hora local en una zona específica
const cita = Temporal.ZonedDateTime.from({
timeZone: 'America/Bogota',
year: 2026, month: 11, day: 20, hour: 9, minute: 30
});
// Registro de auditoría: un instante, sin ambigüedad
const creado = Temporal.Now.instant();

Un caso que suele pasarse por alto: los formularios de fecha. Un campo type="date" entrega 2026-11-20 y mucha gente lo convierte a Date con new Date('2026-11-20'), que el estándar interpreta como medianoche UTC. En Colombia eso se muestra como el día anterior a las 7 de la noche. Con Temporal.PlainDate.from('2026-11-20') el valor nunca se desplaza.

Temporal solo parsea ISO 8601. Si el usuario escribe 20/11/2026, tendrás que parsear tú o mantener una librería de parseo; no esperes que Temporal.PlainDate.from lo entienda. Y si construyes desde un objeto, el modo por defecto es constrain, que recorta fechas imposibles en silencio:

Temporal.PlainDate.from(
{ year: 2026, month: 2, day: 30 },
{ overflow: 'reject' } // RangeError en vez de devolver el 28 de febrero
);

Cómo migrar de Date y moment.js sin romper datos

La migración falla cuando empieza por el código. Empieza por el contrato de datos.

Primero el contrato, después el código

Define qué se guarda en la base de datos y qué viaja por la API. La respuesta sana: instantes en ISO 8601 con Z o en epoch, y fechas de calendario como YYYY-MM-DD. Nunca cadenas localizadas ni objetos de librería serializados. Si la base ya cumple eso, no hay migración de datos: hay migración de código. Si guardaste moment en un campo o fechas con formato local, ese es el primer trabajo y va aparte.

Conversiones en la frontera

Mantén las conversiones en los bordes del sistema, no en la lógica de negocio:

const instante = Temporal.Instant.fromEpochMilliseconds(Date.now());
const legado = new Date(instante.epochMilliseconds);

Para una fecha que llega de la API como ISO con zona, Temporal.ZonedDateTime.from(cadena) conserva el offset y la zona entre corchetes, por ejemplo 2026-11-20T09:30:00-05:00[America/Bogota]. Si solo llega el offset sin nombre de zona, no puedes reconstruir la zona: guarda el identificador IANA desde el principio.

Comparaciones y diferencias

Aquí es donde Date genera más bugs. Con Temporal la comparación es explícita y no depende de restar milisegundos:

const inicio = Temporal.PlainDate.from('2026-11-01');
const fin = inicio.add({ months: 1 }).subtract({ days: 1 });
const dias = inicio.until(fin, { largestUnit: 'days' }).days; // 29
Temporal.PlainDate.compare(inicio, fin); // -1
inicio.equals(fin); // false

until y since respetan el calendario: sumar un mes a un 31 de enero da el último día de febrero, no el 3 de marzo.

Zonas horarias y horario de verano

Colombia vive en UTC-5 todo el año, y eso es una trampa: los bugs de horario de verano no aparecen en las pruebas locales. Prueba siempre con una zona que sí cambie, como America/New_York o Europe/Madrid, y usa disambiguation: 'reject' para detectar horas inexistentes o ambiguas:

// El 8 de marzo de 2026 las 2:30 no existen en Nueva York
Temporal.ZonedDateTime.from(
'2026-03-08T02:30[America/New_York]',
{ disambiguation: 'reject' }
);

Para eventos recurrentes (una suscripción que se cobra cada mes, una clase semanal), guarda la hora local más el identificador de zona, no un instante fijo. Las reglas IANA cambian y un instante calculado hoy puede quedar una hora corrido dentro de dos años.

Checklist antes de retirar moment.js o dayjs

  • Busca new Date(, Date.now() y getTimezoneOffset() en todo el repositorio, incluidos tests y scripts de migración.
  • Revisa cada toLocaleString y Intl.DateTimeFormat: el formateo sigue siendo trabajo de Intl, no de Temporal.
  • Convierte los tests con fechas de cambio de horario y con años bisiestos; son los que revelan los errores de aritmética.
  • Confirma si alguna dependencia transitiva usa moment internamente. Retirarlo del package.json no lo saca del bundle.
  • Verifica que el polyfill quede en un chunk separado y que tu presupuesto de rendimiento lo tolere.

Preguntas frecuentes

¿Temporal reemplaza a Date por completo?

No. Date sigue existiendo y muchas APIs del navegador lo esperan. Temporal lo sustituye en la lógica de negocio y en el modelado de fechas, pero las conversiones en la frontera seguirán siendo necesarias por años.

¿Puedo usar Temporal en producción hoy?

Sí, con polyfill en los navegadores sin soporte nativo. Chrome y Edge lo traen desde la 144, Firefox desde la 139 y Safari 27.3 está en beta; con 72,14 % de cobertura global, asumir soporte universal todavía es un riesgo.

¿Temporal elimina la necesidad de moment.js o dayjs?

Cubre la aritmética, las zonas horarias y las comparaciones, que es el 80 % de lo que la gente usa esas librerías. No cubre el parseo de formatos no ISO ni el formateo con idioma; para eso sigue Intl o una librería de parseo pequeña.

¿Cómo guardo las fechas en la base de datos?

Como instantes en UTC (ISO 8601 con Z o epoch) y las fechas sin hora como YYYY-MM-DD. La zona horaria se guarda aparte, como identificador IANA, cuando el dato la necesite. Convierte a Temporal al leer y a texto al escribir, nunca guardes objetos.

El siguiente paso es medir, no reescribir: cuenta cuántos new Date( hay en tu código, elige un módulo con formularios y migra ese primero. Si el resultado se siente natural, el resto es repetición.

Compartir