Sắp xếp dấu thời gian có độ chính xác cao bằng Temporal.Instant

Tính năng web: Temporal

Trình xem nhật ký, dòng thời gian kiểm tra và giao diện người dùng đo từ xa thường nhập các sự kiện có dấu thời gian ISO 8601 được máy chủ, cơ sở dữ liệu hoặc đồng hồ gốc ghi lại với độ chính xác đến micrô giây hoặc nano giây (ví dụ: 2026-04-01T12:00:00.000123456Z). Vì đối tượng Date cũ chỉ lưu trữ mili giây, nên việc phân tích cú pháp các chuỗi đó bằng new Date() hoặc Date.parse() sẽ tự động cắt bớt các chữ số dưới mili giây, khiến các sự kiện riêng biệt trong cùng một mili giây xung đột và sắp xếp theo thứ tự không ổn định.

Temporal.Instant biểu thị một điểm chính xác trên dòng thời gian UTC với độ chính xác đến nano giây và không có múi giờ đi kèm. Khi phân tích cú pháp dấu thời gian có độ chính xác cao được ghi trước bằng Temporal.Instant.from(), dấu thời gian này sẽ giữ nguyên giá trị nano giây đầy đủ, sắp xếp một cách tất định bằng Temporal.Instant.compare() và tính toán khoảng thời gian Temporal.Duration chính xác giữa các sự kiện. Xin lưu ý rằng trong các trình duyệt, Temporal.Now.instant() được cố ý giới hạn giá trị ở độ phân giải mili giây hoặc micrô giây để giảm nhẹ cuộc tấn công định thời Spectre – hãy sử dụng Temporal.Instant.from() để giữ lại dấu thời gian nano giây do đồng hồ bên ngoài tạo ra và sử dụng performance.now() khi đo lường khoảng thời gian dưới mili giây cục bộ trong ứng dụng.

Cách triển khai

Để phân tích cú pháp, sắp xếp và đo lường khoảng thời gian giữa các dấu thời gian có độ chính xác cao:

  1. Phân tích cú pháp dấu thời gian đã ghi lại dưới dạng Temporal.Instant: Sử dụng Temporal.Instant.from(isoString) trên chuỗi dấu thời gian do máy chủ, cơ sở dữ liệu hoặc dòng nhật ký cung cấp. Giữ dấu thời gian mà nguồn đã ghi lại; không thay thế bằng Temporal.Now.instant() trong trình duyệt.
  2. Sắp xếp sự kiện theo trình tự thời gian: Sử dụng Temporal.Instant.compare(a, b) làm trình so sánh được truyền đến Array.prototype.sort() hoặc Array.prototype.toSorted(). Hàm này so sánh toàn bộ giá trị nano giây để giữ nguyên thứ tự dưới mili giây.
  3. Tính khoảng thời gian: Sử dụng instant.since(previousInstant) để nhận Temporal.Duration giữa các sự kiện liên tiếp. Hãy sử dụng a.equals(b) để phát hiện các sự kiện có dấu thời gian giống hệt nhau.
  4. Hiển thị khoảng thời gian bằng toLocaleString(): Định dạng Temporal.Duration cho người dùng bằng duration.toLocaleString(), uỷ quyền cho Intl.DurationFormat. Đừng chuyển đổi thành một Number nano giây để hiển thị.
  5. Chuyển đổi tuần tự để truyền: Sử dụng Temporal.Instant.prototype.toString() để phát ra một chuỗi ISO 8601 chuẩn (luôn ở dạng UTC có hậu tố Z).

Mã ví dụ: Sắp xếp dấu thời gian có độ chính xác cao

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

Triển khai chiến lược và các phương pháp hay nhất

  • NÊN sử dụng Temporal.Instant.from() để phân tích cú pháp dấu thời gian có độ chính xác cao được ghi lại theo một đồng hồ duy nhất (chẳng hạn như máy chủ, cơ sở dữ liệu hoặc quy trình ghi nhật ký) cần được sắp xếp hoặc phân biệt. Không giống như Temporal.PlainDateTime, Temporal.Instant biểu thị một điểm rõ ràng trên dòng thời gian chung.
  • NÊN giữ dấu thời gian do nguồn ghi lại. Trong trình duyệt, Temporal.Now.instant() được giới hạn giá trị ở độ chính xác mili giây/micro giây để giảm nhẹ tấn công thời gian Spectre và tạo vân tay số; sử dụng performance.now() / performance.mark() cho Khoảng thời gian cục bộ của ứng dụng dưới mili giây và Temporal.Instant.from() để phân tích cú pháp dấu thời gian nano giây từ máy chủ hoặc đồng hồ gốc.
  • NÊN sử dụng Temporal.Instant.compare() để sắp xếp và a.equals(b) để kiểm tra tính bằng nhau; không kiểm tra kết quả đầu ra Temporal.Instant.compare(a, b) === 0 hoặc so sánh kết quả đầu ra .toString().
  • KHÔNG phân tích cú pháp chuỗi dấu thời gian có độ chính xác cao bằng new Date() hoặc Date.parse() để sắp xếp, vì các chuỗi này sẽ cắt bớt thành mili giây và gây ra xung đột.
  • KHÔNG thu gọn khoảng thời gian thành một số duy nhất để hiển thị. duration.total('nanoseconds') trả về một Number, vượt quá Number.MAX_SAFE_INTEGER cho các khoảng thời gian dài hơn ~104 ngày và mất độ chính xác một cách thầm lặng. Phép trừ epochNanoseconds (BigInt) là chính xác nhưng loại bỏ cấu trúc đơn vị; giữ khoảng thời gian là Temporal.Duration và định dạng chúng bằng .toLocaleString().
  • NÊN chuyển đổi Instant thành Temporal.ZonedDateTime bằng instant.toZonedDateTimeISO(timeZone) khi bạn cần hiển thị giờ đồng hồ được bản địa hoá của một sự kiện cho người dùng.

Chiến lược dự phòng

Phương án dự phòng và hỗ trợ trình duyệt cho Temporal

Đối với các trình duyệt chưa hỗ trợ API Temporal gốc, hãy sử dụng tính năng phát hiện tính năng và tải động temporal-polyfill (~15 KB min+gz).

Xin lưu ý rằng việc nhập temporal-polyfill không tự động chỉ định đối tượng Temporal cho phạm vi toàn cục. Hãy chỉ định rõ ràng nếu mã của bạn dựa vào 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();
})();