Temporal API en JavaScript: fechas, horas y zonas horarias sin los problemas de Date

El objeto Date de JavaScript lleva décadas siendo una fuente de bugs sutiles: los meses empiezan en 0, el tiempo local y UTC se mezclan de forma confusa, no hay soporte real de zonas horarias y la aritmética de fechas obliga a manejar milisegundos. La Temporal API llega al Stage 3 de TC39 para resolver todos estos problemas con un diseño moderno, inmutable y explícito sobre zonas horarias.

Por qué Date está roto

Antes de ver Temporal, conviene recordar por qué Date es problemático:

// Los meses van de 0 a 11: enero es 0, diciembre es 11
new Date(2024, 0, 1); // 1 de enero
new Date(2024, 11, 31); // 31 de diciembre

// Date.parse no es consistente entre navegadores
Date.parse('25/12/2024'); // NaN en algunos, número en otros

// No hay zona horaria: todo es UTC o local del sistema
const d = new Date('2024-12-25T00:00:00');
// ¿Es medianoche en Madrid, en UTC, en Nueva York?
// Depende del motor JavaScript

// Mutabilidad: setMonth modifica el objeto original
const fecha = new Date(2024, 0, 31);
fecha.setMonth(1); // Febrero tiene 28-29 días: salta a marzo 2 o 3

Temporal.PlainDate: fechas sin tiempo ni zona horaria

Temporal.PlainDate representa una fecha del calendario sin información de hora ni zona horaria. Los meses empiezan en 1 (como en el mundo real) y todos los objetos son inmutables:

// Instalar el polyfill oficial:
// npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';

const hoy = Temporal.Now.plainDateISO();
console.log(hoy.toString()); // "2024-06-15"
console.log(hoy.year, hoy.month, hoy.day); // 2024, 6, 15

const navidad = Temporal.PlainDate.from({ year: 2024, month: 12, day: 25 });
const diasHastaNavidad = hoy.until(navidad).days;
console.log(`Faltan ${diasHastaNavidad} días para Navidad`);

// Aritmética de fechas: devuelve un nuevo objeto, no modifica el original
const manana = hoy.add({ days: 1 });
const mesQueViene = hoy.add({ months: 1 });
const finDeAnio = hoy.with({ month: 12, day: 31 });

// Comparación
console.log(Temporal.PlainDate.compare(navidad, hoy)); // 1 (navidad > hoy)
console.log(navidad.equals(hoy)); // false

Temporal.ZonedDateTime: fecha, hora y zona horaria

Temporal.ZonedDateTime es el tipo más completo: incluye fecha, hora y zona horaria real (no solo un offset UTC). Gestiona correctamente el horario de verano, saltos de hora y zonas horarias sin ambigüedad:

// Instante actual en una zona horaria específica
const ahoraMadrid = Temporal.Now.zonedDateTimeISO('Europe/Madrid');
console.log(ahoraMadrid.toString());
// "2024-06-15T14:30:00+02:00[Europe/Madrid]"

// Crear un ZonedDateTime concreto
const reunion = Temporal.ZonedDateTime.from({
  year: 2024,
  month: 10,
  day: 27,         // día en que cambia el horario en España
  hour: 2,
  minute: 30,
  timeZone: 'Europe/Madrid',
  disambiguation: 'compatible', // resolver ambigüedad por cambio de hora
});

// Sumar duración respetando zonas horarias y horario de verano
const mananaReunion = reunion.add({ days: 1 });
// La hora es correcta aunque haya pasado un cambio de horario

// Convertir entre zonas horarias
const enNuevaYork = reunion.withTimeZone('America/New_York');
console.log(enNuevaYork.hour); // la hora local correcta en NY

Temporal.Duration: duraciones explícitas

Temporal.Duration representa un período de tiempo con componentes individuales (años, meses, semanas, días, horas, etc.) sin confundirlos con milisegundos:

const duracion = Temporal.Duration.from({ years: 1, months: 6, days: 15 });
console.log(duracion.toString()); // "P1Y6M15D"

const inicio = Temporal.PlainDate.from('2024-01-01');
const fin = Temporal.PlainDate.from('2024-06-15');
const diferencia = inicio.until(fin, { largestUnit: 'month' });
console.log(diferencia.months, diferencia.days); // 5, 14

// Redondear duraciones
const tiempoEjecucion = Temporal.Duration.from({ seconds: 125 });
const redondeado = tiempoEjecucion.round({ largestUnit: 'minute' });
console.log(redondeado.minutes, redondeado.seconds); // 2, 5

Instant: momentos absolutos en el tiempo

Temporal.Instant representa un punto exacto en el tiempo universal, independiente de cualquier zona horaria o calendario. Es el equivalente preciso a un timestamp Unix:

const ahora = Temporal.Now.instant();
console.log(ahora.epochMilliseconds); // como Date.now()
console.log(ahora.epochNanoseconds);  // precisión de nanosegundos

// Convertir Date a Instant y viceversa
const fecha = new Date();
const instant = Temporal.Instant.fromEpochMilliseconds(fecha.getTime());
const fechaDeVuelta = new Date(instant.epochMilliseconds);

// Ordenar eventos por tiempo
const eventos = [evento1, evento2, evento3];
eventos.sort((a, b) => Temporal.Instant.compare(a.instant, b.instant));

La Temporal API está en Stage 3 de TC39 desde 2021 y tiene soporte experimental en Firefox (detrás de una flag) y en todos los entornos a través del polyfill oficial @js-temporal/polyfill. Puedes usarla en producción hoy con el polyfill. La adopción nativa en navegadores se espera en cuanto termine el período de revisión del Stage 3.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP