Suporte a sistemas de calendário globais com o Temporal

Recursos da Web: Temporal

O objeto JavaScript Date tradicional é baseado em um calendário gregoriano proléptico, o que dificulta a criação de aplicativos para usuários que usam outros sistemas de calendário, como o islâmico (lunar), o hebraico (lunissolar) ou o chinês (lunissolar). Antes, os desenvolvedores precisavam usar bibliotecas complexas de terceiros ou fazer cálculos manuais para oferecer suporte a esses sistemas.

A API Temporal oferece suporte de primeira classe para vários sistemas de calendário. Ao associar um identificador de calendário a objetos de data, o Temporal lida com a aritmética e a formatação complexas exigidas para diferentes contextos culturais de forma nativa.

Como implementar

Para oferecer suporte a sistemas de calendário globais usando o Temporal:

  1. Associe uma agenda (obrigatório): ao criar ou converter um objeto Temporal (como Temporal.PlainDate), especifique o sistema de agenda desejado usando withCalendar() para realizar operações que dependem da agenda.
  2. Use identificadores estáveis (obrigatório para calendários lunissolares): use monthCode em vez do índice numérico month para identificar meses específicos em anos diferentes em calendários lunissolares. Use apenas para calendários que usam meses bissextos, como o hebraico e o chinês.
  3. Respeite as invariantes do calendário (obrigatório): ao iterar meses ou dias, não presuma valores fixos (como 12 meses em um ano ou 31 dias em um mês). Use propriedades como monthsInYear e daysInMonth para garantir que seu código funcione em todos os calendários.
  4. Use comparações que consideram o calendário (obrigatório): ao comparar datas em um sistema de calendário específico, use Temporal.PlainDate.compare() em vez de comparar as propriedades de ano/mês/dia manualmente. Isso lida corretamente com a ordem cronológica dentro das regras do calendário.

Exemplo de código: como converter e iterar 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'
});

Implementação estratégica e práticas recomendadas

  • USE monthsInYear como o limite superior ao fazer um loop pelos meses, em vez de assumir 12.
  • USE monthCode para identificar meses específicos em calendários que usam meses bissextos (por exemplo, hebraico ou chinês), independentemente do ano.
  • NÃO presuma que date.month === 12 é o último mês do ano. Use date.month === date.monthsInYear.
  • NÃO presuma que inLeapYear === true implica que o ano é apenas um dia mais longo. Em calendários lunissolares, pode ser adicionado um mês bissexto completo.
  • USE Temporal.PlainDate.compare() ao comparar duas datas em um sistema de calendário específico, em vez de comparar as propriedades de ano/mês/dia manualmente.
  • USE toLocaleString() para formatar datas para os usuários em vez de concatenação manual de strings.
  • VERIFIQUE se o sistema de calendário de destino é compatível com o ambiente usando Intl.supportedValuesOf('calendar') antes de criar objetos temporais específicos do calendário.
  • É IMPORTANTE saber que alguns calendários (como variantes do calendário islâmico) podem depender de observação visual em vez de cálculos fixos. A API Temporal segue a implementação de Intl do ambiente, que geralmente usa aproximações calculadas. Para cálculos de datas culturais ou religiosas importantes, consulte especialistas no assunto ou use bibliotecas especializadas.
  • É RECOMENDÁVEL considerar os nomes das eras ao usar calendários que as utilizam (por exemplo, japonês, budista), usando toLocaleString().

Estratégia de fallback

Substitutos e suporte do navegador para Temporal

Para navegadores que ainda não são compatíveis com a API Temporal nativa, use a detecção de recursos e carregue dinamicamente temporal-polyfill (~15 KB min+gz).

A importação de temporal-polyfill não atribui automaticamente o objeto Temporal ao escopo global. Atribua-o explicitamente se o código depender de 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();
})();