Back

Temporal API en JavaScript: Por fin podemos decir adiós al infierno de Date

Si alguna vez has trabajado con fechas en JavaScript, conoces el sufrimiento.

La confusión entre UTC y hora local. Los meses que empiezan en cero (Enero es 0, ¿en serio?). El objeto Date que puede ser modificado desde cualquier parte del código. La pesadilla de convertir zonas horarias. Y la necesidad de depender de moment.js o date-fns para cosas que deberían venir incluidas en el lenguaje.

El objeto Date lleva roto desde 1995. Casi 30 años aguantando sus defectos. Pero finalmente, ¡llegó la solución!

Temporal API es una nueva API moderna de fechas que lleva desarrollándose desde 2017 y ya alcanzó el Stage 3 del proceso TC39. Chrome, Firefox y Safari están implementándola activamente. Para mediados de 2025, estará disponible en todos lados.

En este artículo vamos a explorar todo sobre Temporal: por qué es necesaria, cómo usarla, y cómo migrar desde moment.js.

¿Por qué Date está tan mal?

Antes de meternos con Temporal, veamos por qué necesitábamos un reemplazo. Date no es solo incómodo, tiene problemas estructurales que causan bugs en producción.

Problema 1: Es mutable (y eso es malo)

```javascript
const meeting = new Date('2025-01-15T10:00:00');
scheduleMeeting(meeting);

// En algún otro lugar...
meeting.setHours(meeting.getHours() + 2);
// ¡Ups! El original cambió
```

Una vez que pasas un Date, nunca sabes si alguien lo modificó.

Problema 2: Los meses empiezan en 0 (el clásico)

```javascript
// ¿Qué mes es?
const date = new Date(2025, 1, 14);
console.log(date.toISOString()); // 2025-02-14 ← ¡Es febrero!

// Para enero hay que poner 0
const january = new Date(2025, 0, 15);
```

¿Querías el 25 de diciembre? new Date(2025, 12, 25) te da el 25 de enero de 2026. Todos hemos caído ahí.

Problema 3: El caos de las zonas horarias

```javascript
const date = new Date('2025-01-15T10:00:00');
console.log(date.getHours()); // Depende de tu zona horaria
```

Guarda un timestamp absoluto pero lo muestra en hora local. No hay forma de representar "las 10 AM en Tokio" sin hacer conversiones manuales.

Problema 4: La aritmética no funciona bien

```javascript
// 31 de enero + 1 mes = ?
const jan31 = new Date(2025, 0, 31);
jan31.setMonth(jan31.getMonth() + 1);
console.log(jan31.toDateString()); // "Mon Mar 03 2025" ← ¿¡Qué!?
```

Febrero no tiene 31 días, así que salta al 3 de marzo. 😱

Temporal: Un diseño desde cero

Temporal resuelve todos estos problemas con un diseño completamente nuevo.

Principios clave

  1. Inmutabilidad: Una vez creado, no cambia. Las operaciones devuelven objetos nuevos.
  2. Tipos claros: Diferentes tipos para diferentes usos (fecha, hora, zona horaria, duración).
  3. Sin sorpresas: Los meses empiezan en 1. El parsing es estricto.
  4. Zonas horarias nativas: Soporte integrado para la base de datos IANA.
  5. Pensado para humanos: APIs que funcionan como esperas.

Los tipos

```javascript
Temporal.PlainDate // Solo fecha (2025-01-15)
Temporal.PlainTime // Solo hora (10:30:00)
Temporal.PlainDateTime // Fecha + hora (2025-01-15T10:30:00)
Temporal.ZonedDateTime // Fecha + hora + zona (2025-01-15T10:30:00[America/Mexico_City])
Temporal.Instant // Punto exacto en el tiempo (como Date pero inmutable)
Temporal.Duration // Duración (2 horas, 30 minutos)
Temporal.PlainYearMonth // Año y mes (2025-01)
Temporal.PlainMonthDay // Mes y día (01-15, para cumpleaños)
```

Con solo ver el nombre del tipo, ya sabes qué esperar.

Parte 1: Fechas (Temporal.PlainDate)

Para cuando solo necesitas la fecha, sin hora ni zona horaria. Perfecto para cumpleaños, días festivos, fechas límite.

Crear fechas

```javascript
// Por partes (¡los meses empiezan en 1!)
const date1 = Temporal.PlainDate.from({ year: 2025, month: 1, day: 15 });
console.log(date1.toString()); // "2025-01-15"

// Desde string
const date2 = Temporal.PlainDate.from('2025-01-15');

// Hoy
const today = Temporal.Now.plainDateISO();
```

