Coordinación de eventos globales con Temporal

Funciones web: Temporal

Programar eventos en diferentes zonas horarias es notoriamente difícil con el objeto Date heredado, en especial en las transiciones del horario de verano (DST), cuando se pueden omitir o repetir horas.

La API de Temporal proporciona Temporal.ZonedDateTime para representar una fecha y hora en una zona horaria específica, lo que controla las transiciones del horario de verano de forma automática y predecible.

Cómo implementarla

Para coordinar eventos globales y controlar posibles conflictos con el horario de verano, haz lo siguiente:

  1. OBLIGATORIO: Crea un ZonedDateTime: Usa Temporal.ZonedDateTime.from() para crear un objeto de fecha y hora que tenga en cuenta la zona horaria.
  2. OBLIGATORIO: Maneja la ambigüedad: Usa la opción disambiguation para controlar el comportamiento cuando una hora es ambigua o no existe (p.ej., durante los cambios de reloj).
  3. OBLIGATORIO: Convertir zonas horarias: Usa .withTimeZone() para ver la hora equivalente en otra ubicación.

Ejemplo: Programación y detección de conflictos

// 1. Define the event time and target time zone
const date = "2025-03-09";
const time = "02:30"; // This time is skipped in New York during Spring Forward
const timeZone = "America/New_York";
const inputStr = `${date}T${time}[${timeZone}]`;

// 2. Detect conflicts using 'reject'
let hasConflict = false;
try {
  // 'reject' throws RangeError if the time is ambiguous or does not exist
  Temporal.ZonedDateTime.from(inputStr, { disambiguation: 'reject' });
} catch (e) {
  if (e instanceof RangeError) {
    hasConflict = true;
    console.log("This time falls in a DST transition gap or overlap.");
  }
}

// 3. Resolve the time safely using 'compatible' (default)
// 'compatible' will resolve to a valid time even if skipped or repeated
const hostTime = Temporal.ZonedDateTime.from(inputStr, { disambiguation: 'compatible' });
console.log(`Resolved time: ${hostTime.toString()}`);

// 4. Convert to another time zone (e.g., Tokyo)
const tokyoTime = hostTime.withTimeZone("Asia/Tokyo");
// Use toLocaleString() for user-facing display instead of manual hour/minute padding
console.log(`Tokyo time: ${tokyoTime.toLocaleString('en-US', { hour: 'numeric', minute: '2-digit' })}`);

Implementación estratégica y prácticas recomendadas

  • SÍ uses Temporal.ZonedDateTime para eventos que están vinculados a una ubicación geográfica específica (como una reunión en una ciudad específica).
  • SÍ usa disambiguation: 'reject' si necesitas detectar conflictos de programación y advertir a los usuarios sobre ellos durante las transiciones del DST.
  • SÍ usa disambiguation: 'compatible' (la opción predeterminada) cuando quieras que el sistema elija automáticamente un momento razonable cuando se produzcan conflictos.
  • NO uses Temporal.PlainDateTime para eventos globales, ya que no contiene información de la zona horaria y no puede tener en cuenta los cambios del horario de verano.
  • SÍ usa .withTimeZone() para calcular la hora equivalente en otras ubicaciones sin mutar el objeto original (los objetos temporales son inmutables).
  • SÍ usa el formato nativo de .toLocaleString() (como { hour: 'numeric', minute: '2-digit' }) para las visualizaciones de tiempo para el usuario en lugar de los cálculos de tiempo y el padding manuales.

Estrategia de resguardo

Resguardos y compatibilidad de navegadores para Temporal

Para los navegadores que aún no admiten la API de Temporal nativa, usa la detección de funciones y carga temporal-polyfill de forma dinámica (~15 KB min+gz).

Ten en cuenta que importar temporal-polyfill no asigna automáticamente el objeto Temporal al alcance global. Asígnalo de forma explícita si tu código depende de globalThis.Temporal.

// Check if Temporal is supported natively
(async () => {
  if (typeof Temporal === 'undefined') {
    // Load the polyfill conditionally
    const module = await import('https://esm.sh/temporal-polyfill');
    globalThis.Temporal = module.Temporal;
    // Extend Date.prototype if needed
    Date.prototype.toTemporalInstant = module.toTemporalInstant;
  }
  initializeApp();
})();