Prise en charge des systèmes de calendrier mondiaux avec Temporal

Fonctionnalités Web : Temporal

L'objet JavaScript Date traditionnel est basé sur un calendrier grégorien proleptique, ce qui complique la création d'applications pour les utilisateurs qui s'appuient sur d'autres systèmes de calendrier, tels que les calendriers islamique (lunaire), hébreu (lunisolaire) ou chinois (lunisolaire). Auparavant, les développeurs devaient s'appuyer sur des bibliothèques tierces complexes ou des calculs manuels pour prendre en charge ces systèmes.

L'API Temporal est compatible avec plusieurs systèmes de calendrier. En associant un identifiant de calendrier à des objets de date, Temporal gère de manière native les calculs et la mise en forme complexes requis pour différents contextes culturels.

Implémentation

Pour prendre en charge les systèmes de calendrier mondiaux à l'aide de Temporal :

  1. Associer un calendrier (obligatoire) : lorsque vous créez ou convertissez un objet temporel (comme Temporal.PlainDate), vous devez spécifier le système de calendrier souhaité à l'aide de withCalendar() pour effectuer des opérations sensibles au calendrier.
  2. Utilisez des identifiants stables (obligatoire pour les calendriers lunisolaires) : vous devez utiliser monthCode plutôt que l'index numérique month pour identifier des mois spécifiques sur plusieurs années dans les calendriers lunisolaires. Utilisez cette option uniquement pour les calendriers qui utilisent des mois bissextiles, comme les calendriers hébreu et chinois.
  3. Respecter les invariants du calendrier (obligatoire) : lorsque vous parcourez les mois ou les jours, vous ne devez pas supposer de valeurs fixes (comme 12 mois dans une année ou 31 jours dans un mois). Utilisez des propriétés telles que monthsInYear et daysInMonth pour vous assurer que votre code fonctionne sur tous les agendas.
  4. Utilisez des comparaisons tenant compte du calendrier (obligatoire) : lorsque vous comparez des dates dans un système de calendrier spécifique, vous devez utiliser Temporal.PlainDate.compare() au lieu de comparer manuellement les propriétés d'année/mois/jour. Cela permet de gérer correctement l'ordre chronologique dans les règles d'agenda.

Exemple de code : conversion et itération d'agendas

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

Implémentation stratégique et bonnes pratiques

  • À FAIRE : Utilisez monthsInYear comme limite supérieure lorsque vous parcourez les mois, plutôt que de supposer qu'il y en a 12.
  • À FAIRE : Utilisez monthCode pour identifier des mois spécifiques dans les calendriers qui utilisent des mois intercalaires (par exemple, hébreu ou chinois), quelle que soit l'année.
  • NE SUPPOSEZ PAS que date.month === 12 est le dernier mois de l'année. Utilisez date.month === date.monthsInYear.
  • NE PARTEZ PAS DU PRINCIPE que inLeapYear === true implique que l'année ne dure qu'un jour de plus. Dans les calendriers lunisolaires, il peut ajouter un mois bissextile complet.
  • À FAIRE : Utilisez Temporal.PlainDate.compare() lorsque vous comparez deux dates dans un système de calendrier spécifique au lieu de comparer manuellement les propriétés Année/Mois/Jour.
  • À FAIRE : Utilisez toLocaleString() pour formater les dates pour les utilisateurs au lieu de concaténer manuellement les chaînes.
  • À FAIRE : Vérifiez que le système de calendrier cible est compatible avec l'environnement à l'aide de Intl.supportedValuesOf('calendar') avant de créer des objets temporels spécifiques au calendrier.
  • À FAIRE : Sachez que certains calendriers (comme les variantes du calendrier islamique) peuvent s'appuyer sur une observation visuelle plutôt que sur des calculs fixes. L'API Temporal suit l'implémentation Intl de l'environnement, qui utilise généralement des approximations calculées. Pour les calculs de dates culturelles ou religieuses critiques, vérifiez auprès d'experts du domaine ou utilisez des bibliothèques spécialisées.
  • PRENEZ EN COMPTE les noms d'ères lorsque vous utilisez des calendriers qui utilisent des ères (par exemple, japonais, bouddhiste), en utilisant toLocaleString().

Stratégie de remplacement

Solutions de remplacement et compatibilité des navigateurs avec Temporal

Pour les navigateurs qui ne sont pas encore compatibles avec l'API Temporal native, utilisez la détection de fonctionnalités et chargez dynamiquement temporal-polyfill (~15 Ko min+gz).

Notez que l'importation de temporal-polyfill n'attribue pas automatiquement l'objet Temporal au champ d'application global. Attribuez-la explicitement si votre code repose sur 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();
})();