ترتيب الطوابع الزمنية العالية الدقة باستخدام Temporal.Instant

ميزات الويب: Temporal

تستوعب واجهات عرض السجلّات والمخططات الزمنية للتدقيق وواجهات مستخدم القياس عن بُعد في كثير من الأحيان الأحداث التي تم تسجيل طوابعها الزمنية بتنسيق 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(): يمكنك تنسيق Temporal.Duration للمستخدمين باستخدام duration.toLocaleString()، والذي يفوّض إلى Intl.DurationFormat. لا تحوِّل إلى Number واحدة من النانو ثانية للعرض.
  5. التسلسل للنقل: استخدِم Temporal.Instant.prototype.toString() لإصدار سلسلة ISO 8601 عادية (دائمًا بالتوقيت العالمي المنسّق مع اللاحقة 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 للفترات التي تزيد عن 104 أيام تقريبًا وتفقد الدقة بدون إشعار. يؤدي طرح epochNanoseconds (BigInt) إلى الحصول على نتيجة دقيقة، ولكنّه يتجاهل بنية الوحدة. يُرجى الاحتفاظ بالفواصل الزمنية على شكل Temporal.Duration وتنسيقها باستخدام .toLocaleString().
  • يجب تحويل Instant إلى Temporal.ZonedDateTime باستخدام instant.toZonedDateTimeISO(timeZone) عندما تحتاج إلى عرض الوقت المحلي لحدث ما للمستخدم.

استراتيجية الإجراءات الاحتياطية

الخيارات الاحتياطية وتوافق المتصفّحات مع Temporal

بالنسبة إلى المتصفّحات التي لا تتيح استخدام واجهة برمجة التطبيقات Temporal الأصلية، استخدِم ميزة الكشف عن الميزات وحمِّل temporal-polyfill بشكل ديناميكي (بحجم 15 كيلوبايت تقريبًا 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();
})();