Cómo ordenar marcas de tiempo de alta precisión con Temporal.Instant

Funciones web: Temporal

Los visores de registros, los cronogramas de auditoría y las IU de telemetría suelen transferir eventos cuyas marcas de tiempo ISO 8601 se registraron con precisión de microsegundos o nanosegundos por un servidor, una base de datos o un reloj nativo (por ejemplo, 2026-04-01T12:00:00.000123456Z). Debido a que el objeto Date heredado solo almacena milisegundos, el análisis de esas cadenas con new Date() o Date.parse() trunca de forma silenciosa los dígitos de menos de un milisegundo, lo que hace que los eventos distintos dentro del mismo milisegundo colisionen y se ordenen de forma inestable.

Temporal.Instant representa un punto exacto en la línea de tiempo UTC con precisión de nanosegundos y sin zona horaria adjunta. Cuando se analizan marcas de tiempo de alta precisión pregrabadas con Temporal.Instant.from(), se conserva el valor completo en nanosegundos, se ordena de forma determinística con Temporal.Instant.compare() y se calculan intervalos exactos de Temporal.Duration entre eventos. Ten en cuenta que, en los navegadores, Temporal.Now.instant() se limita de forma deliberada a una resolución de milisegundos o microsegundos para mitigar los ataques de sincronización de Spectre. Usa Temporal.Instant.from() para conservar las marcas de tiempo de nanosegundos que producen los relojes externos y usa performance.now() cuando midas intervalos de menos de un milisegundo de forma local en el cliente.

Cómo implementarla

Para analizar, ordenar y medir intervalos entre marcas de tiempo de alta precisión, haz lo siguiente:

  1. Analiza las marcas de tiempo registradas como Temporal.Instant: Usa Temporal.Instant.from(isoString) en la cadena de marca de tiempo que proporciona el servidor, la base de datos o la línea de registro. Conserva la marca de tiempo que registró la fuente; no la reemplaces por Temporal.Now.instant() en el navegador.
  2. Ordena los eventos de forma cronológica: Usa Temporal.Instant.compare(a, b) como el comparador que se pasa a Array.prototype.sort() o Array.prototype.toSorted(). Compara el valor completo de nanosegundos para que se conserve el orden de menos de un milisegundo.
  3. Calcula intervalos: Usa instant.since(previousInstant) para obtener un Temporal.Duration entre eventos consecutivos. Usa a.equals(b) para detectar eventos con marcas de tiempo idénticas.
  4. Muestra intervalos con toLocaleString(): Aplica formato a Temporal.Duration para los usuarios con duration.toLocaleString(), que delega en Intl.DurationFormat. No lo conviertas en un solo Number de nanosegundos para mostrarlo.
  5. Serializa para la transmisión: Usa Temporal.Instant.prototype.toString() para emitir una cadena ISO 8601 estándar (siempre en UTC con un sufijo Z).

Código de ejemplo: Ordena marcas de tiempo de alta precisión

// Events received with nanosecond-precision ISO 8601 timestamps.
const rawEvents = [
  { nodeId: 'api-2', eventType: 'db.query', ts: '2026-04-01T12:00:00.000123456Z' },
  { nodeId: 'api-1', eventType: 'request.start', ts: '2026-04-01T12:00:00.000120000Z' },
  { nodeId: 'api-2', eventType: 'request.end', ts: '2026-04-01T12:00:00.000987654Z' },
];

// 1. Parse recorded timestamps as Temporal.Instant (never `new Date(ts)`,
// which drops everything below the millisecond).
const events = rawEvents.map(evt => ({
  ...evt,
  timestamp: Temporal.Instant.from(evt.ts),
}));

// 2. Sort chronologically with the static compare() method
function sequenceEvents(items) {
  // Instant.compare returns -1, 0, or 1, which is what sort() expects.
  return [...items].sort((a, b) => Temporal.Instant.compare(a.timestamp, b.timestamp));
}

// 3. Calculate and display the interval between consecutive events
function analyzeTrace(sortedEvents) {
  for (let i = 1; i < sortedEvents.length; i++) {
    const prev = sortedEvents[i - 1];
    const curr = sortedEvents[i];

    // since() returns a Temporal.Duration that retains nanosecond precision.
    const gap = curr.timestamp.since(prev.timestamp);

    // 4. toLocaleString() formats the duration for people (e.g., "3μs 456ns").
    // Avoid gap.total('nanoseconds'): spans over ~104 days exceed Number.MAX_SAFE_INTEGER.
    // 'en-US' is used here for deterministic output; pass undefined to use the user's locale.
    console.log(`${prev.eventType} → ${curr.eventType}: ${gap.toLocaleString('en-US', { style: 'narrow' })}`);
  }
}

analyzeTrace(sequenceEvents(events));

Implementación estratégica y prácticas recomendadas

  • SÍ usa Temporal.Instant.from() para analizar marcas de tiempo de alta precisión registradas en un solo reloj (como un servidor, una base de datos o una canalización de registros) que deben ordenarse o diferenciarse. A diferencia de Temporal.PlainDateTime, Temporal.Instant representa un punto inequívoco en la línea de tiempo universal.
  • SÍ conserva la marca de tiempo que registró la fuente. En los navegadores, Temporal.Now.instant() se limita a la precisión de milisegundos o microsegundos para mitigar los ataques de sincronización y la toma de huellas digitales de Spectre. Usa performance.now() o performance.mark() para los intervalos de clientes locales inferiores a un milisegundo y Temporal.Instant.from() para analizar las marcas de tiempo en nanosegundos de los relojes nativos o del servidor.
  • SÍ uses Temporal.Instant.compare() para ordenar y a.equals(b) para verificar la igualdad; no verifiques el resultado de Temporal.Instant.compare(a, b) === 0 ni compares el de .toString().
  • NO analices cadenas de marcas de tiempo de alta precisión con new Date() o Date.parse() para ordenar, ya que se truncan a milisegundos y producen colisiones.
  • NO contraigas un intervalo a un solo número para mostrarlo. duration.total('nanoseconds') devuelve un Number, que supera Number.MAX_SAFE_INTEGER para intervalos de más de ~104 días y pierde precisión de forma silenciosa. Restar epochNanoseconds (un BigInt) es exacto, pero descarta la estructura de la unidad; mantén los intervalos como Temporal.Duration y dales formato con .toLocaleString().
  • SÍ convierte un Instant en Temporal.ZonedDateTime con instant.toZonedDateTimeISO(timeZone) cuando necesites mostrarle a un usuario la hora local de un evento.

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();
})();