El objeto Date tradicional de JavaScript se basa en un calendario gregoriano proléptico, lo que dificulta la creación de aplicaciones para usuarios que dependen de otros sistemas de calendario, como el islámico (lunar), el hebreo (lunisolar) o el chino (lunisolar). Antes, los desarrolladores debían recurrir a bibliotecas de terceros complejas o cálculos manuales para admitir estos sistemas.
La API de Temporal proporciona compatibilidad de primer nivel para varios sistemas de calendario. Al asociar un identificador de calendario con objetos de fecha, Temporal controla de forma nativa las operaciones aritméticas y el formato complejos que se requieren para diferentes contextos culturales.
Cómo implementarla
Para admitir sistemas de calendario globales con Temporal, haz lo siguiente:
- Asocia un calendario (obligatorio): Cuando crees o conviertas un objeto Temporal (como
Temporal.PlainDate), debes especificar el sistema de calendario deseado conwithCalendar()para realizar operaciones que dependan del calendario. - Usa identificadores estables (obligatorio para los calendarios lunisolares): Debes usar
monthCodeen lugar del índice numéricomonthpara identificar meses específicos en los años de los calendarios lunisolares. Usa esta opción solo para los calendarios que usan meses bisiestos, como el hebreo y el chino. - Respeta las invariantes del calendario (obligatorio): Cuando iteren por meses o días, no deben suponer valores fijos (como 12 meses en un año o 31 días en un mes). Usa propiedades como
monthsInYearydaysInMonthpara asegurarte de que tu código funcione en todos los calendarios. - Usa comparaciones que tengan en cuenta el calendario (obligatorio): Cuando compares fechas dentro de un sistema de calendario específico, debes usar
Temporal.PlainDate.compare()en lugar de comparar las propiedades de año, mes y día de forma manual. Esto controla correctamente el orden cronológico dentro de las reglas del calendario.
Código de ejemplo: Cómo convertir y recorrer calendarios
// 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'
});
Implementación estratégica y prácticas recomendadas
- SÍ uses
monthsInYearcomo límite superior cuando recorras los meses, en lugar de suponer que son 12. - SÍ uses
monthCodepara identificar meses específicos en calendarios que usan meses bisiestos (p.ej., hebreo o chino), independientemente del año. - NO asumas que
date.month === 12es el último mes del año. Usadate.month === date.monthsInYear. - NO asumas que
inLeapYear === trueimplica que el año solo tiene un día más. En los calendarios lunisolares, se puede agregar un mes bisiesto completo. - SÍ usa
Temporal.PlainDate.compare()cuando compares dos fechas en un sistema de calendario específico en lugar de comparar las propiedades de año, mes y día de forma manual. - SÍ uses
toLocaleString()para dar formato a las fechas de los usuarios en lugar de la concatenación manual de cadenas. - SÍ verifica que el sistema de calendario de destino sea compatible con el entorno usando
Intl.supportedValuesOf('calendar')antes de crear objetos Temporal específicos del calendario. - SÍ ten en cuenta que algunos calendarios (como las variantes del calendario islámico) pueden depender de la observación visual en lugar de cálculos fijos. La API de
Temporalsigue la implementación deIntldel entorno, que suele usar aproximaciones calculadas. Para realizar cálculos de fechas culturales o religiosas importantes, consulta con expertos en el dominio o usa bibliotecas especializadas. - SÍ se deben tener en cuenta los nombres de las eras cuando se usan calendarios que las utilizan (p.ej., japonés, budista), con
toLocaleString().
Estrategia de resguardo
Resguardos y compatibilidad de navegadores para Temporal
Para los navegadores que aún no admiten la API de Temporal nativa, usa la detección de funciones y carga temporal-polyfill de forma dinámica (~15 KB min+gz).
Ten en cuenta que importar temporal-polyfill no asigna automáticamente el objeto Temporal al alcance global. Asígnalo de forma explícita si tu código depende 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();
})();