Obtener información

```javascript
const date = Temporal.PlainDate.from('2025-06-15');

console.log(date.year); // 2025
console.log(date.month); // 6 (¡junio de verdad!)
console.log(date.day); // 15
console.log(date.dayOfWeek); // 7 (domingo. lunes=1)
console.log(date.daysInMonth); // 30
```

Aritmética (¡por fin funciona!)

```javascript
const date = Temporal.PlainDate.from('2025-01-31');

// Sumar 1 mes - Temporal lo maneja bien
const nextMonth = date.add({ months: 1 });
console.log(nextMonth.toString()); // "2025-02-28" ← último día de febrero

// Sumar 2 semanas y 3 días
const later = date.add({ weeks: 2, days: 3 });
console.log(later.toString()); // "2025-02-17"

// Encadenar operaciones
const result = date
.add({ months: 6 })
.add({ days: 15 })
.subtract({ weeks: 1 });
```

Comparar y calcular diferencias

```javascript
const date1 = Temporal.PlainDate.from('2025-01-15');
const date2 = Temporal.PlainDate.from('2025-03-20');

const diff = date2.since(date1);
console.log(diff.days); // 64

const diffMonths = date2.since(date1, { largestUnit: 'month' });
console.log(diffMonths.toString()); // "P2M5D" (2 meses y 5 días)
```

Parte 2: Horas (Temporal.PlainTime)

La hora que marca el reloj, sin fecha ni zona horaria.

```javascript
const time = Temporal.PlainTime.from({ hour: 10, minute: 30 });
console.log(time.toString()); // "10:30:00"

// Operaciones
const later = time.add({ hours: 2, minutes: 45 });
console.log(later.toString()); // "13:15:00"

// Si pasas de medianoche, da la vuelta
const pastMidnight = time.add({ hours: 16 });
console.log(pastMidnight.toString()); // "02:30:00"
```

Parte 3: Fecha + Hora (Temporal.PlainDateTime)

Para cuando necesitas ambas pero no te importa la zona horaria.

```javascript
const dt = Temporal.PlainDateTime.from({
year: 2025, month: 1, day: 15,
hour: 10, minute: 30
});
console.log(dt.toString()); // "2025-01-15T10:30:00"

// Combinar fecha y hora separadas
const date = Temporal.PlainDate.from('2025-01-15');
const time = Temporal.PlainTime.from('10:30:00');
const combined = date.toPlainDateTime(time);
```

Parte 4: ¡La estrella! Zonas horarias (Temporal.ZonedDateTime)

Aquí es donde Temporal brilla. ZonedDateTime representa "las 10:30 en Ciudad de México" o "las 3PM en Londres", con manejo automático del horario de verano.

Crear

```javascript
const zdt = Temporal.ZonedDateTime.from({
year: 2025, month: 1, day: 15,
hour: 10, minute: 30,
timeZone: 'America/Mexico_City'
});
console.log(zdt.toString());
// "2025-01-15T10:30:00-06:00[America/Mexico_City]"

// Hora actual en otra zona
const madridNow = Temporal.Now.zonedDateTimeISO('Europe/Madrid');
```

Convertir entre zonas

```javascript
const mexicoTime = Temporal.ZonedDateTime.from(
'2025-01-15T10:30:00[America/Mexico_City]'
);

// Convertir a España
const madridTime = mexicoTime.withTimeZone('Europe/Madrid');
console.log(madridTime.toString());
// "2025-01-15T17:30:00+01:00[Europe/Madrid]"

// Convertir a UTC
const utc = mexicoTime.withTimeZone('UTC');
```

Horario de verano

```javascript
// En EEUU, en marzo los relojes adelantan 1 hora
const beforeDST = Temporal.ZonedDateTime.from(
'2025-03-09T01:30:00[America/New_York]'
);

const afterDST = beforeDST.add({ hours: 1 });
console.log(afterDST.toString());
// "2025-03-09T03:30:00-04:00[America/New_York]"
// 1:30 + 1 hora = 3:30 (las 2:30 no existen ese día)
```

Parte 5: Duraciones (Temporal.Duration)

