tor-browser

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

commit c285f533dca03ad24aee56b4d42e9a1a4a21d5df
parent 26b566cf17e2d11ad799bfc87a1053a0fbe250d7
Author: André Bargull <andre.bargull@gmail.com>
Date:   Thu, 27 Nov 2025 10:03:35 +0000

Bug 1999315 - Part 6: Allow reference years after 1972 in calendars that need them. r=spidermonkey-reviewers,mgaudet

Implements the changes from <https://github.com/tc39/proposal-temporal/pull/3152>.

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

Diffstat:
Mjs/src/builtin/temporal/Calendar.cpp | 200+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Ajs/src/tests/non262/Temporal/PlainMonthDay/reference-dates-chinese-calendar.js | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 232 insertions(+), 71 deletions(-)

diff --git a/js/src/builtin/temporal/Calendar.cpp b/js/src/builtin/temporal/Calendar.cpp @@ -2208,6 +2208,115 @@ static bool CalendarDateToISO(JSContext* cx, CalendarId calendar, } /** + * CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ) + */ +static bool NonISOMonthDayToISOReferenceDate(JSContext* cx, CalendarId calendar, + icu4x::capi::Calendar* cal, + ISODate startISODate, + ISODate endISODate, + MonthCode monthCode, int32_t day, + UniqueICU4XDate& resultDate) { + MOZ_ASSERT(startISODate != endISODate); + + int32_t direction = startISODate > endISODate ? -1 : 1; + + auto fromIsoDate = CreateICU4XDate(cx, startISODate, calendar, cal); + if (!fromIsoDate) { + return false; + } + + auto toIsoDate = CreateICU4XDate(cx, endISODate, calendar, cal); + if (!toIsoDate) { + return false; + } + + // Find the calendar year for the ISO start date. + int32_t calendarYear; + if (!CalendarDateYear(cx, calendar, fromIsoDate.get(), &calendarYear)) { + return false; + } + + // Find the calendar year for the ISO end date. + int32_t toCalendarYear; + if (!CalendarDateYear(cx, calendar, toIsoDate.get(), &toCalendarYear)) { + return false; + } + + while (direction < 0 ? calendarYear >= toCalendarYear + : calendarYear <= toCalendarYear) { + // This loop can run for a long time. + if (!CheckForInterrupt(cx)) { + return false; + } + + auto candidateYear = CalendarEraYear(calendar, calendarYear); + + auto result = + CreateDateFromCodes(calendar, cal, candidateYear, monthCode, day); + if (result.isOk()) { + auto isoDate = ToISODate(result.inspect().get()); + + // Make sure the resolved date is before |startISODate|. + if (direction < 0 ? isoDate > startISODate : isoDate < startISODate) { + calendarYear += direction; + continue; + } + + // Stop searching if |endISODate| was reached. + if (direction < 0 ? isoDate < endISODate : isoDate > endISODate) { + resultDate = nullptr; + return true; + } + + resultDate = result.unwrap(); + return true; + } + + switch (result.inspectErr()) { + case CalendarError::UnknownMonthCode: { + MOZ_ASSERT(CalendarHasLeapMonths(calendar)); + MOZ_ASSERT(monthCode.isLeapMonth()); + + // Try the next candidate year if the requested leap month doesn't + // occur in the current year. + calendarYear += direction; + continue; + } + + case CalendarError::OutOfRange: { + // ICU4X throws an out-of-range error when: + // 1. month > monthsInYear(year), or + // 2. days > daysInMonthOf(year, month). + // + // Case 1 can't happen for month-codes, so it doesn't apply here. + // Case 2 can only happen when |day| is larger than the minimum number + // of days in the month. + MOZ_ASSERT(day > CalendarDaysInMonth(calendar, monthCode).first); + + // Try next candidate year to find an earlier year which can fulfill + // the input request. + calendarYear += direction; + continue; + } + + case CalendarError::UnknownEra: + MOZ_ASSERT(false, "unexpected calendar error"); + break; + + case CalendarError::Generic: + break; + } + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR); + return false; + } + + resultDate = nullptr; + return true; +} + +/** * NonISOMonthDayToISOReferenceDate ( calendar, fields, overflow ) */ static bool NonISOMonthDayToISOReferenceDate(JSContext* cx, CalendarId calendar, @@ -2287,85 +2396,34 @@ static bool NonISOMonthDayToISOReferenceDate(JSContext* cx, CalendarId calendar, } } - // Try years starting from 31 December, 1972. - constexpr auto isoReferenceDate = ISODate{1972, 12, 31}; + constexpr ISODate candidates[][2] = { + // The reference date is the latest ISO 8601 date corresponding to the + // calendar date that is between January 1, 1900 and December 31, 1972 + // inclusive. + {ISODate{1972, 12, 31}, ISODate{1900, 1, 1}}, - auto fromIsoDate = CreateICU4XDate(cx, isoReferenceDate, calendar, cal.get()); - if (!fromIsoDate) { - return false; - } + // If there is no such date, it is the earliest ISO 8601 date + // corresponding to the calendar date between January 1, 1973 and + // December 31, 2035. + {ISODate{1973, 1, 1}, ISODate{2035, 12, 31}}, - // Find the calendar year for the ISO reference date. - int32_t calendarYear; - if (!CalendarDateYear(cx, calendar, fromIsoDate.get(), &calendarYear)) { - return false; - } - - // 10'000 is sufficient to find all possible month-days, even for rare cases - // like `{calendar: "chinese", monthCode: "M09L", day: 30}`. - constexpr size_t maxIterations = 10'000; + // If there is still no such date, it is the latest ISO 8601 date + // corresponding to the calendar date on or before December 31, 1899. + // + // Year -8000 is sufficient to find all possible month-days, even for + // rare cases like `{calendar: "chinese", monthCode: "M09L", day: 30}`. + {ISODate{1899, 12, 31}, ISODate{-8000, 1, 1}}, + }; UniqueICU4XDate date; - for (size_t i = 0; i < maxIterations; i++) { - // This loop can run for a long time. - if (!CheckForInterrupt(cx)) { + for (auto& [start, end] : candidates) { + if (!NonISOMonthDayToISOReferenceDate(cx, calendar, cal.get(), start, end, + monthCode, day, date)) { return false; } - - auto candidateYear = CalendarEraYear(calendar, calendarYear); - - auto result = - CreateDateFromCodes(calendar, cal.get(), candidateYear, monthCode, day); - if (result.isOk()) { - // Make sure the resolved date is before December 31, 1972. - auto isoDate = ToISODate(result.inspect().get()); - if (isoDate.year > isoReferenceDate.year) { - calendarYear -= 1; - continue; - } - - date = result.unwrap(); + if (date) { break; } - - switch (result.inspectErr()) { - case CalendarError::UnknownMonthCode: { - MOZ_ASSERT(CalendarHasLeapMonths(calendar)); - MOZ_ASSERT(monthCode.isLeapMonth()); - - // Try the next candidate year if the requested leap month doesn't - // occur in the current year. - calendarYear -= 1; - continue; - } - - case CalendarError::OutOfRange: { - // ICU4X throws an out-of-range error when: - // 1. month > monthsInYear(year), or - // 2. days > daysInMonthOf(year, month). - // - // Case 1 can't happen for month-codes, so it doesn't apply here. - // Case 2 can only happen when |day| is larger than the minimum number - // of days in the month. - MOZ_ASSERT(day > CalendarDaysInMonth(calendar, monthCode).first); - - // Try next candidate year to find an earlier year which can fulfill - // the input request. - calendarYear -= 1; - continue; - } - - case CalendarError::UnknownEra: - MOZ_ASSERT(false, "unexpected calendar error"); - break; - - case CalendarError::Generic: - break; - } - - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR); - return false; } // We shouldn't end up here with |maxIterations == 10'000|, but just in case diff --git a/js/src/tests/non262/Temporal/PlainMonthDay/reference-dates-chinese-calendar.js b/js/src/tests/non262/Temporal/PlainMonthDay/reference-dates-chinese-calendar.js @@ -0,0 +1,103 @@ +// |reftest| skip-if(!this.hasOwnProperty("Temporal")) + +const calendar = "chinese"; + +// The Chinese calendar has 29-30 days per month. +const tests = [ + { + day: 29, + leapMonth: false, + expected: [ + "1972-03-14[u-ca=chinese]", + "1972-04-12[u-ca=chinese]", + "1972-05-12[u-ca=chinese]", + "1972-06-10[u-ca=chinese]", + "1972-07-09[u-ca=chinese]", + "1972-08-08[u-ca=chinese]", + "1972-09-06[u-ca=chinese]", + "1972-10-06[u-ca=chinese]", + "1972-11-04[u-ca=chinese]", + "1972-12-04[u-ca=chinese]", + "1972-01-15[u-ca=chinese]", + "1972-02-13[u-ca=chinese]", + ], + }, + { + day: 29, + leapMonth: true, + expected: [ + "1651-03-20[u-ca=chinese]", + "1947-04-20[u-ca=chinese]", + "1966-05-19[u-ca=chinese]", + "1963-06-20[u-ca=chinese]", + "1971-07-21[u-ca=chinese]", + "1960-08-21[u-ca=chinese]", + "1968-09-21[u-ca=chinese]", + "1957-10-22[u-ca=chinese]", + "2014-11-21[u-ca=chinese]", + "1984-12-21[u-ca=chinese]", + "2034-01-19[u-ca=chinese]", + "1404-02-19[u-ca=chinese]", + ], + }, + { + day: 30, + leapMonth: false, + expected: [ + "1970-03-07[u-ca=chinese]", + "1972-04-13[u-ca=chinese]", + "1966-04-20[u-ca=chinese]", + "1970-06-03[u-ca=chinese]", + "1972-07-10[u-ca=chinese]", + "1971-08-20[u-ca=chinese]", + "1972-09-07[u-ca=chinese]", + "1971-10-18[u-ca=chinese]", + "1972-11-05[u-ca=chinese]", + "1972-12-05[u-ca=chinese]", + "1970-01-07[u-ca=chinese]", + "1972-02-14[u-ca=chinese]", + ], + }, + { + day: 30, + leapMonth: true, + expected: [ + "1461-03-20[u-ca=chinese]", + "1765-04-19[u-ca=chinese]", + "1955-05-21[u-ca=chinese]", + "1944-06-20[u-ca=chinese]", + "1952-07-21[u-ca=chinese]", + "1941-08-22[u-ca=chinese]", + "1938-09-23[u-ca=chinese]", + "1718-10-23[u-ca=chinese]", + "-005738-11-17[u-ca=chinese]", + "-004098-12-19[u-ca=chinese]", + "-002172-01-19[u-ca=chinese]", + "-000179-02-18[u-ca=chinese]", + ], + }, +]; + +for (let {day, leapMonth, expected} of tests) { + assertEq(expected.length, 12); + + for (let i = 1; i <= 12; ++i) { + let expectedToString = expected[i - 1]; + + // Skip over dates which are too far into the past (and therefore are likely + // incorrect anyway). This avoids slowing down this test. + if (expectedToString.startsWith("-")) { + continue; + } + + let monthCode = "M" + String(i).padStart(2, "0") + (leapMonth ? "L" : ""); + + let pmd = Temporal.PlainMonthDay.from({calendar, monthCode, day}); + assertEq(pmd.monthCode, monthCode); + assertEq(pmd.day, day); + assertEq(pmd.toString(), expectedToString); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true);