مرتب‌سازی مُهرهای زمان با دقت بالا با Temporal.Instant

ویژگی‌های وب: Temporal

نمایشگرهای گزارش، خطوط زمانی ممیزی، و واسط‌های کاربری دورسنجی اغلب رویدادهایی را دریافت می‌کنند که مُهرهای زمان ISO 8601 آن‌ها با دقت میکروثانیه یا نانوثانیه توسط سرور، پایگاه داده، یا ساعت بومی (برای مثال، 2026-04-01T12:00:00.000123456Z) ثبت شده‌اند. ازآنجایی‌که شیء قدیمی Date فقط میلی‌ثانیه را ذخیره می‌کند، تجزیه آن رشته‌ها با new Date() یا Date.parse() به‌طور پنهانی ارقام زیر میلی‌ثانیه را کوتاه می‌کند، که باعث می‌شود رویدادهای متمایز در همان میلی‌ثانیه باهم تداخل پیدا کنند و با ترتیبی ناپایدار مرتب شوند.

Temporal.Instant نشان‌دهنده نقطه‌ای دقیق در خط زمان «ساعت هماهنگ جهانی» با دقت نانوثانیه و بدون منطقه زمانی پیوست‌شده است. هنگام تجزیه مُهرهای زمان با دقت بالا که ازقبل ضبط شده‌اند با 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(): قالب‌بندی Temporal.Duration برای کاربران با duration.toLocaleString()، که به Intl.DurationFormat واگذار می‌شود. برای نمایش، به یک 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 برمی‌گرداند که برای بازه‌های طولانی‌تر از حدود ۱۰۴ روز از Number.MAX_SAFE_INTEGER فراتر می‌رود و دقت را بدون اطلاع ازدست می‌دهد. تفریق epochNanoseconds (یک BigInt) دقیق است اما ساختار واحد را نادیده می‌گیرد؛ فاصله‌ها را به‌صورت Temporal.Duration نگه دارید و آن‌ها را با .toLocaleString() قالب‌بندی کنید.
  • باید Instant را با instant.toZonedDateTimeISO(timeZone) به Temporal.ZonedDateTime تبدیل کنید، وقتی که لازم است زمان محلی‌شده رویداد را به کاربر نمایش دهید.

راهبرد جایگزین

پشتیبانی مرورگر و بازگشت به حالت قبلی برای Temporal

برای مرورگرهایی که هنوز از Temporal API بومی پشتیبانی نمی‌کنند، از تشخیص ویژگی استفاده کنید و temporal-polyfill (~۱۵ کیلوبایت 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();
})();