```javascript
const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
console.log(duration.toString()); // "PT2H30M"

// Sumar duraciones
const d1 = Temporal.Duration.from({ hours: 1, minutes: 30 });
const d2 = Temporal.Duration.from({ hours: 2, minutes: 45 });
console.log(d1.add(d2).toString()); // "PT4H15M"

// Convertir 150 minutos a horas
const mins = Temporal.Duration.from({ minutes: 150 });
const balanced = mins.round({ largestUnit: 'hour' });
console.log(balanced.toString()); // "PT2H30M"
```

Ejemplos prácticos

```javascript
// Calcular edad
const birthdate = Temporal.PlainDate.from('1990-05-15');
const today = Temporal.Now.plainDateISO();
const age = today.since(birthdate, { largestUnit: 'year' });
console.log(`${age.years} años, ${age.months} meses`);

// Cuenta regresiva
const deadline = Temporal.PlainDate.from('2025-12-31');
const remaining = deadline.since(today);
console.log(`Faltan ${remaining.days} días`);
```

Parte 6: Ejemplos reales

Coordinar reuniones globales

```javascript
function scheduleMeeting(dateTime, hostTz, attendeeTzs) {
const meeting = Temporal.ZonedDateTime.from(`${dateTime}[${hostTz}]`);

const schedule = new Map();
schedule.set(hostTz, meeting.toString());

for (const tz of attendeeTzs) {
schedule.set(tz, meeting.withTimeZone(tz).toString());
}
return schedule;
}

// Si la reunión es a las 9AM en México,
// ¿qué hora es en Madrid y Buenos Aires?
scheduleMeeting('2025-01-20T09:00:00', 'America/Mexico_City',
['Europe/Madrid', 'America/Argentina/Buenos_Aires']);
```

Días hábiles

```javascript
function addBusinessDays(start, days) {
let current = start;
let remaining = days;

while (remaining > 0) {
current = current.add({ days: 1 });
if (current.dayOfWeek < 6) { // Saltar fines de semana
remaining--;
}
}
return current;
}
```

Parte 7: Migrar desde moment.js

Comparación de código

```javascript
// moment.js
const m = moment('2025-01-15');
m.add(1, 'month'); // ¡Cuidado! Modifica el original

// Temporal
const t = Temporal.PlainDate.from('2025-01-15');
const next = t.add({ months: 1 }); // El original NO cambia

// moment-timezone
moment.tz('2025-01-15 10:30', 'America/Mexico_City');

// Temporal
Temporal.ZonedDateTime.from('2025-01-15T10:30:00[America/Mexico_City]');
```

Estrategia de migración

  1. Código nuevo → usa Temporal desde el principio
  2. Funciones adaptadoras → para conectar código viejo y nuevo
  3. Ir reemplazando → empezando por el código más problemático
  4. Eliminar dependencias → cuando todo esté migrado

```javascript
// Funciones para convertir
function dateToTemporal(date) {
return Temporal.Instant.fromEpochMilliseconds(date.getTime());
}

function temporalToDate(instant) {
return new Date(instant.epochMilliseconds);
}
```

Parte 8: ¿Cómo usarlo hoy?

A finales de 2024, aún no está en los navegadores por defecto.

EntornoEstado
ChromeDisponible con flag
FirefoxEn desarrollo
SafariEn desarrollo
Node.jsDisponible con flag

Con polyfill funciona ya

```bash
npm install @js-temporal/polyfill
```

```javascript
import { Temporal } from '@js-temporal/polyfill';

const today = Temporal.Now.plainDateISO();
```

Detectar soporte nativo

```javascript
async function getTemporal() {
if (typeof globalThis.Temporal !== 'undefined') {
return globalThis.Temporal;
}
const { Temporal } = await import('@js-temporal/polyfill');
return Temporal;
}
```

Conclusión

Temporal es la mejora más importante en el manejo de fechas en toda la historia de JavaScript.

¿Por qué es tan bueno?

  1. Inmutable → no más cambios accidentales
  2. Tipos claros → PlainDate, ZonedDateTime... sabes qué es cada cosa
  3. Sensato → meses desde 1, nombres de métodos lógicos
  4. Zonas horarias bien → horario de verano incluido
  5. Aritmética correcta → por fin funciona como esperas

Próximos pasos:

  1. Prueba el polyfill hoy mismo
  2. Usa Temporal en proyectos nuevos
  3. Planifica dejar moment.js
  4. Sigue el progreso en navegadores

El infierno de Date está por terminar. ¡Temporal llegó! 🚀

javascripttemporal-apidate-timeecmascriptweb-developmenttypescriptmigration

Explora herramientas relacionadas

Prueba estas herramientas gratuitas de Pockit