¿Cansado de que las fechas te arruinen la noche? Bienvenido a Temporal
Tiempo estimado de lectura: 6 min
- Temporal reemplaza a Date para lógica de negocio: menos bugs y contratos más claros.
- Almacena Instants en DB (ISO UTC canonical) y convierte a zonas locales solo en UI.
- Polyfill hoy, tipos con TypeScript 6.0: feature-detect y carga condicional.
- Migración por capas y tests: evita PR monstruo, prioriza fronteras y añade reglas ESLint.
Introducción
Voy directo: Temporal no es una nueva API bonita para mirar. Es el fin del “setDate + pray”. Si adoptas esto con criterio, reduces bugs, clarificas contratos y dejas de escribir parsers para timestamps por toda la app. Te explico cómo hacerlo sin romper producción.
Resumen rápido (lectores con prisa)
Temporal es la API moderna para manejar tiempo en JS. Usa Instant para puntos absolutos (DB/logs), ZonedDateTime para eventos con zona y PlainDate para fechas sin hora. TypeScript 6.0 trae tipos; usa el polyfill donde el runtime no lo implemente.
Configuración y polyfill
TypeScript 6.0 trae los tipos de Temporal, pero el runtime puede no implementarlo aún en todos los motores.
Para usarlo hoy en Node o navegadores, instala el polyfill oficial:
npm install @js-temporal/polyfill
En el punto de entrada (server o client) haz feature-detect y carga el polyfill solo si hace falta:
if (!globalThis.Temporal) {
await import('@js-temporal/polyfill');
}
Asegura tsconfig:
{
"compilerOptions": {
"target": "esnext",
"lib": ["esnext"],
"strict": true
}
}
Qué cambia en tu arquitectura (reglas rápidas)
- No más
new Date()para lógica de negocio. - Almacena timestamps en DB como Instant (ISO) — UTC canonical.
- Muestra/convierte a zonas locales con ZonedDateTime solo donde importe (UI, emails, calendarios).
- En tests y serialización: convierte a strings para persistir; rehidrata con
Temporal.from()al cargar.
Casos prácticos y patrones — copia, pega y aplícalo
1) Obtener “ahora” correctamente
// Instant (UTC preciso, para auditoría/logs)
const nowInstant = Temporal.Now.instant();
// ZonedDateTime (evento con zona)
const nowZoned = Temporal.Now.zonedDateTimeISO();
// PlainDate (cumpleaños, sin hora ni zona)
const today = Temporal.Now.plainDateISO();
2) Sumar y restar sin mutar
const hoy = Temporal.Now.plainDateISO();
const dentroDe7Dias = hoy.add({ days: 7 }); // creado nuevo, hoy no cambia
3) Guardar en BD (mejor práctica)
- Guarda Instants como strings:
instant.toString()→ “2024-05-01T12:00:00Z”. - Razonamiento: Instant es un punto absoluto; si reconstituyes en otro país, sigues teniendo el mismo momento.
const timestamp = Temporal.Now.instant().toString();
// INSERT INTO events (created_at) VALUES (timestamp);
Recuperación:
const instantFromDb = Temporal.Instant.from(dbValue);
const zonedInMadrid = instantFromDb.toZonedDateTimeISO('Europe/Madrid');
4) Mostrar en UI (zona del usuario)
const instant = Temporal.Instant.from(event.created_at);
const inUserTZ = instant.toZonedDateTimeISO(user.timeZone);
const formatted = inUserTZ.toLocaleString('es-ES', { dateStyle: 'medium', timeStyle: 'short' });
5) Schedules y DST (sin manualidades)
const vuelo = Temporal.ZonedDateTime.from('2024-10-15T14:30:00+09:00[Asia/Tokyo]');
const llegadaMadrid = vuelo.withTimeZone('Europe/Madrid');
// Temporal aplica reglas de DST correctamente.
Migración práctica y estrategia (no pegues un PR monstruo)
Plan corto y efectivo:
1. Detecta usos
- grep/rg por
new Date(,Date.now(),.toISOString(),getUTC*,setUTC*. - Haz una lista priorizada: endpoints, parsers de JSON, jobs, filas de cola.
2. Protege el repo
Añade rule de ESLint que prohíba new Date en código nuevo:
// .eslintrc.json
"rules": {
"no-restricted-syntax": [
"error",
{
"selector": "NewExpression[callee.name='Date']",
"message": "Usa Temporal en lugar de Date"
}
]
}
3. Cambia en capas
- Fronteras primero: parsers de requests, handlers, webhooks.
- Library layer: utilidades de fecha centralizadas.
- UI: formateadores y locales.
4. Persistencia y API contract
- Define y documenta: “todos los timestamps en la DB son Instant ISO strings”.
- Añade validaciones en los endpoints que aceptan timestamps (Zod / Zod schemas o runtime checks).
5. Tests y CI
- En jest/mocha, añade el polyfill en setupTests:
import '@js-temporal/polyfill'; - Añade pruebas para zona horaria, DST y serialización.
Peculiaridades y errores que verás (y cómo arreglarlos)
- Serialización: Temporal types no siempre serializan como esperas en
JSON.stringify. Convierte a string explícitamente. - Redux / Hydration: almacena ISO strings en store si necesitas serialización. Temporal objects son inmutables pero no pensados para serializar automáticamente.
- Comparaciones: usa
Temporal.PlainDate.compareo Instant.avoid numeric timezone math. - Interoperabilidad con libs: elimina gradualmente date-fns/moment; si dependes de ellas, mantén adaptadores hasta reemplazar lógica.
Decisiones de diseño: Instant vs ZonedDateTime vs PlainDate
- Instant → logs, audit, DB primary timestamp.
- ZonedDateTime → eventos programados que dependen de la hora local.
- PlainDate → cumpleaños, fechainterna sin hora.
- Duration → expiraciones, TTLs, duraciones humanas.
Ejemplo real: reserva de vuelo (correcto)
- Guardar en DB:
Vuelo.departureInstant(Instant) - Guardar metadatos:
Vuelo.departureTZ = 'Asia/Tokyo' - Mostrar en UI:
instant.toZonedDateTimeISO(tz).toLocaleString(...)
Performance y bundle
El polyfill tiene coste. Si tu app es frontend, lazy-load the polyfill: solo usuarios que lo necesiten (browsers sin Temporal nativo) lo cargarán.
En server (Node), añade polyfill en arranque. No suele ser un problema de perf si lo colocas correctamente.
Checklist rápido antes de mergear cambios de fecha
- Todos los endpoints aceptan/retornan ISO Instant cuando corresponde.
- Tests cubren conversiones entre zonas y casos límites (fin de mes, cambio DST).
- No quedan
new Date()en la lógica de negocio. - Documentación para frontend/backend: cómo serializar y rehidratar.
Metáfora breve: por qué vale la pena
Date era un martillo con un tornillo. Temporal es el juego de herramientas correcto para el tiempo. No es más trabajo: es menos debugging.
Cierre (CTA claro)
¿Quieres el script que escanea tu repo, lista todas las ocurrencias de Date y genera un plan de migración automático por prioridad? Respóndeme con “MIGRAR FECHAS” y te lo entrego: branch de prueba, report con 100 entradas ordenadas, y PR template para cada cambio.
Esto no acaba aquí. La siguiente nota: cómo reescribir utilidades de date-fns a Temporal con transformaciones seguras y codemods semi-automáticos. ¿Lo hacemos?
Si te interesa integrar estas prácticas con flujos de trabajo y automatización de repos, revisa Dominicode Labs para plantillas y herramientas que aceleran migraciones y codemods.
FAQ
¿Por qué usar Temporal en lugar de Date?
Temporal proporciona tipos específicos (Instant, ZonedDateTime, PlainDate) que aclaran contratos y evitan errores comunes asociados a zonas y mutabilidad de Date.
¿Debo cambiar todo el código de golpe?
No. La estrategia recomendada es migrar por capas: fronteras primero (parsers/handlers), luego librerías internas y finalmente UI. Evita PRs monolíticos.
Cómo debo almacenar timestamps en la base de datos?
Almacena Instants como ISO strings en UTC (ej. “2024-05-01T12:00:00Z”). Reconstituye con Temporal.Instant.from() al leer.
Qué pasa con la serialización y Redux?
Temporal objects no siempre serializan con JSON.stringify. Guarda ISO strings en el store si necesitas serializar/hidratar.
Necesito un polyfill en producción?
Sí si tus entornos (navegadores/Node) no implementan Temporal nativamente. Usa feature-detect y lazy-load en frontend; carga en arranque en server.
Cómo asegurar que no queden Date en el repo?
Usa búsquedas (grep/rg) y añade una regla ESLint que prohíba new Date. Prioriza endpoints y parsers para corregir primero las fronteras.

Leave a Reply