دعم أنظمة التقويم العالمية باستخدام Temporal

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

يستند كائن Date التقليدي في JavaScript إلى التقويم الميلادي الاستباقي، ما يجعل من الصعب إنشاء تطبيقات للمستخدمين الذين يعتمدون على أنظمة تقويم أخرى، مثل التقويم الإسلامي (القمري) أو العبري (القمري الشمسي) أو الصيني (القمري الشمسي). في السابق، كان على المطوّرين الاعتماد على مكتبات معقّدة تابعة لجهات خارجية أو إجراء عمليات حسابية يدوية لإتاحة هذه الأنظمة.

توفّر واجهة برمجة التطبيقات Temporal دعمًا من الدرجة الأولى لأنظمة تقويم متعددة. من خلال ربط معرّف التقويم بعناصر التاريخ، يتعامل Temporal مع العمليات الحسابية المعقّدة والتنسيق المطلوب للسياقات الثقافية المختلفة بشكلٍ أصلي.

كيفية الإعداد

لدعم أنظمة التقويم العالمية باستخدام Temporal:

  1. ربط تقويم (إلزامي): عند إنشاء عنصر زمني أو تحويله (مثل Temporal.PlainDate)، يجب تحديد نظام التقويم المطلوب باستخدام withCalendar() لإجراء عمليات حساسة للتقويم.
  2. استخدام معرّفات ثابتة (إلزامي للتقاويم القمرية الشمسية): يجب استخدام monthCode بدلاً من الفهرس الرقمي month لتحديد أشهر معيّنة على مدار السنوات في التقاويم القمرية الشمسية. لا تستخدِم هذا الخيار إلا مع التقاويم التي تستخدم الأشهر الكبيسة، مثل التقويمَين العبري والصيني.
  3. الالتزام بثوابت التقويم (إلزامي): عند التكرار خلال الأشهر أو الأيام، يجب عدم افتراض قيم ثابتة (مثل 12 شهرًا في السنة أو 31 يومًا في الشهر). استخدِم سمات مثل monthsInYear وdaysInMonth لضمان عمل الرمز البرمجي في جميع التقاويم.
  4. استخدام المقارنات المتوافقة مع التقويم (إلزامي): عند مقارنة التواريخ ضمن نظام تقويم معيّن، يجب استخدام Temporal.PlainDate.compare() بدلاً من مقارنة سمات السنة/الشهر/اليوم يدويًا. يتعامل هذا الإجراء بشكل صحيح مع الترتيب الزمني ضمن قواعد التقويم.

مثال على الرمز البرمجي: تحويل التقاويم وتكرارها

// 1. Helper to check calendar support
function isCalendarSupported(calendarId) {
  try {
    return Intl.supportedValuesOf('calendar').includes(calendarId);
  } catch {
    // Fallback for environments where supportedValuesOf is not available
    return false;
  }
}

// 2. Get current date in default ISO 8601 calendar
const isoDate = Temporal.Now.plainDateISO();

// 3. Convert to Hebrew calendar if supported
const calendarId = 'hebrew';
const targetDate = isCalendarSupported(calendarId) 
  ? isoDate.withCalendar(calendarId)
  : isoDate; // Fallback to ISO if not supported

if (targetDate.calendar.id !== calendarId) {
  console.warn(`Calendar ${calendarId} not supported; falling back to ISO 8601`);
}

// 4. Log properties specific to the calendar
console.log(`Calendar: ${targetDate.calendar.id}`);
console.log(`Year: ${targetDate.year}`);
console.log(`Month Code: ${targetDate.monthCode}`); // Stable across leap years

// 5. Safely iterate through months in the current year
for (let m = 1; m <= targetDate.monthsInYear; m++) {
  console.log(`Month ${m} has ${targetDate.with({ month: m }).daysInMonth} days.`);
}

// 6. Compare dates within the same calendar
const today = Temporal.Now.plainDateISO().withCalendar(calendarId);
const comparison = Temporal.PlainDate.compare(targetDate, today);
const relative = comparison < 0 ? 'Past' : comparison > 0 ? 'Future' : 'Today';
console.log(`Timeline: ${relative}`);

// 7. Format for display using toLocaleString (including era for era-based calendars like 'japanese')
const localizedDisplay = targetDate.toLocaleString('en-u-ca-hebrew', {
  day: 'numeric',
  month: 'long',
  year: 'numeric',
  era: 'long'
});

التنفيذ الاستراتيجي وأفضل الممارسات

  • يجب استخدام monthsInYear كحدّ أقصى عند تكرار الأشهر، بدلاً من افتراض أنّ عددها 12.
  • يجب استخدام monthCode لتحديد أشهر معيّنة في التقاويم التي تستخدم الأشهر الكبيسة (مثل التقويم العبري أو الصيني)، بغض النظر عن السنة.
  • لا تفترض أنّ date.month === 12 هو الشهر الأخير من العام. استخدِم date.month === date.monthsInYear.
  • لا تفترض أنّ inLeapYear === true تعني أنّ السنة أطول بيوم واحد فقط. وفي التقاويم القمرية الشمسية، قد يضيف شهرًا كبيسًا كاملاً.
  • يجب استخدام Temporal.PlainDate.compare() عند مقارنة تاريخَين في نظام تقويم معيّن بدلاً من مقارنة سمات السنة/الشهر/اليوم يدويًا.
  • يجب استخدام toLocaleString() لتنسيق التواريخ للمستخدمين بدلاً من دمج السلاسل يدويًا.
  • يجب التحقّق من أنّ نظام التقويم المستهدَف متوافق مع البيئة التي تستخدم Intl.supportedValuesOf('calendar') قبل إنشاء عناصر Temporal خاصة بالتقويم.
  • يجب الانتباه إلى أنّ بعض التقاويم (مثل التقويم الهجري) قد تعتمد على الرؤية البصرية بدلاً من الحسابات الثابتة. تتّبع واجهة برمجة التطبيقات Temporal عملية تنفيذ Intl في البيئة، والتي تستخدم عادةً قيمًا تقريبية محسوبة. لإجراء حسابات دقيقة للتواريخ الثقافية أو الدينية، يُرجى مراجعة خبراء في هذا المجال أو استخدام مكتبات متخصصة.
  • يجب مراعاة أسماء العصور عند استخدام التقاويم التي تستخدم العصور (مثل التقويم الياباني والبوذي)، وذلك باستخدام toLocaleString().

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

الخيارات الاحتياطية وتوافق المتصفّحات مع 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();
})();