Temporal.Instant를 사용한 고정밀 타임스탬프 정렬

웹 기능: Temporal

로그 뷰어, 감사 타임라인, 원격 분석 UI는 서버, 데이터베이스 또는 네이티브 시계에서 마이크로초 또는 나노초 정밀도로 ISO 8601 타임스탬프가 기록된 이벤트를 수집하는 경우가 많습니다 (예: 2026-04-01T12:00:00.000123456Z). 기존 Date 객체는 밀리초만 저장하므로 new Date() 또는 Date.parse()를 사용하여 이러한 문자열을 파싱하면 밀리초 미만의 숫자가 자동으로 잘려 동일한 밀리초 내의 고유한 이벤트가 충돌하고 불안정한 순서로 정렬됩니다.

Temporal.Instant은 UTC 타임라인의 정확한 지점을 나노초 단위로 나타내며 시간대가 연결되어 있지 않습니다. Temporal.Instant.from()를 사용하여 사전 녹화된 고정밀 타임스탬프를 파싱할 때 전체 나노초 값을 보존하고 Temporal.Instant.compare()를 사용하여 결정론적으로 정렬하며 이벤트 간의 정확한 Temporal.Duration 간격을 계산합니다. 브라우저에서 Temporal.Now.instant()는 Spectre 타이밍 공격 완화를 위해 의도적으로 밀리초 또는 마이크로초 해상도로 고정됩니다. 외부 시계에서 생성된 나노초 타임스탬프를 보존하려면 Temporal.Instant.from()를 사용하고, 클라이언트에서 로컬로 밀리초 미만 간격을 측정할 때는 performance.now()를 사용하세요.

구현 방법

고정밀 타임스탬프 간의 간격을 파싱, 정렬, 측정하려면 다음 안내를 따르세요.

  1. 기록된 타임스탬프를 Temporal.Instant로 파싱: 서버, 데이터베이스 또는 로그 줄에서 제공하는 타임스탬프 문자열에 Temporal.Instant.from(isoString)를 사용합니다. 소스에서 기록한 타임스탬프를 유지합니다. 브라우저에서 Temporal.Now.instant()로 대체하지 않습니다.
  2. 이벤트를 시간순으로 정렬: Temporal.Instant.compare(a, b)를 Array.prototype.sort() 또는 Array.prototype.toSorted()에 전달된 비교자로 사용합니다. 전체 나노초 값을 비교하므로 밀리초 미만 순서가 유지됩니다.
  3. 간격 계산: instant.since(previousInstant)를 사용하여 연속된 이벤트 간의 Temporal.Duration를 가져옵니다. a.equals(b)를 사용하여 타임스탬프가 동일한 이벤트를 감지합니다.
  4. toLocaleString()로 간격 표시: Intl.DurationFormat에 위임하는 duration.toLocaleString()를 사용하여 사용자의 Temporal.Duration 형식을 지정합니다. 표시를 위해 단일 Number(나노초)로 변환하지 마세요.
  5. 전송을 위한 직렬화: Temporal.Instant.prototype.toString()를 사용하여 표준 ISO 8601 문자열을 내보냅니다 (항상 UTC, Z 서픽스 사용).

예시 코드: 고정밀 타임스탬프 정렬

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

전략적 구현 및 권장사항

  • 순서를 지정하거나 차이를 계산해야 하는 단일 시계 (예: 서버, 데이터베이스, 로그 파이프라인)에 기록된 고정밀 타임스탬프를 파싱하는 데 Temporal.Instant.from()를 사용하세요. Temporal.PlainDateTime와 달리 Temporal.Instant는 범용 타임라인의 명확한 시점을 나타냅니다.
  • 권장사항: 소스에서 기록한 타임스탬프를 유지합니다. 브라우저에서 Temporal.Now.instant()는 Spectre 타이밍 공격 및 디지털 지문 완화를 위해 밀리초/마이크로초 정밀도로 고정됩니다. 로컬 밀리초 미만 클라이언트 간격에는 performance.now()/performance.mark()를 사용하고 서버 또는 네이티브 시계에서 나노초 타임스탬프를 파싱하려면 Temporal.Instant.from()를 사용하세요.
  • 정렬에는 Temporal.Instant.compare()를 사용하고 동등성 검사에는 a.equals(b)를 사용합니다. Temporal.Instant.compare(a, b) === 0를 확인하거나 .toString() 출력을 비교하지 마세요.
  • new Date() 또는 Date.parse()를 사용하여 순서 지정을 위해 고정밀 타임스탬프 문자열을 파싱하지 마세요. 밀리초로 잘리고 충돌이 발생합니다.
  • 간격을 단일 숫자로 축소하여 표시하지 않습니다. duration.total('nanoseconds')는 Number를 반환하며, 이는 약 104일보다 긴 기간의 경우 Number.MAX_SAFE_INTEGER를 초과하고 자동으로 정밀도를 잃습니다. epochNanoseconds(BigInt)을 빼면 정확하지만 단위 구조가 삭제됩니다. 간격을 Temporal.Duration로 유지하고 .toLocaleString()로 형식을 지정하세요.
  • 사용자에게 현지화된 이벤트의 벽시계 시간을 표시해야 하는 경우 instant.toZonedDateTimeISO(timeZone)를 사용하여 Instant를 Temporal.ZonedDateTime로 변환해야 합니다(DO).

대체 전략

Temporal의 대체 및 브라우저 지원

아직 네이티브 Temporal API를 지원하지 않는 브라우저의 경우 기능 감지를 사용하고 temporal-polyfill (~15KB min+gz)를 동적으로 로드합니다.

temporal-polyfill를 가져와도 Temporal 객체가 전역 범위에 자동으로 할당되지 않습니다. 코드가 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();
})();