Como classificar carimbos de data/hora de alta precisão com Temporal.Instant

Recursos da Web: Temporal

Os visualizadores de registros, as linhas do tempo de auditoria e as interfaces de telemetria geralmente ingerem eventos com carimbos de data/hora ISO 8601 registrados com precisão de microssegundos ou nanossegundos por um servidor, banco de dados ou relógio nativo (por exemplo, 2026-04-01T12:00:00.000123456Z). Como o objeto Date legado armazena apenas milissegundos, a análise dessas strings com new Date() ou Date.parse() trunca silenciosamente os dígitos de submilisegundos, fazendo com que eventos distintos no mesmo milissegundo colidam e sejam classificados em uma ordem instável.

Temporal.Instant representa um ponto exato na linha do tempo UTC com precisão de nanossegundos e sem fuso horário anexado. Ao analisar carimbos de data/hora de alta precisão pré-gravados com Temporal.Instant.from(), ele preserva o valor total de nanossegundos, classifica de forma determinística com Temporal.Instant.compare() e calcula intervalos exatos de Temporal.Duration entre eventos. Nos navegadores, Temporal.Now.instant() é deliberadamente limitado à resolução de milissegundos ou microssegundos para mitigar ataques de temporização do Spectre. Use Temporal.Instant.from() para preservar os carimbos de data/hora em nanossegundos produzidos por relógios externos e use performance.now() ao medir intervalos de submilisegundos localmente no cliente.

Como implementar

Para analisar, classificar e medir intervalos entre carimbos de data/hora de alta precisão:

  1. Analisar carimbos de data/hora registrados como Temporal.Instant: use Temporal.Instant.from(isoString) na string de carimbo de data/hora fornecida pelo servidor, banco de dados ou linha de registro. Mantenha o carimbo de data/hora registrado pela origem. Não o substitua por Temporal.Now.instant() no navegador.
  2. Classificar eventos cronologicamente: use Temporal.Instant.compare(a, b) como o comparador transmitido para Array.prototype.sort() ou Array.prototype.toSorted(). Ele compara o valor total em nanossegundos para que a ordem de submilissegundos seja preservada.
  3. Calcular intervalos: use instant.since(previousInstant) para receber um Temporal.Duration entre eventos consecutivos. Use a.equals(b) para detectar eventos com carimbos de data/hora idênticos.
  4. Mostrar intervalos com toLocaleString(): formate o Temporal.Duration para usuários com duration.toLocaleString(), que delega para Intl.DurationFormat. Não converta em um único Number de nanossegundos para exibição.
  5. Serializar para transmissão: use Temporal.Instant.prototype.toString() para emitir uma string padrão ISO 8601 (sempre em UTC com um sufixo Z).

Exemplo de código: como classificar carimbos de data/hora de alta precisão

// 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));

Implementação estratégica e práticas recomendadas

  • USE Temporal.Instant.from() para analisar carimbos de data/hora de alta precisão registrados em um único relógio (como um servidor, banco de dados ou pipeline de registro) que precisam ser ordenados ou diferenciados. Ao contrário de Temporal.PlainDateTime, Temporal.Instant representa um ponto inequívoco na linha do tempo universal.
  • MANTENHA o carimbo de data/hora registrado pela origem. Em navegadores, Temporal.Now.instant() é fixado na precisão de milissegundos/microssegundos para mitigação de ataques de temporização Spectre e de impressões digitais. Use performance.now() / performance.mark() para intervalos de clientes locais de submilisegundos e Temporal.Instant.from() para analisar carimbos de data/hora de nanossegundos de servidores ou relógios nativos.
  • USE Temporal.Instant.compare() para classificação e a.equals(b) para verificações de igualdade. Não verifique Temporal.Instant.compare(a, b) === 0 nem compare a saída de .toString().
  • NÃO analise strings de carimbo de data/hora de alta precisão com new Date() ou Date.parse() para ordenação, porque elas são truncadas em milissegundos e produzem colisões.
  • NÃO reduza um intervalo a um único número para exibição. duration.total('nanoseconds') retorna um Number, que excede Number.MAX_SAFE_INTEGER para períodos maiores que ~104 dias e perde precisão silenciosamente. Subtrair epochNanoseconds (um BigInt) é exato, mas descarta a estrutura da unidade. Mantenha os intervalos como Temporal.Duration e formate-os com .toLocaleString().
  • É RECOMENDÁVEL converter um Instant em Temporal.ZonedDateTime com instant.toZonedDateTimeISO(timeZone) quando você precisar mostrar ao usuário o horário localizado de um evento.

Estratégia de fallback

Substitutos e suporte do navegador para Temporal

Para navegadores que ainda não são compatíveis com a API Temporal nativa, use a detecção de recursos e carregue dinamicamente temporal-polyfill (~15 KB min+gz).

A importação de temporal-polyfill não atribui automaticamente o objeto Temporal ao escopo global. Atribua-o explicitamente se o código depender 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();
})();