tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 584dd6fe83c9eb57869275809bf40851555e26e0
parent c239c1f224a35434175a2d7fe78c7e2fa7f7480a
Author: André Bargull <andre.bargull@gmail.com>
Date:   Thu, 27 Nov 2025 10:03:38 +0000

Bug 1999316 - Part 5: Support eras for more calendars. r=spidermonkey-reviewers,mgaudet

Add eras specified in the proposal, except for the ones which will be removed
in <https://github.com/tc39/proposal-intl-era-monthcode/pull/72>.

Differential Revision: https://phabricator.services.mozilla.com/D272035

Diffstat:
Mjs/src/builtin/temporal/Calendar.cpp | 72++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mjs/src/builtin/temporal/CalendarFields.cpp | 10+++++-----
Mjs/src/builtin/temporal/Era.h | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 147 insertions(+), 39 deletions(-)

diff --git a/js/src/builtin/temporal/Calendar.cpp b/js/src/builtin/temporal/Calendar.cpp @@ -754,9 +754,12 @@ static constexpr size_t EraNameMaxLength() { return length; } -static mozilla::Maybe<EraCode> EraForString(CalendarId calendar, - JSLinearString* string) { - MOZ_ASSERT(CalendarEraRelevant(calendar)); +/** + * CanonicalizeEraInCalendar ( calendar, era ) + */ +static mozilla::Maybe<EraCode> CanonicalizeEraInCalendar( + CalendarId calendar, JSLinearString* string) { + MOZ_ASSERT(CalendarSupportsEra(calendar)); // Note: Assigning MaxLength to EraNameMaxLength() breaks the CDT indexer. constexpr size_t MaxLength = 24; @@ -921,7 +924,7 @@ static mozilla::Result<UniqueICU4XDate, CalendarError> CreateDateFromCodes( MOZ_ASSERT(icu4x::capi::icu4x_Calendar_kind_mv1(calendar) == ToAnyCalendarKind(calendarId)); MOZ_ASSERT(CalendarErasAsEnumSet(calendarId).contains(eraYear.era)); - MOZ_ASSERT_IF(CalendarEraRelevant(calendarId), eraYear.year > 0); + MOZ_ASSERT_IF(CalendarEraHasInverse(calendarId), eraYear.year > 0); MOZ_ASSERT(mozilla::Abs(eraYear.year) <= MaximumCalendarYear(calendarId)); MOZ_ASSERT(IsValidMonthCodeForCalendar(calendarId, monthCode)); MOZ_ASSERT(day > 0); @@ -1667,10 +1670,10 @@ struct EraYears { static bool CalendarEraYear(JSContext* cx, CalendarId calendarId, EraYear eraYear, EraYear* result) { - MOZ_ASSERT(CalendarEraRelevant(calendarId)); + MOZ_ASSERT(CalendarSupportsEra(calendarId)); MOZ_ASSERT(mozilla::Abs(eraYear.year) <= MaximumCalendarYear(calendarId)); - if (eraYear.year > 0) { + if (eraYear.year > 0 || !CalendarEraHasInverse(calendarId)) { *result = eraYear; return true; } @@ -1726,9 +1729,9 @@ static bool CalendarFieldYear(JSContext* cx, CalendarId calendar, // |eraYear| is to be ignored when not relevant for |calendar| per // CalendarResolveFields. - bool hasRelevantEra = - fields.has(CalendarField::Era) && CalendarEraRelevant(calendar); - MOZ_ASSERT_IF(fields.has(CalendarField::Era), CalendarEraRelevant(calendar)); + bool supportsEra = + fields.has(CalendarField::Era) && CalendarSupportsEra(calendar); + MOZ_ASSERT_IF(fields.has(CalendarField::Era), CalendarSupportsEra(calendar)); // Case 1: |year| field is present. mozilla::Maybe<EraYear> fromEpoch; @@ -1745,12 +1748,12 @@ static bool CalendarFieldYear(JSContext* cx, CalendarId calendar, fromEpoch = mozilla::Some(CalendarEraYear(calendar, intYear)); } else { - MOZ_ASSERT(hasRelevantEra); + MOZ_ASSERT(supportsEra); } // Case 2: |era| and |eraYear| fields are present and relevant for |calendar|. mozilla::Maybe<EraYear> fromEra; - if (hasRelevantEra) { + if (supportsEra) { MOZ_ASSERT(fields.has(CalendarField::Era)); MOZ_ASSERT(fields.has(CalendarField::EraYear)); @@ -1766,7 +1769,7 @@ static bool CalendarFieldYear(JSContext* cx, CalendarId calendar, } // Ensure the requested era is valid for |calendar|. - auto eraCode = EraForString(calendar, linearEra); + auto eraCode = CanonicalizeEraInCalendar(calendar, linearEra); if (!eraCode) { if (auto code = QuoteString(cx, era)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, @@ -1803,9 +1806,8 @@ struct Month { }; /** - * CalendarResolveFields ( calendar, fields, type ) - * CalendarDateToISO ( calendar, fields, overflow ) - * CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ) + * NonISOCalendarDateToISO ( calendar, fields, overflow ) + * NonISOMonthDayToISOReferenceDate ( calendar, fields, overflow ) * * Extract `month` and `monthCode` from |fields| and perform some initial * validation to ensure the values are valid for the requested calendar. @@ -2441,7 +2443,7 @@ static bool NonISOResolveFields(JSContext* cx, CalendarId calendar, missingField = "monthCode"; } else if (requireDay && !fields.has(CalendarField::Day)) { missingField = "day"; - } else if (!CalendarEraRelevant(calendar)) { + } else if (!CalendarSupportsEra(calendar)) { if (requireYear && !fields.has(CalendarField::Year)) { missingField = "year"; } @@ -2505,6 +2507,7 @@ static bool CalendarResolveFields(JSContext* cx, CalendarId calendar, /** * CalendarISOToDate ( calendar, isoDate ) * NonISOCalendarISOToDate ( calendar, isoDate ) + * CalendarDateEra ( calendar, date ) * * Return the Calendar Date Record's [[Era]] field. */ @@ -2520,20 +2523,28 @@ bool js::temporal::CalendarEra(JSContext* cx, Handle<CalendarValue> calendar, } // Step 2. - if (!CalendarEraRelevant(calendarId)) { + if (!CalendarSupportsEra(calendarId)) { result.setUndefined(); return true; } - auto cal = CreateICU4XCalendar(calendarId); - auto dt = CreateICU4XDate(cx, date, calendarId, cal.get()); - if (!dt) { - return false; - } + auto era = EraCode::Standard; - EraCode era; - if (!CalendarDateEra(cx, calendarId, dt.get(), &era)) { - return false; + // Call into ICU4X if the calendar has more than one era. + auto eras = CalendarEras(calendarId); + if (eras.size() > 1) { + auto cal = CreateICU4XCalendar(calendarId); + auto dt = CreateICU4XDate(cx, date, calendarId, cal.get()); + if (!dt) { + return false; + } + + if (!CalendarDateEra(cx, calendarId, dt.get(), &era)) { + return false; + } + } else { + MOZ_ASSERT(*eras.begin() == EraCode::Standard, + "single era calendars use only the standard era"); } auto* str = NewStringCopy<CanGC>(cx, CalendarEraName(calendarId, era)); @@ -2548,6 +2559,7 @@ bool js::temporal::CalendarEra(JSContext* cx, Handle<CalendarValue> calendar, /** * CalendarISOToDate ( calendar, isoDate ) * NonISOCalendarISOToDate ( calendar, isoDate ) + * CalendarDateEraYear ( calendar, date ) * * Return the Calendar Date Record's [[EraYear]] field. */ @@ -2564,11 +2576,18 @@ bool js::temporal::CalendarEraYear(JSContext* cx, } // Step 2. - if (!CalendarEraRelevant(calendarId)) { + if (!CalendarSupportsEra(calendarId)) { result.setUndefined(); return true; } + auto eras = CalendarEras(calendarId); + if (eras.size() == 1) { + // Return the calendar year for calendars with a single era. + return CalendarYear(cx, calendar, date, result); + } + MOZ_ASSERT(eras.size() > 1); + auto cal = CreateICU4XCalendar(calendarId); auto dt = CreateICU4XDate(cx, date, calendarId, cal.get()); if (!dt) { @@ -2583,6 +2602,7 @@ bool js::temporal::CalendarEraYear(JSContext* cx, /** * CalendarISOToDate ( calendar, isoDate ) * NonISOCalendarISOToDate ( calendar, isoDate ) + * CalendarDateArithmeticYear ( calendar, date ) * * Return the Calendar Date Record's [[Year]] field. */ diff --git a/js/src/builtin/temporal/CalendarFields.cpp b/js/src/builtin/temporal/CalendarFields.cpp @@ -199,9 +199,9 @@ static mozilla::EnumSet<CalendarField> CalendarExtraFields( // Step 2. - // "era" and "eraYear" are relevant for calendars with multiple eras when + // "era" and "eraYear" are relevant for calendars supporting eras when // "year" is present. - if (fields.contains(CalendarField::Year) && CalendarEraRelevant(calendar)) { + if (fields.contains(CalendarField::Year) && CalendarSupportsEra(calendar)) { return {CalendarField::Era, CalendarField::EraYear}; } return {}; @@ -571,9 +571,9 @@ static auto NonISOFieldKeysToIgnore(CalendarId calendar, result += monthOrMonthCode; } - // "era", "eraYear", and "year" are mutually exclusive in non-single era - // calendar systems. - if (CalendarEraRelevant(calendar) && !(keys & eraOrAnyYear).isEmpty()) { + // "era", "eraYear", and "year" are mutually exclusive when the calendar + // supports eras. + if (CalendarSupportsEra(calendar) && !(keys & eraOrAnyYear).isEmpty()) { result += eraOrAnyYear; } diff --git a/js/src/builtin/temporal/Era.h b/js/src/builtin/temporal/Era.h @@ -61,6 +61,28 @@ inline constexpr auto Empty = { ""sv, }; +inline constexpr auto Buddhist = { + "be"sv, +}; + +inline constexpr auto Coptic = { + "am"sv, +}; + +inline constexpr auto EthiopianAmeteAlem = { + "aa"sv, +}; + +// "Intl era and monthCode" proposal follows CDLR which defines that Amete Alem +// era is used for years before the incarnation. This may not match modern +// usage, though. For the time being use a single era to check if we get any +// user reports to clarify the situation. +// +// CLDR bug report: https://unicode-org.atlassian.net/browse/CLDR-18739 +inline constexpr auto Ethiopian = { + "am"sv, +}; + inline constexpr auto Gregorian = { "ce"sv, "ad"sv, @@ -71,6 +93,14 @@ inline constexpr auto GregorianInverse = { "bc"sv, }; +inline constexpr auto Hebrew = { + "am"sv, +}; + +inline constexpr auto Indian = { + "shaka"sv, +}; + inline constexpr auto Islamic = { "ah"sv, }; @@ -99,6 +129,10 @@ inline constexpr auto JapaneseReiwa = { "reiwa"sv, }; +inline constexpr auto Persian = { + "ap"sv, +}; + inline constexpr auto ROC = { "roc"sv, "minguo"sv, @@ -112,8 +146,8 @@ inline constexpr auto ROCInverse = { } // namespace names } // namespace eras -constexpr auto& CalendarEras(CalendarId id) { - switch (id) { +constexpr auto& CalendarEras(CalendarId calendar) { + switch (calendar) { case CalendarId::ISO8601: case CalendarId::Buddhist: case CalendarId::Chinese: @@ -139,24 +173,78 @@ constexpr auto& CalendarEras(CalendarId id) { MOZ_CRASH("invalid calendar id"); } -constexpr bool CalendarEraRelevant(CalendarId calendar) { +/** + * Return `true` iff the calendar has an inverse era. + */ +constexpr bool CalendarEraHasInverse(CalendarId calendar) { + // More than one era implies an inverse era is used. return CalendarEras(calendar).size() > 1; } -constexpr auto& CalendarEraNames(CalendarId calendar, EraCode era) { +/** + * CalendarSupportsEra ( calendar ) + */ +constexpr bool CalendarSupportsEra(CalendarId calendar) { switch (calendar) { case CalendarId::ISO8601: - case CalendarId::Buddhist: case CalendarId::Chinese: - case CalendarId::Coptic: case CalendarId::Dangi: + return false; + + case CalendarId::Buddhist: + case CalendarId::Coptic: case CalendarId::Ethiopian: case CalendarId::EthiopianAmeteAlem: case CalendarId::Hebrew: case CalendarId::Indian: case CalendarId::Persian: + case CalendarId::Gregorian: + case CalendarId::IslamicCivil: + case CalendarId::IslamicTabular: + case CalendarId::IslamicUmmAlQura: + case CalendarId::ROC: + case CalendarId::Japanese: + return true; + } + MOZ_CRASH("invalid calendar id"); +} + +constexpr auto& CalendarEraNames(CalendarId calendar, EraCode era) { + switch (calendar) { + case CalendarId::ISO8601: + case CalendarId::Chinese: + case CalendarId::Dangi: + MOZ_ASSERT(era == EraCode::Standard); return eras::names::Empty; + case CalendarId::Buddhist: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::Buddhist; + + case CalendarId::Coptic: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::Coptic; + + case CalendarId::Ethiopian: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::Ethiopian; + + case CalendarId::EthiopianAmeteAlem: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::EthiopianAmeteAlem; + + case CalendarId::Hebrew: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::Hebrew; + + case CalendarId::Indian: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::Indian; + + case CalendarId::Persian: + MOZ_ASSERT(era == EraCode::Standard); + return eras::names::Persian; + case CalendarId::Gregorian: { MOZ_ASSERT(era == EraCode::Standard || era == EraCode::Inverse); return era == EraCode::Standard ? eras::names::Gregorian @@ -237,8 +325,8 @@ struct EraYear { int32_t year = 0; }; -constexpr EraYear CalendarEraYear(CalendarId id, int32_t year) { - if (year > 0 || !CalendarEraRelevant(id)) { +constexpr EraYear CalendarEraYear(CalendarId calendar, int32_t year) { + if (year > 0 || !CalendarEraHasInverse(calendar)) { return EraYear{EraCode::Standard, year}; } return EraYear{EraCode::Inverse, int32_t(mozilla::Abs(year) + 1)};