پشتیبانی از سیستم‌های تقویم جهانی با Temporal

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

شیء Date جاوا اسکریپت سنتی براساس تقویم گریگوری پیش‌نگر است، که ساختن برنامه‌های کاربردی را برای کاربرانی که به سیستم‌های تقویم دیگر، مانند تقویم اسلامی (قمری)، عبری (قمری-شمسی)، یا چینی (قمری-شمسی) متکی هستند، دشوار می‌کند. توسعه‌دهندگان قبلاً برای پشتیبانی از این سیستم‌ها باید به کتابخانه‌های پیچیده طرف سوم یا محاسبات دستی تکیه می‌کردند.

Temporal API پشتیبانی درجه یکی برای سیستم‌های تقویم متعدد ارائه می‌دهد. با مرتبط کردن شناسه تقویم با اشیای تاریخ، Temporal محاسبات پیچیده و قالب‌بندی موردنیاز برای زمینه‌های فرهنگی مختلف را به‌صورت بومی مدیریت می‌کند.

نحوه پیاده‌سازی

برای پشتیبانی از سیستم‌های تقویم جهانی بااستفاده از Temporal:

  1. مرتبط کردن تقویم (اجباری): هنگام ایجاد یا تبدیل شیء Temporal (مانند Temporal.PlainDate)، باید سیستم تقویم موردنظر را بااستفاده از withCalendar() مشخص کنید تا عملیات حساس به تقویم انجام شود.
  2. استفاده از شناسه‌های پایدار (برای تقویم‌های شمسی-قمری الزامی است): برای شناسایی ماه‌های خاص در سال‌های مختلف در تقویم‌های شمسی-قمری، باید از monthCode به‌جای نمایه عددی month استفاده کنید. فقط برای تقویم‌هایی که از ماه کبیسه استفاده می‌کنند، مانند تقویم عبری و چینی، از این گزینه استفاده کنید.
  3. رعایت ثابت‌های تقویم (اجباری): هنگام تکرار در ماه‌ها یا روزها، نباید مقادیر ثابتی را فرض کنید (مانند ۱۲ ماه در سال یا ۳۱ روز در ماه). از ویژگی‌هایی مانند 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 به‌عنوان حد بالا هنگام تکرار در ماه‌ها استفاده کنید، به‌جای اینکه فرض کنید ۱۲ است.
  • باید از monthCode برای شناسایی ماه‌های خاص در تقویم‌هایی که از ماه‌های کبیسه استفاده می‌کنند (مانند عبری یا چینی)، صرف‌نظر از سال، استفاده کنید.
  • فرض نکنید date.month === 12 آخرین ماه سال است. از date.month === date.monthsInYear استفاده کنید.
  • فرض نکنید که inLeapYear === true به این معنی است که سال فقط یک روز طولانی‌تر است. در تقویم‌های شمسی-قمری، ممکن است یک ماه کبیسه کامل اضافه شود.
  • باید از Temporal.PlainDate.compare() هنگام مقایسه دو تاریخ در یک سیستم تقویم خاص استفاده کنید، به‌جای اینکه ویژگی‌های سال/ماه/روز را به‌صورت دستی مقایسه کنید.
  • باید از toLocaleString() برای قالب‌بندی تاریخ‌ها برای کاربران استفاده کنید، نه از الحاق رشته‌ای دستی.
  • باید پیش‌از ایجاد کردن اشیای Temporal مختص تقویم، بااستفاده از Intl.supportedValuesOf('calendar') تأیید کنید که سیستم تقویم هدف توسط محیط پشتیبانی می‌شود.
  • توجه داشته باشید که برخی تقویم‌ها (مانند انواع تقویم اسلامی) ممکن است به‌جای محاسبات ثابت، بر مشاهده بصری متکی باشند. ‫Temporal API از پیاده‌سازی Intl محیط پیروی می‌کند که معمولاً از تقریب‌های محاسبه‌شده استفاده می‌کند. برای محاسبات تاریخ‌های مهم فرهنگی یا مذهبی، با کارشناسان حوزه مربوطه مشورت کنید یا از کتابخانه‌های تخصصی استفاده کنید.
  • باید هنگام استفاده از تقویم‌هایی که از عصرها استفاده می‌کنند (مانند تقویم ژاپنی یا بودایی)، بااستفاده از toLocaleString()، نام عصرها را درنظر بگیرید.

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

پشتیبانی مرورگر و بازگشت به حالت قبلی برای 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();
})();