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:
- Analiza las marcas de tiempo registradas como
Temporal.Instant: UsaTemporal.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 porTemporal.Now.instant()en el navegador. - Ordena los eventos de forma cronológica: Usa
Temporal.Instant.compare(a, b)como el comparador que se pasa aArray.prototype.sort()oArray.prototype.toSorted(). Compara el valor completo de nanosegundos para que se conserve el orden de menos de un milisegundo. - Calcula intervalos: Usa
instant.since(previousInstant)para obtener unTemporal.Durationentre eventos consecutivos. Usaa.equals(b)para detectar eventos con marcas de tiempo idénticas. - Muestra intervalos con
toLocaleString(): Aplica formato aTemporal.Durationpara los usuarios conduration.toLocaleString(), que delega enIntl.DurationFormat. No lo conviertas en un soloNumberde nanosegundos para mostrarlo. - Serializa para la transmisión: Usa
Temporal.Instant.prototype.toString()para emitir una cadena ISO 8601 estándar (siempre en UTC con un sufijoZ).
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 deTemporal.PlainDateTime,Temporal.Instantrepresenta 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. Usaperformance.now()operformance.mark()para los intervalos de clientes locales inferiores a un milisegundo yTemporal.Instant.from()para analizar las marcas de tiempo en nanosegundos de los relojes nativos o del servidor. - SÍ uses
Temporal.Instant.compare()para ordenar ya.equals(b)para verificar la igualdad; no verifiques el resultado deTemporal.Instant.compare(a, b) === 0ni compares el de.toString(). - NO analices cadenas de marcas de tiempo de alta precisión con
new Date()oDate.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 unNumber, que superaNumber.MAX_SAFE_INTEGERpara intervalos de más de ~104 días y pierde precisión de forma silenciosa. RestarepochNanoseconds(unBigInt) es exacto, pero descarta la estructura de la unidad; mantén los intervalos comoTemporal.Durationy dales formato con.toLocaleString(). - SÍ convierte un
InstantenTemporal.ZonedDateTimeconinstant.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();
})();