tor-browser

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

Calendar.cpp (127902B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/temporal/Calendar.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/Attributes.h"
     11 #include "mozilla/Casting.h"
     12 #include "mozilla/CheckedInt.h"
     13 #include "mozilla/EnumSet.h"
     14 #include "mozilla/FloatingPoint.h"
     15 #include "mozilla/intl/Locale.h"
     16 #include "mozilla/MathAlgorithms.h"
     17 #include "mozilla/Maybe.h"
     18 #include "mozilla/Result.h"
     19 #include "mozilla/ResultVariant.h"
     20 #include "mozilla/Span.h"
     21 #include "mozilla/TextUtils.h"
     22 #include "mozilla/UniquePtr.h"
     23 
     24 #include <algorithm>
     25 #include <array>
     26 #include <cmath>
     27 #include <stddef.h>
     28 #include <stdint.h>
     29 
     30 #include "diplomat_runtime.hpp"
     31 #include "jsnum.h"
     32 #include "jstypes.h"
     33 #include "NamespaceImports.h"
     34 
     35 #include "builtin/temporal/CalendarFields.h"
     36 #include "builtin/temporal/Duration.h"
     37 #include "builtin/temporal/Era.h"
     38 #include "builtin/temporal/MonthCode.h"
     39 #include "builtin/temporal/PlainDate.h"
     40 #include "builtin/temporal/PlainDateTime.h"
     41 #include "builtin/temporal/PlainMonthDay.h"
     42 #include "builtin/temporal/PlainTime.h"
     43 #include "builtin/temporal/PlainYearMonth.h"
     44 #include "builtin/temporal/Temporal.h"
     45 #include "builtin/temporal/TemporalParser.h"
     46 #include "builtin/temporal/TemporalRoundingMode.h"
     47 #include "builtin/temporal/TemporalTypes.h"
     48 #include "builtin/temporal/TemporalUnit.h"
     49 #include "builtin/temporal/ZonedDateTime.h"
     50 #include "gc/Barrier.h"
     51 #include "gc/GCEnum.h"
     52 #include "icu4x/Calendar.hpp"
     53 #include "icu4x/Date.hpp"
     54 #include "icu4x/IsoDate.hpp"
     55 #include "js/AllocPolicy.h"
     56 #include "js/ErrorReport.h"
     57 #include "js/friend/ErrorMessages.h"
     58 #include "js/Printer.h"
     59 #include "js/RootingAPI.h"
     60 #include "js/TracingAPI.h"
     61 #include "js/Value.h"
     62 #include "js/Vector.h"
     63 #include "util/Text.h"
     64 #include "vm/BytecodeUtil.h"
     65 #include "vm/Compartment.h"
     66 #include "vm/JSAtomState.h"
     67 #include "vm/JSContext.h"
     68 #include "vm/StringType.h"
     69 
     70 #include "vm/Compartment-inl.h"
     71 #include "vm/JSContext-inl.h"
     72 #include "vm/JSObject-inl.h"
     73 #include "vm/ObjectOperations-inl.h"
     74 
     75 // diplomat_simple_write isn't defined in C++ headers, but we have to use it to
     76 // avoid memory allocation.
     77 // (https://github.com/rust-diplomat/diplomat/issues/866)
     78 namespace diplomat::capi {
     79 extern "C" DiplomatWrite diplomat_simple_write(char* buf, size_t buf_size);
     80 }
     81 
     82 using namespace js;
     83 using namespace js::temporal;
     84 
     85 void js::temporal::CalendarValue::trace(JSTracer* trc) {
     86  TraceRoot(trc, &value_, "CalendarValue::value");
     87 }
     88 
     89 bool js::temporal::WrapCalendarValue(JSContext* cx,
     90                                     MutableHandle<JS::Value> calendar) {
     91  MOZ_ASSERT(calendar.isInt32());
     92  return cx->compartment()->wrap(cx, calendar);
     93 }
     94 
     95 /**
     96 * IsISOLeapYear ( year )
     97 */
     98 static constexpr bool IsISOLeapYear(int32_t year) {
     99  // Steps 1-5.
    100  return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
    101 }
    102 
    103 /**
    104 * ISODaysInYear ( year )
    105 */
    106 static int32_t ISODaysInYear(int32_t year) {
    107  // Steps 1-3.
    108  return IsISOLeapYear(year) ? 366 : 365;
    109 }
    110 
    111 /**
    112 * ISODaysInMonth ( year, month )
    113 */
    114 static constexpr int32_t ISODaysInMonth(int32_t year, int32_t month) {
    115  MOZ_ASSERT(1 <= month && month <= 12);
    116 
    117  constexpr uint8_t daysInMonth[2][13] = {
    118      {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    119      {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
    120 
    121  // Steps 1-4.
    122  return daysInMonth[IsISOLeapYear(year)][month];
    123 }
    124 
    125 /**
    126 * ISODaysInMonth ( year, month )
    127 */
    128 int32_t js::temporal::ISODaysInMonth(int32_t year, int32_t month) {
    129  return ::ISODaysInMonth(year, month);
    130 }
    131 
    132 /**
    133 * 21.4.1.6 Week Day
    134 *
    135 * Compute the week day from |day| without first expanding |day| into a full
    136 * date through |MakeDate(day, 0)|:
    137 *
    138 *   WeekDay(MakeDate(day, 0))
    139 * = WeekDay(day × msPerDay + 0)
    140 * = WeekDay(day × msPerDay)
    141 * = 𝔽(ℝ(Day(day × msPerDay) + 4𝔽) modulo 7)
    142 * = 𝔽(ℝ(𝔽(floor(ℝ((day × msPerDay) / msPerDay))) + 4𝔽) modulo 7)
    143 * = 𝔽(ℝ(𝔽(floor(ℝ(day))) + 4𝔽) modulo 7)
    144 * = 𝔽(ℝ(𝔽(day) + 4𝔽) modulo 7)
    145 */
    146 static int32_t WeekDay(int32_t day) {
    147  int32_t result = (day + 4) % 7;
    148  if (result < 0) {
    149    result += 7;
    150  }
    151  return result;
    152 }
    153 
    154 /**
    155 * ISODayOfWeek ( isoDate )
    156 */
    157 static int32_t ISODayOfWeek(const ISODate& isoDate) {
    158  MOZ_ASSERT(ISODateWithinLimits(isoDate));
    159 
    160  // Step 1.
    161  int32_t day = MakeDay(isoDate);
    162 
    163  // Step 2.
    164  int32_t dayOfWeek = WeekDay(day);
    165 
    166  // Steps 3-4.
    167  return dayOfWeek != 0 ? dayOfWeek : 7;
    168 }
    169 
    170 static constexpr auto FirstDayOfMonth(int32_t year) {
    171  // The following array contains the day of year for the first day of each
    172  // month, where index 0 is January, and day 0 is January 1.
    173  std::array<int32_t, 13> days = {};
    174  for (int32_t month = 1; month <= 12; ++month) {
    175    days[month] = days[month - 1] + ::ISODaysInMonth(year, month);
    176  }
    177  return days;
    178 }
    179 
    180 /**
    181 * ISODayOfYear ( isoDate )
    182 */
    183 static int32_t ISODayOfYear(const ISODate& isoDate) {
    184  MOZ_ASSERT(ISODateWithinLimits(isoDate));
    185 
    186  const auto& [year, month, day] = isoDate;
    187 
    188  // First day of month arrays for non-leap and leap years.
    189  constexpr decltype(FirstDayOfMonth(0)) firstDayOfMonth[2] = {
    190      FirstDayOfMonth(1), FirstDayOfMonth(0)};
    191 
    192  // Steps 1-2.
    193  //
    194  // Instead of first computing the date and then using DayWithinYear to map the
    195  // date to the day within the year, directly lookup the first day of the month
    196  // and then add the additional days.
    197  return firstDayOfMonth[IsISOLeapYear(year)][month - 1] + day;
    198 }
    199 
    200 static int32_t FloorDiv(int32_t dividend, int32_t divisor) {
    201  MOZ_ASSERT(divisor > 0);
    202 
    203  int32_t quotient = dividend / divisor;
    204  int32_t remainder = dividend % divisor;
    205  if (remainder < 0) {
    206    quotient -= 1;
    207  }
    208  return quotient;
    209 }
    210 
    211 /**
    212 * 21.4.1.3 Year Number, DayFromYear
    213 */
    214 static int32_t DayFromYear(int32_t year) {
    215  return 365 * (year - 1970) + FloorDiv(year - 1969, 4) -
    216         FloorDiv(year - 1901, 100) + FloorDiv(year - 1601, 400);
    217 }
    218 
    219 /**
    220 * 21.4.1.11 MakeTime ( hour, min, sec, ms )
    221 */
    222 static int64_t MakeTime(const Time& time) {
    223  MOZ_ASSERT(IsValidTime(time));
    224 
    225  // Step 1 (Not applicable).
    226 
    227  // Step 2.
    228  int64_t h = time.hour;
    229 
    230  // Step 3.
    231  int64_t m = time.minute;
    232 
    233  // Step 4.
    234  int64_t s = time.second;
    235 
    236  // Step 5.
    237  int64_t milli = time.millisecond;
    238 
    239  // Steps 6-7.
    240  return h * ToMilliseconds(TemporalUnit::Hour) +
    241         m * ToMilliseconds(TemporalUnit::Minute) +
    242         s * ToMilliseconds(TemporalUnit::Second) + milli;
    243 }
    244 
    245 /**
    246 * 21.4.1.12 MakeDay ( year, month, date )
    247 */
    248 int32_t js::temporal::MakeDay(const ISODate& date) {
    249  MOZ_ASSERT(ISODateWithinLimits(date));
    250 
    251  return DayFromYear(date.year) + ISODayOfYear(date) - 1;
    252 }
    253 
    254 /**
    255 * 21.4.1.13 MakeDate ( day, time )
    256 */
    257 int64_t js::temporal::MakeDate(const ISODateTime& dateTime) {
    258  MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
    259 
    260  // Step 1 (Not applicable).
    261 
    262  // Steps 2-3.
    263  int64_t tv = MakeDay(dateTime.date) * ToMilliseconds(TemporalUnit::Day) +
    264               MakeTime(dateTime.time);
    265 
    266  // Step 4.
    267  return tv;
    268 }
    269 
    270 struct YearWeek final {
    271  int32_t year = 0;
    272  int32_t week = 0;
    273 };
    274 
    275 /**
    276 * ISOWeekOfYear ( isoDate )
    277 */
    278 static YearWeek ISOWeekOfYear(const ISODate& isoDate) {
    279  MOZ_ASSERT(ISODateWithinLimits(isoDate));
    280 
    281  // Step 1.
    282  int32_t year = isoDate.year;
    283 
    284  // Step 2-7. (Not applicable in our implementation.)
    285 
    286  // Steps 8-9.
    287  int32_t dayOfYear = ISODayOfYear(isoDate);
    288  int32_t dayOfWeek = ISODayOfWeek(isoDate);
    289 
    290  // Step 10.
    291  int32_t week = (10 + dayOfYear - dayOfWeek) / 7;
    292  MOZ_ASSERT(0 <= week && week <= 53);
    293 
    294  // An ISO year has 53 weeks if the year starts on a Thursday or if it's a
    295  // leap year which starts on a Wednesday.
    296  auto isLongYear = [](int32_t year) {
    297    int32_t startOfYear = ISODayOfWeek({year, 1, 1});
    298    return startOfYear == 4 || (startOfYear == 3 && IsISOLeapYear(year));
    299  };
    300 
    301  // Step 11.
    302  //
    303  // Part of last year's last week, which is either week 52 or week 53.
    304  if (week == 0) {
    305    return {year - 1, 52 + int32_t(isLongYear(year - 1))};
    306  }
    307 
    308  // Step 12.
    309  //
    310  // Part of next year's first week if the current year isn't a long year.
    311  if (week == 53 && !isLongYear(year)) {
    312    return {year + 1, 1};
    313  }
    314 
    315  // Step 13.
    316  return {year, week};
    317 }
    318 
    319 /**
    320 * ToTemporalCalendarIdentifier ( calendarSlotValue )
    321 */
    322 std::string_view js::temporal::CalendarIdentifier(CalendarId calendarId) {
    323  switch (calendarId) {
    324    case CalendarId::ISO8601:
    325      return "iso8601";
    326    case CalendarId::Buddhist:
    327      return "buddhist";
    328    case CalendarId::Chinese:
    329      return "chinese";
    330    case CalendarId::Coptic:
    331      return "coptic";
    332    case CalendarId::Dangi:
    333      return "dangi";
    334    case CalendarId::Ethiopian:
    335      return "ethiopic";
    336    case CalendarId::EthiopianAmeteAlem:
    337      return "ethioaa";
    338    case CalendarId::Gregorian:
    339      return "gregory";
    340    case CalendarId::Hebrew:
    341      return "hebrew";
    342    case CalendarId::Indian:
    343      return "indian";
    344    case CalendarId::IslamicCivil:
    345      return "islamic-civil";
    346    case CalendarId::IslamicTabular:
    347      return "islamic-tbla";
    348    case CalendarId::IslamicUmmAlQura:
    349      return "islamic-umalqura";
    350    case CalendarId::Japanese:
    351      return "japanese";
    352    case CalendarId::Persian:
    353      return "persian";
    354    case CalendarId::ROC:
    355      return "roc";
    356  }
    357  MOZ_CRASH("invalid calendar id");
    358 }
    359 
    360 class MOZ_STACK_CLASS AsciiLowerCaseChars final {
    361  static constexpr size_t InlineCapacity = 24;
    362 
    363  Vector<char, InlineCapacity> chars_;
    364 
    365 public:
    366  explicit AsciiLowerCaseChars(JSContext* cx) : chars_(cx) {}
    367 
    368  operator mozilla::Span<const char>() const {
    369    return mozilla::Span<const char>{chars_};
    370  }
    371 
    372  [[nodiscard]] bool init(JSLinearString* str) {
    373    MOZ_ASSERT(StringIsAscii(str));
    374 
    375    if (!chars_.resize(str->length())) {
    376      return false;
    377    }
    378 
    379    CopyChars(reinterpret_cast<JS::Latin1Char*>(chars_.begin()), *str);
    380 
    381    mozilla::intl::AsciiToLowerCase(chars_.begin(), chars_.length(),
    382                                    chars_.begin());
    383 
    384    return true;
    385  }
    386 };
    387 
    388 /**
    389 * CanonicalizeCalendar ( id )
    390 */
    391 bool js::temporal::CanonicalizeCalendar(JSContext* cx, Handle<JSString*> id,
    392                                        MutableHandle<CalendarValue> result) {
    393  Rooted<JSLinearString*> linear(cx, id->ensureLinear(cx));
    394  if (!linear) {
    395    return false;
    396  }
    397 
    398  // Steps 1-3.
    399  do {
    400    if (!StringIsAscii(linear) || linear->empty()) {
    401      break;
    402    }
    403 
    404    AsciiLowerCaseChars lowerCaseChars(cx);
    405    if (!lowerCaseChars.init(linear)) {
    406      return false;
    407    }
    408    mozilla::Span<const char> id = lowerCaseChars;
    409 
    410    // Reject invalid types before trying to resolve aliases.
    411    if (mozilla::intl::LocaleParser::CanParseUnicodeExtensionType(id).isErr()) {
    412      break;
    413    }
    414 
    415    // Resolve calendar aliases.
    416    static constexpr auto key = mozilla::MakeStringSpan("ca");
    417    if (const char* replacement =
    418            mozilla::intl::Locale::ReplaceUnicodeExtensionType(key, id)) {
    419      id = mozilla::MakeStringSpan(replacement);
    420    }
    421 
    422    // Step 1.
    423    static constexpr auto& calendars = AvailableCalendars();
    424 
    425    // Steps 2-3.
    426    for (auto identifier : calendars) {
    427      if (id == mozilla::Span{CalendarIdentifier(identifier)}) {
    428        result.set(CalendarValue(identifier));
    429        return true;
    430      }
    431    }
    432  } while (false);
    433 
    434  if (auto chars = QuoteString(cx, linear)) {
    435    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    436                             JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get());
    437  }
    438  return false;
    439 }
    440 
    441 template <typename T, typename... Ts>
    442 static bool ToTemporalCalendar(JSContext* cx, Handle<JSObject*> object,
    443                               MutableHandle<CalendarValue> result) {
    444  if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
    445    result.set(unwrapped->calendar());
    446    return result.wrap(cx);
    447  }
    448 
    449  if constexpr (sizeof...(Ts) > 0) {
    450    return ToTemporalCalendar<Ts...>(cx, object, result);
    451  }
    452 
    453  result.set(CalendarValue());
    454  return true;
    455 }
    456 
    457 /**
    458 * ToTemporalCalendarSlotValue ( temporalCalendarLike )
    459 */
    460 bool js::temporal::ToTemporalCalendar(JSContext* cx,
    461                                      Handle<Value> temporalCalendarLike,
    462                                      MutableHandle<CalendarValue> result) {
    463  // Step 1.
    464  if (temporalCalendarLike.isObject()) {
    465    Rooted<JSObject*> obj(cx, &temporalCalendarLike.toObject());
    466 
    467    // Step 1.a.
    468    Rooted<CalendarValue> calendar(cx);
    469    if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject,
    470                              PlainMonthDayObject, PlainYearMonthObject,
    471                              ZonedDateTimeObject>(cx, obj, &calendar)) {
    472      return false;
    473    }
    474    if (calendar) {
    475      result.set(calendar);
    476      return true;
    477    }
    478  }
    479 
    480  // Step 2.
    481  if (!temporalCalendarLike.isString()) {
    482    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
    483                     temporalCalendarLike, nullptr, "not a string");
    484    return false;
    485  }
    486  Rooted<JSString*> str(cx, temporalCalendarLike.toString());
    487 
    488  // Step 3.
    489  Rooted<JSLinearString*> id(cx, ParseTemporalCalendarString(cx, str));
    490  if (!id) {
    491    return false;
    492  }
    493 
    494  // Step 4.
    495  return CanonicalizeCalendar(cx, id, result);
    496 }
    497 
    498 /**
    499 * GetTemporalCalendarSlotValueWithISODefault ( item )
    500 */
    501 bool js::temporal::GetTemporalCalendarWithISODefault(
    502    JSContext* cx, Handle<JSObject*> item,
    503    MutableHandle<CalendarValue> result) {
    504  // Step 1.
    505  Rooted<CalendarValue> calendar(cx);
    506  if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject,
    507                            PlainMonthDayObject, PlainYearMonthObject,
    508                            ZonedDateTimeObject>(cx, item, &calendar)) {
    509    return false;
    510  }
    511  if (calendar) {
    512    result.set(calendar);
    513    return true;
    514  }
    515 
    516  // Step 2.
    517  Rooted<Value> calendarValue(cx);
    518  if (!GetProperty(cx, item, item, cx->names().calendar, &calendarValue)) {
    519    return false;
    520  }
    521 
    522  // Step 3.
    523  if (calendarValue.isUndefined()) {
    524    result.set(CalendarValue(CalendarId::ISO8601));
    525    return true;
    526  }
    527 
    528  // Step 4.
    529  return ToTemporalCalendar(cx, calendarValue, result);
    530 }
    531 
    532 static inline int32_t OrdinalMonth(const icu4x::capi::Date* date) {
    533  int32_t month = icu4x::capi::icu4x_Date_ordinal_month_mv1(date);
    534  MOZ_ASSERT(month > 0);
    535  return month;
    536 }
    537 
    538 static inline int32_t DayOfMonth(const icu4x::capi::Date* date) {
    539  int32_t dayOfMonth = icu4x::capi::icu4x_Date_day_of_month_mv1(date);
    540  MOZ_ASSERT(dayOfMonth > 0);
    541  return dayOfMonth;
    542 }
    543 
    544 static inline int32_t DayOfYear(const icu4x::capi::Date* date) {
    545  int32_t dayOfYear = icu4x::capi::icu4x_Date_day_of_year_mv1(date);
    546  MOZ_ASSERT(dayOfYear > 0);
    547  return dayOfYear;
    548 }
    549 
    550 static inline int32_t DaysInMonth(const icu4x::capi::Date* date) {
    551  int32_t daysInMonth = icu4x::capi::icu4x_Date_days_in_month_mv1(date);
    552  MOZ_ASSERT(daysInMonth > 0);
    553  return daysInMonth;
    554 }
    555 
    556 static inline int32_t DaysInYear(const icu4x::capi::Date* date) {
    557  int32_t daysInYear = icu4x::capi::icu4x_Date_days_in_year_mv1(date);
    558  MOZ_ASSERT(daysInYear > 0);
    559  return daysInYear;
    560 }
    561 
    562 static inline int32_t MonthsInYear(const icu4x::capi::Date* date) {
    563  int32_t monthsInYear = icu4x::capi::icu4x_Date_months_in_year_mv1(date);
    564  MOZ_ASSERT(monthsInYear > 0);
    565  return monthsInYear;
    566 }
    567 
    568 static auto ToAnyCalendarKind(CalendarId id) {
    569  switch (id) {
    570    case CalendarId::ISO8601:
    571      return icu4x::capi::CalendarKind_Iso;
    572    case CalendarId::Buddhist:
    573      return icu4x::capi::CalendarKind_Buddhist;
    574    case CalendarId::Chinese:
    575      return icu4x::capi::CalendarKind_Chinese;
    576    case CalendarId::Coptic:
    577      return icu4x::capi::CalendarKind_Coptic;
    578    case CalendarId::Dangi:
    579      return icu4x::capi::CalendarKind_Dangi;
    580    case CalendarId::Ethiopian:
    581      return icu4x::capi::CalendarKind_Ethiopian;
    582    case CalendarId::EthiopianAmeteAlem:
    583      return icu4x::capi::CalendarKind_EthiopianAmeteAlem;
    584    case CalendarId::Gregorian:
    585      return icu4x::capi::CalendarKind_Gregorian;
    586    case CalendarId::Hebrew:
    587      return icu4x::capi::CalendarKind_Hebrew;
    588    case CalendarId::Indian:
    589      return icu4x::capi::CalendarKind_Indian;
    590    case CalendarId::IslamicCivil:
    591      return icu4x::capi::CalendarKind_HijriTabularTypeIIFriday;
    592    case CalendarId::IslamicTabular:
    593      return icu4x::capi::CalendarKind_HijriTabularTypeIIThursday;
    594    case CalendarId::IslamicUmmAlQura:
    595      return icu4x::capi::CalendarKind_HijriUmmAlQura;
    596    case CalendarId::Japanese:
    597      return icu4x::capi::CalendarKind_Japanese;
    598    case CalendarId::Persian:
    599      return icu4x::capi::CalendarKind_Persian;
    600    case CalendarId::ROC:
    601      return icu4x::capi::CalendarKind_Roc;
    602  }
    603  MOZ_CRASH("invalid calendar id");
    604 }
    605 
    606 class ICU4XCalendarDeleter {
    607 public:
    608  void operator()(icu4x::capi::Calendar* ptr) {
    609    icu4x::capi::icu4x_Calendar_destroy_mv1(ptr);
    610  }
    611 };
    612 
    613 using UniqueICU4XCalendar =
    614    mozilla::UniquePtr<icu4x::capi::Calendar, ICU4XCalendarDeleter>;
    615 
    616 static UniqueICU4XCalendar CreateICU4XCalendar(CalendarId id) {
    617  auto* result = icu4x::capi::icu4x_Calendar_create_mv1(ToAnyCalendarKind(id));
    618  MOZ_ASSERT(result, "unexpected null-pointer result");
    619  return UniqueICU4XCalendar{result};
    620 }
    621 
    622 static uint32_t MaximumISOYear(CalendarId calendarId) {
    623  switch (calendarId) {
    624    case CalendarId::ISO8601:
    625    case CalendarId::Buddhist:
    626    case CalendarId::Coptic:
    627    case CalendarId::Ethiopian:
    628    case CalendarId::EthiopianAmeteAlem:
    629    case CalendarId::Gregorian:
    630    case CalendarId::Hebrew:
    631    case CalendarId::Indian:
    632    case CalendarId::IslamicCivil:
    633    case CalendarId::IslamicTabular:
    634    case CalendarId::Japanese:
    635    case CalendarId::Persian:
    636    case CalendarId::ROC: {
    637      // Passing values near INT32_{MIN,MAX} triggers ICU4X assertions, so we
    638      // have to handle large input years early.
    639      return 300'000;
    640    }
    641 
    642    case CalendarId::Chinese:
    643    case CalendarId::Dangi: {
    644      // Lower limit for these calendars to avoid running into ICU4X assertions.
    645      //
    646      // https://github.com/unicode-org/icu4x/issues/4917
    647      return 10'000;
    648    }
    649 
    650    case CalendarId::IslamicUmmAlQura: {
    651      // Lower limit for these calendars to avoid running into ICU4X assertions.
    652      //
    653      // https://github.com/unicode-org/icu4x/issues/4917
    654      return 5'000;
    655    }
    656  }
    657  MOZ_CRASH("invalid calendar");
    658 }
    659 
    660 static uint32_t MaximumCalendarYear(CalendarId calendarId) {
    661  switch (calendarId) {
    662    case CalendarId::ISO8601:
    663    case CalendarId::Buddhist:
    664    case CalendarId::Coptic:
    665    case CalendarId::Ethiopian:
    666    case CalendarId::EthiopianAmeteAlem:
    667    case CalendarId::Gregorian:
    668    case CalendarId::Hebrew:
    669    case CalendarId::Indian:
    670    case CalendarId::IslamicCivil:
    671    case CalendarId::IslamicTabular:
    672    case CalendarId::Japanese:
    673    case CalendarId::Persian:
    674    case CalendarId::ROC: {
    675      // Passing values near INT32_{MIN,MAX} triggers ICU4X assertions, so we
    676      // have to handle large input years early.
    677      return 300'000;
    678    }
    679 
    680    case CalendarId::Chinese:
    681    case CalendarId::Dangi: {
    682      // Lower limit for these calendars to avoid running into ICU4X assertions.
    683      //
    684      // https://github.com/unicode-org/icu4x/issues/4917
    685      return 10'000;
    686    }
    687 
    688    case CalendarId::IslamicUmmAlQura: {
    689      // Lower limit for these calendars to avoid running into ICU4X assertions.
    690      //
    691      // https://github.com/unicode-org/icu4x/issues/4917
    692      return 5'000;
    693    }
    694  }
    695  MOZ_CRASH("invalid calendar");
    696 }
    697 
    698 static void ReportCalendarFieldOverflow(JSContext* cx, const char* name,
    699                                        double num) {
    700  ToCStringBuf numCbuf;
    701  const char* numStr = NumberToCString(&numCbuf, num);
    702 
    703  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    704                            JSMSG_TEMPORAL_CALENDAR_OVERFLOW_FIELD, name,
    705                            numStr);
    706 }
    707 
    708 class ICU4XDateDeleter {
    709 public:
    710  void operator()(icu4x::capi::Date* ptr) {
    711    icu4x::capi::icu4x_Date_destroy_mv1(ptr);
    712  }
    713 };
    714 
    715 using UniqueICU4XDate = mozilla::UniquePtr<icu4x::capi::Date, ICU4XDateDeleter>;
    716 
    717 static UniqueICU4XDate CreateICU4XDate(JSContext* cx, const ISODate& date,
    718                                       CalendarId calendarId,
    719                                       const icu4x::capi::Calendar* calendar) {
    720  if (mozilla::Abs(date.year) > MaximumISOYear(calendarId)) {
    721    ReportCalendarFieldOverflow(cx, "year", date.year);
    722    return nullptr;
    723  }
    724 
    725  auto result = icu4x::capi::icu4x_Date_from_iso_in_calendar_mv1(
    726      date.year, date.month, date.day, calendar);
    727  if (!result.is_ok) {
    728    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    729                              JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR);
    730    return nullptr;
    731  }
    732  return UniqueICU4XDate{result.ok};
    733 }
    734 
    735 class ICU4XIsoDateDeleter {
    736 public:
    737  void operator()(icu4x::capi::IsoDate* ptr) {
    738    icu4x::capi::icu4x_IsoDate_destroy_mv1(ptr);
    739  }
    740 };
    741 
    742 using UniqueICU4XIsoDate =
    743    mozilla::UniquePtr<icu4x::capi::IsoDate, ICU4XIsoDateDeleter>;
    744 
    745 static constexpr size_t EraNameMaxLength() {
    746  size_t length = 0;
    747  for (auto calendar : AvailableCalendars()) {
    748    for (auto era : CalendarEras(calendar)) {
    749      for (auto name : CalendarEraNames(calendar, era)) {
    750        length = std::max(length, name.length());
    751      }
    752    }
    753  }
    754  return length;
    755 }
    756 
    757 /**
    758 * CanonicalizeEraInCalendar ( calendar, era )
    759 */
    760 static mozilla::Maybe<EraCode> CanonicalizeEraInCalendar(
    761    CalendarId calendar, JSLinearString* string) {
    762  MOZ_ASSERT(CalendarSupportsEra(calendar));
    763 
    764  // Note: Assigning MaxLength to EraNameMaxLength() breaks the CDT indexer.
    765  constexpr size_t MaxLength = 8;
    766  static_assert(MaxLength >= EraNameMaxLength(),
    767                "Storage size is at least as large as the largest known era");
    768 
    769  if (string->length() > MaxLength || !StringIsAscii(string)) {
    770    return mozilla::Nothing();
    771  }
    772 
    773  char chars[MaxLength] = {};
    774  CopyChars(reinterpret_cast<JS::Latin1Char*>(chars), *string);
    775 
    776  auto stringView = std::string_view{chars, string->length()};
    777 
    778  for (auto era : CalendarEras(calendar)) {
    779    for (auto name : CalendarEraNames(calendar, era)) {
    780      if (name == stringView) {
    781        return mozilla::Some(era);
    782      }
    783    }
    784  }
    785  return mozilla::Nothing();
    786 }
    787 
    788 static constexpr std::string_view IcuEraName(CalendarId calendar, EraCode era) {
    789  switch (calendar) {
    790    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Iso.html#era-codes
    791    case CalendarId::ISO8601: {
    792      MOZ_ASSERT(era == EraCode::Standard);
    793      return "default";
    794    }
    795 
    796    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Buddhist.html#era-codes
    797    case CalendarId::Buddhist: {
    798      MOZ_ASSERT(era == EraCode::Standard);
    799      return "be";
    800    }
    801 
    802    // https://docs.rs/icu/latest/icu/calendar/cal/east_asian_traditional/struct.EastAsianTraditional.html#year-and-era-codes
    803    case CalendarId::Chinese: {
    804      MOZ_ASSERT(era == EraCode::Standard);
    805      return "";
    806    }
    807 
    808    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Coptic.html#era-codes
    809    case CalendarId::Coptic: {
    810      MOZ_ASSERT(era == EraCode::Standard);
    811      return "am";
    812    }
    813 
    814    // https://docs.rs/icu/latest/icu/calendar/cal/east_asian_traditional/struct.EastAsianTraditional.html#year-and-era-codes
    815    case CalendarId::Dangi: {
    816      MOZ_ASSERT(era == EraCode::Standard);
    817      return "";
    818    }
    819 
    820    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Ethiopian.html#era-codes
    821    case CalendarId::Ethiopian: {
    822      MOZ_ASSERT(era == EraCode::Standard);
    823      return "am";
    824    }
    825 
    826    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Ethiopian.html#era-codes
    827    case CalendarId::EthiopianAmeteAlem: {
    828      MOZ_ASSERT(era == EraCode::Standard);
    829      return "aa";
    830    }
    831 
    832    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Gregorian.html#era-codes
    833    case CalendarId::Gregorian: {
    834      MOZ_ASSERT(era == EraCode::Standard || era == EraCode::Inverse);
    835      return era == EraCode::Standard ? "ce" : "bce";
    836    }
    837 
    838    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Hebrew.html#era-codes
    839    case CalendarId::Hebrew: {
    840      MOZ_ASSERT(era == EraCode::Standard);
    841      return "am";
    842    }
    843 
    844    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Indian.html#era-codes
    845    case CalendarId::Indian: {
    846      MOZ_ASSERT(era == EraCode::Standard);
    847      return "shaka";
    848    }
    849 
    850    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Hijri.html#era-codes
    851    case CalendarId::IslamicCivil:
    852    case CalendarId::IslamicTabular:
    853    case CalendarId::IslamicUmmAlQura: {
    854      MOZ_ASSERT(era == EraCode::Standard || era == EraCode::Inverse);
    855      return era == EraCode::Standard ? "ah" : "bh";
    856    }
    857 
    858    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Persian.html#era-codes
    859    case CalendarId::Persian: {
    860      MOZ_ASSERT(era == EraCode::Standard);
    861      return "ap";
    862    }
    863 
    864    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Japanese.html#era-codes
    865    case CalendarId::Japanese: {
    866      switch (era) {
    867        case EraCode::Standard:
    868          return "ce";
    869        case EraCode::Inverse:
    870          return "bce";
    871        case EraCode::Meiji:
    872          return "meiji";
    873        case EraCode::Taisho:
    874          return "taisho";
    875        case EraCode::Showa:
    876          return "showa";
    877        case EraCode::Heisei:
    878          return "heisei";
    879        case EraCode::Reiwa:
    880          return "reiwa";
    881      }
    882      break;
    883    }
    884 
    885    // https://docs.rs/icu/latest/icu/calendar/cal/struct.Roc.html#era-codes
    886    case CalendarId::ROC: {
    887      MOZ_ASSERT(era == EraCode::Standard || era == EraCode::Inverse);
    888      return era == EraCode::Standard ? "roc" : "broc";
    889    }
    890  }
    891  MOZ_CRASH("invalid era");
    892 }
    893 
    894 enum class CalendarError {
    895  // Catch-all kind for all other error types.
    896  Generic,
    897 
    898  // https://docs.rs/icu/latest/icu/calendar/enum.DateError.html#variant.Range
    899  OutOfRange,
    900 
    901  // https://docs.rs/icu/latest/icu/calendar/enum.DateError.html#variant.UnknownEra
    902  UnknownEra,
    903 
    904  // https://docs.rs/icu/latest/icu/calendar/enum.DateError.html#variant.UnknownMonthCode
    905  UnknownMonthCode,
    906 };
    907 
    908 #ifdef DEBUG
    909 static auto CalendarErasAsEnumSet(CalendarId calendarId) {
    910  // `mozilla::EnumSet<EraCode>(CalendarEras(calendarId))` doesn't work in old
    911  // GCC versions, so add all era codes manually to the enum set.
    912  mozilla::EnumSet<EraCode> eras{};
    913  for (auto era : CalendarEras(calendarId)) {
    914    eras += era;
    915  }
    916  return eras;
    917 }
    918 #endif
    919 
    920 static mozilla::Result<UniqueICU4XDate, CalendarError> CreateDateFromCodes(
    921    CalendarId calendarId, const icu4x::capi::Calendar* calendar,
    922    EraYear eraYear, MonthCode monthCode, int32_t day) {
    923  MOZ_ASSERT(calendarId != CalendarId::ISO8601);
    924  MOZ_ASSERT(icu4x::capi::icu4x_Calendar_kind_mv1(calendar) ==
    925             ToAnyCalendarKind(calendarId));
    926  MOZ_ASSERT(CalendarErasAsEnumSet(calendarId).contains(eraYear.era));
    927  MOZ_ASSERT_IF(CalendarEraHasInverse(calendarId), eraYear.year > 0);
    928  MOZ_ASSERT(mozilla::Abs(eraYear.year) <= MaximumCalendarYear(calendarId));
    929  MOZ_ASSERT(IsValidMonthCodeForCalendar(calendarId, monthCode));
    930  MOZ_ASSERT(day > 0);
    931  MOZ_ASSERT(day <= CalendarDaysInMonth(calendarId).second);
    932 
    933  auto era = IcuEraName(calendarId, eraYear.era);
    934  auto monthCodeView = std::string_view{monthCode};
    935  auto date = icu4x::capi::icu4x_Date_from_codes_in_calendar_mv1(
    936      diplomat::capi::DiplomatStringView{era.data(), era.length()},
    937      eraYear.year,
    938      diplomat::capi::DiplomatStringView{monthCodeView.data(),
    939                                         monthCodeView.length()},
    940      day, calendar);
    941  if (date.is_ok) {
    942    return UniqueICU4XDate{date.ok};
    943  }
    944 
    945  // Map possible calendar errors.
    946  //
    947  // Calendar error codes which can't happen for `create_from_codes_in_calendar`
    948  // are mapped to `CalendarError::Generic`.
    949  switch (date.err) {
    950    case icu4x::capi::CalendarError_OutOfRange:
    951      return mozilla::Err(CalendarError::OutOfRange);
    952    case icu4x::capi::CalendarError_UnknownEra:
    953      return mozilla::Err(CalendarError::UnknownEra);
    954    case icu4x::capi::CalendarError_UnknownMonthCode:
    955      return mozilla::Err(CalendarError::UnknownMonthCode);
    956    default:
    957      return mozilla::Err(CalendarError::Generic);
    958  }
    959 }
    960 
    961 /**
    962 * Return the first year (gannen) of a Japanese era.
    963 */
    964 static bool FirstYearOfJapaneseEra(JSContext* cx, CalendarId calendarId,
    965                                   const icu4x::capi::Calendar* calendar,
    966                                   EraCode era, int32_t* result) {
    967  MOZ_ASSERT(calendarId == CalendarId::Japanese);
    968  MOZ_ASSERT(IsJapaneseEraName(era));
    969 
    970  // All supported Japanese eras last at least one year, so December 31 is
    971  // guaranteed to be in the first year of the era.
    972  auto dateResult =
    973      CreateDateFromCodes(calendarId, calendar, {era, 1}, MonthCode{12}, 31);
    974  if (dateResult.isErr()) {
    975    MOZ_ASSERT(dateResult.inspectErr() == CalendarError::Generic,
    976               "unexpected non-generic calendar error");
    977 
    978    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    979                              JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR);
    980    return false;
    981  }
    982 
    983  auto date = dateResult.unwrap();
    984  UniqueICU4XIsoDate isoDate{icu4x::capi::icu4x_Date_to_iso_mv1(date.get())};
    985  MOZ_ASSERT(isoDate, "unexpected null-pointer result");
    986 
    987  int32_t isoYear = icu4x::capi::icu4x_IsoDate_year_mv1(isoDate.get());
    988  MOZ_ASSERT(isoYear > 0, "unexpected era start before 1 CE");
    989 
    990  *result = isoYear;
    991  return true;
    992 }
    993 
    994 /**
    995 * Return the equivalent common era year for a Japanese era year.
    996 */
    997 static bool JapaneseEraYearToCommonEraYear(
    998    JSContext* cx, CalendarId calendarId, const icu4x::capi::Calendar* calendar,
    999    EraYear eraYear, EraYear* result) {
   1000  int32_t firstYearOfEra;
   1001  if (!FirstYearOfJapaneseEra(cx, calendarId, calendar, eraYear.era,
   1002                              &firstYearOfEra)) {
   1003    return false;
   1004  }
   1005 
   1006  // Map non-positive era years to years before the first era year:
   1007  //
   1008  //  1 Reiwa =  2019 CE
   1009  //  0 Reiwa -> 2018 CE
   1010  // -1 Reiwa -> 2017 CE
   1011  // etc.
   1012  //
   1013  // Map too large era years to the next era:
   1014  //
   1015  // Heisei 31 =  2019 CE
   1016  // Heisei 32 -> 2020 CE
   1017  // ...
   1018 
   1019  int32_t year = (firstYearOfEra - 1) + eraYear.year;
   1020  if (year > 0) {
   1021    *result = {EraCode::Standard, year};
   1022    return true;
   1023  }
   1024  *result = {EraCode::Inverse, int32_t(mozilla::Abs(year) + 1)};
   1025  return true;
   1026 }
   1027 
   1028 static constexpr int32_t ethiopianYearsFromCreationToIncarnation = 5500;
   1029 
   1030 static int32_t FromAmeteAlemToAmeteMihret(int32_t year) {
   1031  // Subtract the number of years from creation to incarnation to anchor
   1032  // at the date of incarnation.
   1033  return year - ethiopianYearsFromCreationToIncarnation;
   1034 }
   1035 
   1036 static int32_t FromAmeteMihretToAmeteAlem(int32_t year) {
   1037  // Add the number of years from creation to incarnation to anchor at the date
   1038  // of creation.
   1039  return year + ethiopianYearsFromCreationToIncarnation;
   1040 }
   1041 
   1042 /**
   1043 * ConstrainMonthCode ( calendar, arithmeticYear, monthCode, overflow )
   1044 */
   1045 static bool ConstrainMonthCode(JSContext* cx, CalendarId calendar,
   1046                               MonthCode monthCode, TemporalOverflow overflow,
   1047                               MonthCode* result) {
   1048  // Step 1.
   1049  MOZ_ASSERT(IsValidMonthCodeForCalendar(calendar, monthCode));
   1050 
   1051  // Steps 2 and 4.
   1052  MOZ_ASSERT(CalendarHasLeapMonths(calendar));
   1053  MOZ_ASSERT(monthCode.isLeapMonth());
   1054 
   1055  // Step 3.
   1056  if (overflow == TemporalOverflow::Reject) {
   1057    // Ensure the month code is null-terminated.
   1058    char code[5] = {};
   1059    auto monthCodeView = std::string_view{monthCode};
   1060    monthCodeView.copy(code, monthCodeView.length());
   1061 
   1062    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1063                             JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE, code);
   1064    return false;
   1065  }
   1066 
   1067  // Steps 5-6.
   1068  bool skipBackward =
   1069      calendar == CalendarId::Chinese || calendar == CalendarId::Dangi;
   1070 
   1071  // Step 7.
   1072  if (skipBackward) {
   1073    // Step 7.a.
   1074    *result = MonthCode{monthCode.ordinal()};
   1075    return true;
   1076  }
   1077 
   1078  // Step 8.a
   1079  MOZ_ASSERT(calendar == CalendarId::Hebrew);
   1080  MOZ_ASSERT(monthCode.code() == MonthCode::Code::M05L);
   1081 
   1082  // Step 8.b
   1083  *result = MonthCode{6};
   1084  return true;
   1085 }
   1086 
   1087 static UniqueICU4XDate CreateDateFromCodes(
   1088    JSContext* cx, CalendarId calendarId, const icu4x::capi::Calendar* calendar,
   1089    EraYear eraYear, MonthCode monthCode, int32_t day,
   1090    TemporalOverflow overflow) {
   1091  MOZ_ASSERT(IsValidMonthCodeForCalendar(calendarId, monthCode));
   1092  MOZ_ASSERT(day > 0);
   1093  MOZ_ASSERT(day <= CalendarDaysInMonth(calendarId).second);
   1094 
   1095  // Constrain day to the maximum possible day for the input month.
   1096  //
   1097  // Special cases like February 29 in leap years of the Gregorian calendar are
   1098  // handled below.
   1099  int32_t daysInMonth = CalendarDaysInMonth(calendarId, monthCode).second;
   1100  if (overflow == TemporalOverflow::Constrain) {
   1101    day = std::min(day, daysInMonth);
   1102  } else {
   1103    MOZ_ASSERT(overflow == TemporalOverflow::Reject);
   1104 
   1105    if (day > daysInMonth) {
   1106      ReportCalendarFieldOverflow(cx, "day", day);
   1107      return nullptr;
   1108    }
   1109  }
   1110 
   1111  // ICU4X doesn't support large dates, so we have to handle this case early.
   1112  if (mozilla::Abs(eraYear.year) > MaximumCalendarYear(calendarId)) {
   1113    ReportCalendarFieldOverflow(cx, "year", eraYear.year);
   1114    return nullptr;
   1115  }
   1116 
   1117  // ICU4X requires to switch from Amete Mihret to Amete Alem calendar when the
   1118  // year is non-positive.
   1119  //
   1120  // https://unicode-org.atlassian.net/browse/CLDR-18739
   1121  if (calendarId == CalendarId::Ethiopian && eraYear.year <= 0) {
   1122    auto cal = CreateICU4XCalendar(CalendarId::EthiopianAmeteAlem);
   1123    return CreateDateFromCodes(
   1124        cx, CalendarId::EthiopianAmeteAlem, cal.get(),
   1125        {EraCode::Standard, FromAmeteMihretToAmeteAlem(eraYear.year)},
   1126        monthCode, day, overflow);
   1127  }
   1128 
   1129  auto result =
   1130      CreateDateFromCodes(calendarId, calendar, eraYear, monthCode, day);
   1131  if (result.isOk()) {
   1132    return result.unwrap();
   1133  }
   1134 
   1135  switch (result.inspectErr()) {
   1136    case CalendarError::UnknownMonthCode: {
   1137      // We've asserted above that |monthCode| is valid for this calendar, so
   1138      // any unknown month code must be for a leap month which doesn't happen in
   1139      // the current year.
   1140      MonthCode constrained;
   1141      if (!ConstrainMonthCode(cx, calendarId, monthCode, overflow,
   1142                              &constrained)) {
   1143        return nullptr;
   1144      }
   1145      MOZ_ASSERT(!constrained.isLeapMonth());
   1146 
   1147      // Retry as non-leap month when we're allowed to constrain.
   1148      return CreateDateFromCodes(cx, calendarId, calendar, eraYear, constrained,
   1149                                 day, overflow);
   1150    }
   1151 
   1152    case CalendarError::OutOfRange: {
   1153      // ICU4X throws an out-of-range error if:
   1154      // 1. Dates are before/after the requested named Japanese era.
   1155      // 2. month > monthsInYear(year), or
   1156      // 3. days > daysInMonthOf(year, month).
   1157 
   1158      // If a named Japanese era is used, this can be an error for either case 1
   1159      // or case 3. Handle a possible case 1 error first by mapping the era year
   1160      // to a common era year and then re-try creating the date.
   1161      if (calendarId == CalendarId::Japanese &&
   1162          IsJapaneseEraName(eraYear.era)) {
   1163        EraYear commonEraYear;
   1164        if (!JapaneseEraYearToCommonEraYear(cx, calendarId, calendar, eraYear,
   1165                                            &commonEraYear)) {
   1166          return nullptr;
   1167        }
   1168        return CreateDateFromCodes(cx, calendarId, calendar, commonEraYear,
   1169                                   monthCode, day, overflow);
   1170      }
   1171 
   1172      // Case 2 can't happen for month-codes, so it doesn't apply here.
   1173      // Case 3 can only happen when |day| is larger than the minimum number
   1174      // of days in the month.
   1175      MOZ_ASSERT(day > CalendarDaysInMonth(calendarId, monthCode).first);
   1176 
   1177      if (overflow == TemporalOverflow::Reject) {
   1178        ReportCalendarFieldOverflow(cx, "day", day);
   1179        return nullptr;
   1180      }
   1181 
   1182      auto firstDayOfMonth = CreateDateFromCodes(
   1183          cx, calendarId, calendar, eraYear, monthCode, 1, overflow);
   1184      if (!firstDayOfMonth) {
   1185        return nullptr;
   1186      }
   1187 
   1188      int32_t daysInMonth = DaysInMonth(firstDayOfMonth.get());
   1189      MOZ_ASSERT(day > daysInMonth);
   1190      return CreateDateFromCodes(cx, calendarId, calendar, eraYear, monthCode,
   1191                                 daysInMonth, overflow);
   1192    }
   1193 
   1194    case CalendarError::UnknownEra:
   1195      MOZ_ASSERT(false, "unexpected calendar error");
   1196      break;
   1197 
   1198    case CalendarError::Generic:
   1199      break;
   1200  }
   1201 
   1202  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1203                            JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR);
   1204  return nullptr;
   1205 }
   1206 
   1207 static UniqueICU4XDate CreateDateFrom(JSContext* cx, CalendarId calendarId,
   1208                                      const icu4x::capi::Calendar* calendar,
   1209                                      EraYear eraYear, int32_t month,
   1210                                      int32_t day, TemporalOverflow overflow) {
   1211  MOZ_ASSERT(calendarId != CalendarId::ISO8601);
   1212  MOZ_ASSERT(month > 0);
   1213  MOZ_ASSERT(day > 0);
   1214  MOZ_ASSERT(month <= CalendarMonthsPerYear(calendarId));
   1215  MOZ_ASSERT(day <= CalendarDaysInMonth(calendarId).second);
   1216 
   1217  switch (calendarId) {
   1218    case CalendarId::ISO8601:
   1219    case CalendarId::Buddhist:
   1220    case CalendarId::Coptic:
   1221    case CalendarId::Ethiopian:
   1222    case CalendarId::EthiopianAmeteAlem:
   1223    case CalendarId::Gregorian:
   1224    case CalendarId::Indian:
   1225    case CalendarId::IslamicCivil:
   1226    case CalendarId::IslamicTabular:
   1227    case CalendarId::IslamicUmmAlQura:
   1228    case CalendarId::Japanese:
   1229    case CalendarId::Persian:
   1230    case CalendarId::ROC: {
   1231      MOZ_ASSERT(!CalendarHasLeapMonths(calendarId));
   1232 
   1233      // Use the month-code corresponding to the ordinal month number for
   1234      // calendar systems without leap months.
   1235      auto date = CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1236                                      MonthCode{month}, day, overflow);
   1237      if (!date) {
   1238        return nullptr;
   1239      }
   1240      MOZ_ASSERT_IF(!CalendarHasMidYearEras(calendarId),
   1241                    OrdinalMonth(date.get()) == month);
   1242      return date;
   1243    }
   1244 
   1245    case CalendarId::Dangi:
   1246    case CalendarId::Chinese: {
   1247      static_assert(CalendarHasLeapMonths(CalendarId::Chinese));
   1248      static_assert(CalendarMonthsPerYear(CalendarId::Chinese) == 13);
   1249      static_assert(CalendarHasLeapMonths(CalendarId::Dangi));
   1250      static_assert(CalendarMonthsPerYear(CalendarId::Dangi) == 13);
   1251 
   1252      MOZ_ASSERT(1 <= month && month <= 13);
   1253 
   1254      // Create date with month number replaced by month-code.
   1255      auto monthCode = MonthCode{std::min(month, 12)};
   1256      auto date = CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1257                                      monthCode, day, overflow);
   1258      if (!date) {
   1259        return nullptr;
   1260      }
   1261 
   1262      // If the ordinal month of |date| matches the input month, no additional
   1263      // changes are necessary and we can directly return |date|.
   1264      int32_t ordinal = OrdinalMonth(date.get());
   1265      if (ordinal == month) {
   1266        return date;
   1267      }
   1268 
   1269      // Otherwise we need to handle three cases:
   1270      // 1. The input year contains a leap month and we need to adjust the
   1271      //    month-code.
   1272      // 2. The thirteenth month of a year without leap months was requested.
   1273      // 3. The thirteenth month of a year with leap months was requested.
   1274      if (ordinal > month) {
   1275        MOZ_ASSERT(1 < month && month <= 12);
   1276 
   1277        // This case can only happen in leap years.
   1278        MOZ_ASSERT(MonthsInYear(date.get()) == 13);
   1279 
   1280        // Leap months can occur after any month in the Chinese calendar.
   1281        //
   1282        // Example when the fourth month is a leap month between M03 and M04.
   1283        //
   1284        // Month code:     M01  M02  M03  M03L  M04  M05  M06 ...
   1285        // Ordinal month:  1    2    3    4     5    6    7
   1286 
   1287        // The month can be off by exactly one.
   1288        MOZ_ASSERT((ordinal - month) == 1);
   1289 
   1290        // First try the case when the previous month isn't a leap month. This
   1291        // case can only occur when |month > 2|, because otherwise we know that
   1292        // "M01L" is the correct answer.
   1293        if (month > 2) {
   1294          auto previousMonthCode = MonthCode{month - 1};
   1295          date = CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1296                                     previousMonthCode, day, overflow);
   1297          if (!date) {
   1298            return nullptr;
   1299          }
   1300 
   1301          int32_t ordinal = OrdinalMonth(date.get());
   1302          if (ordinal == month) {
   1303            return date;
   1304          }
   1305        }
   1306 
   1307        // Fall-through when the previous month is a leap month.
   1308      } else {
   1309        MOZ_ASSERT(month == 13);
   1310        MOZ_ASSERT(ordinal == 12);
   1311 
   1312        // Years with leap months contain thirteen months.
   1313        if (MonthsInYear(date.get()) != 13) {
   1314          if (overflow == TemporalOverflow::Reject) {
   1315            ReportCalendarFieldOverflow(cx, "month", month);
   1316            return nullptr;
   1317          }
   1318          return date;
   1319        }
   1320 
   1321        // Fall-through to return leap month "M12L" at the end of the year.
   1322      }
   1323 
   1324      // Finally handle the case when the previous month is a leap month.
   1325      auto leapMonthCode = MonthCode{month - 1, /* isLeapMonth= */ true};
   1326      date = CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1327                                 leapMonthCode, day, overflow);
   1328      if (!date) {
   1329        return nullptr;
   1330      }
   1331      MOZ_ASSERT(OrdinalMonth(date.get()) == month, "unexpected ordinal month");
   1332      return date;
   1333    }
   1334 
   1335    case CalendarId::Hebrew: {
   1336      static_assert(CalendarHasLeapMonths(CalendarId::Hebrew));
   1337      static_assert(CalendarMonthsPerYear(CalendarId::Hebrew) == 13);
   1338 
   1339      MOZ_ASSERT(1 <= month && month <= 13);
   1340 
   1341      // Constrain |day| when overflow is "reject" to avoid rejecting too large
   1342      // day values in CreateDateFromCodes.
   1343      //
   1344      // For example when month = 10 and day = 30 and the input year is a leap
   1345      // year. We first try month code "M10", but since "M10" can have at most
   1346      // 29 days, we need to constrain the days value before calling
   1347      // CreateDateFromCodes.
   1348      int32_t constrainedDay = day;
   1349      if (overflow == TemporalOverflow::Reject) {
   1350        constexpr auto daysInMonth = CalendarDaysInMonth(CalendarId::Hebrew);
   1351        if (day > daysInMonth.first && day <= daysInMonth.second) {
   1352          constrainedDay = daysInMonth.first;
   1353        }
   1354      }
   1355 
   1356      // Create date with month number replaced by month-code.
   1357      auto monthCode = MonthCode{std::min(month, 12)};
   1358      auto date = CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1359                                      monthCode, constrainedDay, overflow);
   1360      if (!date) {
   1361        return nullptr;
   1362      }
   1363 
   1364      // If the ordinal month of |date| matches the input month, no additional
   1365      // changes are necessary and we can directly return |date|.
   1366      int32_t ordinal = OrdinalMonth(date.get());
   1367      if (ordinal == month) {
   1368        // If |day| was constrained, check if the actual input days value
   1369        // exceeds the number of days in the resolved month.
   1370        if (constrainedDay < day) {
   1371          MOZ_ASSERT(overflow == TemporalOverflow::Reject);
   1372 
   1373          if (day > CalendarDaysInMonth(calendarId, monthCode).second) {
   1374            ReportCalendarFieldOverflow(cx, "day", day);
   1375            return nullptr;
   1376          }
   1377          return CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1378                                     monthCode, day, overflow);
   1379        }
   1380        return date;
   1381      }
   1382 
   1383      // Otherwise we need to handle two cases:
   1384      // 1. The input year contains a leap month and we need to adjust the
   1385      //    month-code.
   1386      // 2. The thirteenth month of a year without leap months was requested.
   1387      if (ordinal > month) {
   1388        MOZ_ASSERT(1 < month && month <= 12);
   1389 
   1390        // This case can only happen in leap years.
   1391        MOZ_ASSERT(MonthsInYear(date.get()) == 13);
   1392 
   1393        // Leap months can occur between M05 and M06 in the Hebrew calendar.
   1394        //
   1395        // Month code:     M01  M02  M03  M04  M05  M05L  M06 ...
   1396        // Ordinal month:  1    2    3    4    5    6     7
   1397 
   1398        // The month can be off by exactly one.
   1399        MOZ_ASSERT((ordinal - month) == 1);
   1400      } else {
   1401        MOZ_ASSERT(month == 13);
   1402        MOZ_ASSERT(ordinal == 12);
   1403 
   1404        if (overflow == TemporalOverflow::Reject) {
   1405          ReportCalendarFieldOverflow(cx, "month", month);
   1406          return nullptr;
   1407        }
   1408        return date;
   1409      }
   1410 
   1411      // The previous month is the leap month Adar I iff |month| is six.
   1412      bool isLeapMonth = month == 6;
   1413      auto previousMonthCode = MonthCode{month - 1, isLeapMonth};
   1414      date = CreateDateFromCodes(cx, calendarId, calendar, eraYear,
   1415                                 previousMonthCode, day, overflow);
   1416      if (!date) {
   1417        return nullptr;
   1418      }
   1419      MOZ_ASSERT(OrdinalMonth(date.get()) == month, "unexpected ordinal month");
   1420      return date;
   1421    }
   1422  }
   1423  MOZ_CRASH("invalid calendar id");
   1424 }
   1425 
   1426 static constexpr size_t ICUEraNameMaxLength() {
   1427  size_t length = 0;
   1428  for (auto calendar : AvailableCalendars()) {
   1429    for (auto era : CalendarEras(calendar)) {
   1430      auto name = IcuEraName(calendar, era);
   1431      length = std::max(length, name.length());
   1432    }
   1433  }
   1434  return length;
   1435 }
   1436 
   1437 class EraName {
   1438  // Note: Assigning MaxLength to ICUEraNameMaxLength() breaks the CDT indexer.
   1439  static constexpr size_t MaxLength = 7;
   1440 
   1441 // Disable tautological-value-range-compare to avoid a bogus Clang warning.
   1442 // See bug 1956918 and bug 1936626.
   1443 #ifdef __clang__
   1444 #  pragma clang diagnostic push
   1445 #  pragma clang diagnostic ignored "-Wtautological-value-range-compare"
   1446 #endif
   1447 
   1448  static_assert(MaxLength >= ICUEraNameMaxLength(),
   1449                "Storage size is at least as large as the largest known era");
   1450 
   1451 #ifdef __clang__
   1452 #  pragma clang diagnostic pop
   1453 #endif
   1454 
   1455  // Storage for the largest known era string and the terminating NUL-character.
   1456  char buf[MaxLength + 1] = {};
   1457  size_t length = 0;
   1458 
   1459 public:
   1460  explicit EraName(const icu4x::capi::Date* date) {
   1461    auto writable = diplomat::capi::diplomat_simple_write(buf, std::size(buf));
   1462 
   1463    icu4x::capi::icu4x_Date_era_mv1(date, &writable);
   1464    MOZ_ASSERT(writable.buf == buf, "unexpected buffer relocation");
   1465 
   1466    length = writable.len;
   1467  }
   1468 
   1469  bool operator==(std::string_view sv) const {
   1470    return std::string_view{buf, length} == sv;
   1471  }
   1472 
   1473  bool operator!=(std::string_view sv) const { return !(*this == sv); }
   1474 };
   1475 
   1476 /**
   1477 * Retrieve the era code from |date| and then map the returned ICU4X era code to
   1478 * the corresponding |EraCode| member.
   1479 */
   1480 static bool CalendarDateEra(JSContext* cx, CalendarId calendar,
   1481                            const icu4x::capi::Date* date, EraCode* result) {
   1482  MOZ_ASSERT(calendar != CalendarId::ISO8601);
   1483 
   1484  auto eraName = EraName(date);
   1485 
   1486  // Map from era name to era code.
   1487  for (auto era : CalendarEras(calendar)) {
   1488    if (eraName == IcuEraName(calendar, era)) {
   1489      *result = era;
   1490      return true;
   1491    }
   1492  }
   1493 
   1494  // Invalid/Unknown era name.
   1495  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1496                            JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR);
   1497  return false;
   1498 }
   1499 
   1500 /**
   1501 * Return the extended (non-era) year from |date|.
   1502 */
   1503 static int32_t CalendarDateYear(CalendarId calendar,
   1504                                const icu4x::capi::Date* date) {
   1505  MOZ_ASSERT(calendar != CalendarId::ISO8601);
   1506 
   1507  switch (calendar) {
   1508    case CalendarId::ISO8601:
   1509    case CalendarId::Buddhist:
   1510    case CalendarId::Coptic:
   1511    case CalendarId::EthiopianAmeteAlem:
   1512    case CalendarId::Hebrew:
   1513    case CalendarId::Indian:
   1514    case CalendarId::Persian:
   1515    case CalendarId::Gregorian:
   1516    case CalendarId::IslamicCivil:
   1517    case CalendarId::IslamicTabular:
   1518    case CalendarId::IslamicUmmAlQura:
   1519    case CalendarId::Japanese: {
   1520      return icu4x::capi::icu4x_Date_extended_year_mv1(date);
   1521    }
   1522 
   1523    case CalendarId::Chinese:
   1524    case CalendarId::Dangi: {
   1525      // Return the related ISO year for Chinese/Dangi.
   1526      return icu4x::capi::icu4x_Date_era_year_or_related_iso_mv1(date);
   1527    }
   1528 
   1529    case CalendarId::Ethiopian: {
   1530      // ICU4X implements the current CLDR rules for Ethopian (Amete Mihret)
   1531      // calendar eras. It's unclear if CLDR reflects modern use of the
   1532      // calendar, therefore we map all years to a single era, anchored at the
   1533      // date of incarnation.
   1534      //
   1535      // https://unicode-org.atlassian.net/browse/CLDR-18739
   1536 
   1537      int32_t year = icu4x::capi::icu4x_Date_extended_year_mv1(date);
   1538 
   1539      auto eraName = EraName(date);
   1540      MOZ_ASSERT(
   1541          eraName == IcuEraName(CalendarId::Ethiopian, EraCode::Standard) ||
   1542          eraName ==
   1543              IcuEraName(CalendarId::EthiopianAmeteAlem, EraCode::Standard));
   1544 
   1545      // Workaround for <https://github.com/unicode-org/icu4x/issues/6719>.
   1546      if (eraName ==
   1547          IcuEraName(CalendarId::EthiopianAmeteAlem, EraCode::Standard)) {
   1548        year = FromAmeteAlemToAmeteMihret(year);
   1549      }
   1550 
   1551      return year;
   1552    }
   1553 
   1554    case CalendarId::ROC: {
   1555      static_assert(CalendarEras(CalendarId::ROC).size() == 2);
   1556 
   1557      // ICU4X returns the related ISO year for the extended year, but we want
   1558      // to anchor the extended year at 1 ROC instead.
   1559      //
   1560      // https://github.com/unicode-org/icu4x/issues/6720
   1561 
   1562      int32_t year = icu4x::capi::icu4x_Date_era_year_or_related_iso_mv1(date);
   1563      MOZ_ASSERT(year > 0, "era years are strictly positive in ICU4X");
   1564 
   1565      auto eraName = EraName(date);
   1566      MOZ_ASSERT(eraName == IcuEraName(CalendarId::ROC, EraCode::Standard) ||
   1567                 eraName == IcuEraName(CalendarId::ROC, EraCode::Inverse));
   1568 
   1569      // Map from era year to extended year. Examples:
   1570      //
   1571      // ----------------------------
   1572      // | Era Year | Extended Year |
   1573      // | 2 ROC    |  2            |
   1574      // | 1 ROC    |  1            |
   1575      // | 1 BROC   |  0            |
   1576      // | 2 BROC   | -1            |
   1577      // ----------------------------
   1578      if (eraName == IcuEraName(CalendarId::ROC, EraCode::Inverse)) {
   1579        year = -(year - 1);
   1580      }
   1581 
   1582      return year;
   1583    }
   1584  }
   1585  MOZ_CRASH("invalid calendar id");
   1586 }
   1587 
   1588 /**
   1589 * Retrieve the month code from |date| and then map the returned ICU4X month
   1590 * code to the corresponding |MonthCode| member.
   1591 */
   1592 static MonthCode CalendarDateMonthCode(CalendarId calendar,
   1593                                       const icu4x::capi::Date* date) {
   1594  MOZ_ASSERT(calendar != CalendarId::ISO8601);
   1595 
   1596  // Valid month codes are "M01".."M13" and "M01L".."M12L".
   1597  constexpr size_t MaxLength =
   1598      std::string_view{MonthCode::maxLeapMonth()}.length();
   1599  static_assert(
   1600      MaxLength > std::string_view{MonthCode::maxNonLeapMonth()}.length(),
   1601      "string representation of max-leap month is larger");
   1602 
   1603  // Storage for the largest valid month code and the terminating NUL-character.
   1604  char buf[MaxLength + 1] = {};
   1605  auto writable = diplomat::capi::diplomat_simple_write(buf, std::size(buf));
   1606 
   1607  icu4x::capi::icu4x_Date_month_code_mv1(date, &writable);
   1608  MOZ_ASSERT(writable.buf == buf, "unexpected buffer relocation");
   1609 
   1610  auto view = std::string_view{writable.buf, writable.len};
   1611 
   1612  MOZ_ASSERT(view.length() >= 3);
   1613  MOZ_ASSERT(view[0] == 'M');
   1614  MOZ_ASSERT(mozilla::IsAsciiDigit(view[1]));
   1615  MOZ_ASSERT(mozilla::IsAsciiDigit(view[2]));
   1616  MOZ_ASSERT_IF(view.length() > 3, view[3] == 'L');
   1617 
   1618  int32_t ordinal =
   1619      AsciiDigitToNumber(view[1]) * 10 + AsciiDigitToNumber(view[2]);
   1620  bool isLeapMonth = view.length() > 3;
   1621  auto monthCode = MonthCode{ordinal, isLeapMonth};
   1622 
   1623  // The month code must be valid for this calendar.
   1624  MOZ_ASSERT(IsValidMonthCodeForCalendar(calendar, monthCode));
   1625 
   1626  return monthCode;
   1627 }
   1628 
   1629 class MonthCodeString {
   1630  // Zero-terminated month code string.
   1631  char str_[4 + 1];
   1632 
   1633 public:
   1634  explicit MonthCodeString(MonthCodeField field) {
   1635    str_[0] = 'M';
   1636    str_[1] = char('0' + (field.ordinal() / 10));
   1637    str_[2] = char('0' + (field.ordinal() % 10));
   1638    str_[3] = field.isLeapMonth() ? 'L' : '\0';
   1639    str_[4] = '\0';
   1640  }
   1641 
   1642  const char* toCString() const { return str_; }
   1643 };
   1644 
   1645 /**
   1646 * CalendarResolveFields ( calendar, fields, type )
   1647 */
   1648 static bool ISOCalendarResolveMonth(JSContext* cx,
   1649                                    Handle<CalendarFields> fields,
   1650                                    double* result) {
   1651  double month = fields.month();
   1652  MOZ_ASSERT_IF(fields.has(CalendarField::Month),
   1653                IsInteger(month) && month > 0);
   1654 
   1655  // CalendarResolveFields, steps 1.e.
   1656  if (!fields.has(CalendarField::MonthCode)) {
   1657    MOZ_ASSERT(fields.has(CalendarField::Month));
   1658 
   1659    *result = month;
   1660    return true;
   1661  }
   1662 
   1663  auto monthCode = fields.monthCode();
   1664 
   1665  // CalendarResolveFields, steps 1.f-k.
   1666  int32_t ordinal = monthCode.ordinal();
   1667  if (ordinal < 1 || ordinal > 12 || monthCode.isLeapMonth()) {
   1668    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1669                             JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
   1670                             MonthCodeString{monthCode}.toCString());
   1671    return false;
   1672  }
   1673 
   1674  // CalendarResolveFields, steps 1.l-m.
   1675  if (fields.has(CalendarField::Month) && month != ordinal) {
   1676    ToCStringBuf cbuf;
   1677    const char* monthStr = NumberToCString(&cbuf, month);
   1678 
   1679    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1680                             JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE_MONTHCODE,
   1681                             MonthCodeString{monthCode}.toCString(), monthStr);
   1682    return false;
   1683  }
   1684 
   1685  // CalendarResolveFields, steps 1.n.
   1686  *result = ordinal;
   1687  return true;
   1688 }
   1689 
   1690 struct EraYears {
   1691  // Year starting from the calendar epoch.
   1692  mozilla::Maybe<EraYear> fromEpoch;
   1693 
   1694  // Year starting from a specific calendar era.
   1695  mozilla::Maybe<EraYear> fromEra;
   1696 };
   1697 
   1698 static bool CalendarEraYear(JSContext* cx, CalendarId calendarId,
   1699                            EraYear eraYear, EraYear* result) {
   1700  MOZ_ASSERT(CalendarSupportsEra(calendarId));
   1701  MOZ_ASSERT(mozilla::Abs(eraYear.year) <= MaximumCalendarYear(calendarId));
   1702 
   1703  if (eraYear.year > 0 || !CalendarEraHasInverse(calendarId)) {
   1704    *result = eraYear;
   1705    return true;
   1706  }
   1707 
   1708  switch (eraYear.era) {
   1709    case EraCode::Standard: {
   1710      // Map non-positive era years as follows:
   1711      //
   1712      //  0 CE -> 1 BCE
   1713      // -1 CE -> 2 BCE
   1714      // etc.
   1715      *result = {EraCode::Inverse, int32_t(mozilla::Abs(eraYear.year) + 1)};
   1716      return true;
   1717    }
   1718 
   1719    case EraCode::Inverse: {
   1720      // Map non-positive era years as follows:
   1721      //
   1722      //  0 BCE -> 1 CE
   1723      // -1 BCE -> 2 CE
   1724      // etc.
   1725      *result = {EraCode::Standard, int32_t(mozilla::Abs(eraYear.year) + 1)};
   1726      return true;
   1727    }
   1728 
   1729    case EraCode::Meiji:
   1730    case EraCode::Taisho:
   1731    case EraCode::Showa:
   1732    case EraCode::Heisei:
   1733    case EraCode::Reiwa: {
   1734      MOZ_ASSERT(calendarId == CalendarId::Japanese);
   1735 
   1736      auto cal = CreateICU4XCalendar(calendarId);
   1737      return JapaneseEraYearToCommonEraYear(cx, calendarId, cal.get(), eraYear,
   1738                                            result);
   1739    }
   1740  }
   1741  MOZ_CRASH("invalid era id");
   1742 }
   1743 
   1744 /**
   1745 * CalendarResolveFields ( calendar, fields, type )
   1746 * CalendarDateToISO ( calendar, fields, overflow )
   1747 * CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow )
   1748 *
   1749 * Extract `year` and `eraYear` from |fields| and perform some initial
   1750 * validation to ensure the values are valid for the requested calendar.
   1751 */
   1752 static bool CalendarFieldYear(JSContext* cx, CalendarId calendar,
   1753                              Handle<CalendarFields> fields, EraYears* result) {
   1754  MOZ_ASSERT(fields.has(CalendarField::Year) ||
   1755             fields.has(CalendarField::EraYear));
   1756 
   1757  // |eraYear| is to be ignored when not relevant for |calendar| per
   1758  // CalendarResolveFields.
   1759  bool supportsEra =
   1760      fields.has(CalendarField::Era) && CalendarSupportsEra(calendar);
   1761  MOZ_ASSERT_IF(fields.has(CalendarField::Era), CalendarSupportsEra(calendar));
   1762 
   1763  // Case 1: |year| field is present.
   1764  mozilla::Maybe<EraYear> fromEpoch;
   1765  if (fields.has(CalendarField::Year)) {
   1766    double year = fields.year();
   1767    MOZ_ASSERT(IsInteger(year));
   1768 
   1769    int32_t intYear;
   1770    if (!mozilla::NumberEqualsInt32(year, &intYear) ||
   1771        mozilla::Abs(intYear) > MaximumCalendarYear(calendar)) {
   1772      ReportCalendarFieldOverflow(cx, "year", year);
   1773      return false;
   1774    }
   1775 
   1776    fromEpoch = mozilla::Some(CalendarEraYear(calendar, intYear));
   1777  } else {
   1778    MOZ_ASSERT(supportsEra);
   1779  }
   1780 
   1781  // Case 2: |era| and |eraYear| fields are present and relevant for |calendar|.
   1782  mozilla::Maybe<EraYear> fromEra;
   1783  if (supportsEra) {
   1784    MOZ_ASSERT(fields.has(CalendarField::Era));
   1785    MOZ_ASSERT(fields.has(CalendarField::EraYear));
   1786 
   1787    auto era = fields.era();
   1788    MOZ_ASSERT(era);
   1789 
   1790    double eraYear = fields.eraYear();
   1791    MOZ_ASSERT(IsInteger(eraYear));
   1792 
   1793    auto* linearEra = era->ensureLinear(cx);
   1794    if (!linearEra) {
   1795      return false;
   1796    }
   1797 
   1798    // Ensure the requested era is valid for |calendar|.
   1799    auto eraCode = CanonicalizeEraInCalendar(calendar, linearEra);
   1800    if (!eraCode) {
   1801      if (auto code = QuoteString(cx, era)) {
   1802        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1803                                 JSMSG_TEMPORAL_CALENDAR_INVALID_ERA,
   1804                                 code.get());
   1805      }
   1806      return false;
   1807    }
   1808 
   1809    int32_t intEraYear;
   1810    if (!mozilla::NumberEqualsInt32(eraYear, &intEraYear) ||
   1811        mozilla::Abs(intEraYear) > MaximumCalendarYear(calendar)) {
   1812      ReportCalendarFieldOverflow(cx, "eraYear", eraYear);
   1813      return false;
   1814    }
   1815 
   1816    EraYear eraAndYear;
   1817    if (!CalendarEraYear(cx, calendar, {*eraCode, intEraYear}, &eraAndYear)) {
   1818      return false;
   1819    }
   1820    fromEra = mozilla::Some(eraAndYear);
   1821  }
   1822 
   1823  *result = {fromEpoch, fromEra};
   1824  return true;
   1825 }
   1826 
   1827 struct Month {
   1828  // Month code.
   1829  MonthCode code;
   1830 
   1831  // Ordinal month number.
   1832  int32_t ordinal = 0;
   1833 };
   1834 
   1835 /**
   1836 * NonISOCalendarDateToISO ( calendar, fields, overflow )
   1837 * NonISOMonthDayToISOReferenceDate ( calendar, fields, overflow )
   1838 *
   1839 * Extract `month` and `monthCode` from |fields| and perform some initial
   1840 * validation to ensure the values are valid for the requested calendar.
   1841 */
   1842 static bool CalendarFieldMonth(JSContext* cx, CalendarId calendar,
   1843                               Handle<CalendarFields> fields,
   1844                               TemporalOverflow overflow, Month* result) {
   1845  MOZ_ASSERT(fields.has(CalendarField::Month) ||
   1846             fields.has(CalendarField::MonthCode));
   1847 
   1848  // Case 1: |month| field is present.
   1849  int32_t intMonth = 0;
   1850  if (fields.has(CalendarField::Month)) {
   1851    double month = fields.month();
   1852    MOZ_ASSERT(IsInteger(month) && month > 0);
   1853 
   1854    if (!mozilla::NumberEqualsInt32(month, &intMonth)) {
   1855      intMonth = 0;
   1856    }
   1857 
   1858    const int32_t monthsPerYear = CalendarMonthsPerYear(calendar);
   1859    if (intMonth < 1 || intMonth > monthsPerYear) {
   1860      if (overflow == TemporalOverflow::Reject) {
   1861        ReportCalendarFieldOverflow(cx, "month", month);
   1862        return false;
   1863      }
   1864      MOZ_ASSERT(overflow == TemporalOverflow::Constrain);
   1865 
   1866      intMonth = monthsPerYear;
   1867    }
   1868 
   1869    MOZ_ASSERT(intMonth > 0);
   1870  }
   1871 
   1872  // Case 2: |monthCode| field is present.
   1873  MonthCode fromMonthCode;
   1874  if (fields.has(CalendarField::MonthCode)) {
   1875    auto monthCode = fields.monthCode();
   1876    int32_t ordinal = monthCode.ordinal();
   1877    bool isLeapMonth = monthCode.isLeapMonth();
   1878 
   1879    constexpr int32_t minMonth = MonthCode{1}.ordinal();
   1880    constexpr int32_t maxNonLeapMonth = MonthCode::maxNonLeapMonth().ordinal();
   1881    constexpr int32_t maxLeapMonth = MonthCode::maxLeapMonth().ordinal();
   1882 
   1883    // Minimum month number is 1. Maximum month is 12 (or 13 when the calendar
   1884    // uses epagomenal months).
   1885    const int32_t maxMonth = isLeapMonth ? maxLeapMonth : maxNonLeapMonth;
   1886    if (minMonth <= ordinal && ordinal <= maxMonth) {
   1887      fromMonthCode = MonthCode{ordinal, isLeapMonth};
   1888    }
   1889 
   1890    // Ensure the month code is valid for this calendar.
   1891    if (!IsValidMonthCodeForCalendar(calendar, fromMonthCode)) {
   1892      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1893                               JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
   1894                               MonthCodeString{monthCode}.toCString());
   1895      return false;
   1896    }
   1897  }
   1898 
   1899  *result = {fromMonthCode, intMonth};
   1900  return true;
   1901 }
   1902 
   1903 /**
   1904 * CalendarResolveFields ( calendar, fields, type )
   1905 * CalendarDateToISO ( calendar, fields, overflow )
   1906 * CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow )
   1907 *
   1908 * Extract `day` from |fields| and perform some initial validation to ensure the
   1909 * value is valid for the requested calendar.
   1910 */
   1911 static bool CalendarFieldDay(JSContext* cx, CalendarId calendar,
   1912                             Handle<CalendarFields> fields,
   1913                             TemporalOverflow overflow, int32_t* result) {
   1914  MOZ_ASSERT(fields.has(CalendarField::Day));
   1915 
   1916  double day = fields.day();
   1917  MOZ_ASSERT(IsInteger(day) && day > 0);
   1918 
   1919  int32_t intDay;
   1920  if (!mozilla::NumberEqualsInt32(day, &intDay)) {
   1921    intDay = 0;
   1922  }
   1923 
   1924  // Constrain to a valid day value in this calendar.
   1925  int32_t daysPerMonth = CalendarDaysInMonth(calendar).second;
   1926  if (intDay < 1 || intDay > daysPerMonth) {
   1927    if (overflow == TemporalOverflow::Reject) {
   1928      ReportCalendarFieldOverflow(cx, "day", day);
   1929      return false;
   1930    }
   1931    MOZ_ASSERT(overflow == TemporalOverflow::Constrain);
   1932 
   1933    intDay = daysPerMonth;
   1934  }
   1935 
   1936  *result = intDay;
   1937  return true;
   1938 }
   1939 
   1940 /**
   1941 * CalendarResolveFields ( calendar, fields, type )
   1942 *
   1943 * > The operation throws a TypeError exception if the properties of fields are
   1944 * > internally inconsistent within the calendar [...]. For example:
   1945 * >
   1946 * > [...] The values for "era" and "eraYear" do not together identify the same
   1947 * > year as the value for "year".
   1948 */
   1949 static bool CalendarFieldEraYearMatchesYear(JSContext* cx, CalendarId calendar,
   1950                                            Handle<CalendarFields> fields,
   1951                                            const icu4x::capi::Date* date) {
   1952  MOZ_ASSERT(fields.has(CalendarField::EraYear));
   1953  MOZ_ASSERT(fields.has(CalendarField::Year));
   1954 
   1955  double year = fields.year();
   1956  MOZ_ASSERT(IsInteger(year));
   1957 
   1958  int32_t intYear;
   1959  MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt32(year, &intYear));
   1960 
   1961  int32_t yearFromEraYear = CalendarDateYear(calendar, date);
   1962 
   1963  // The user requested year must match the actual (extended/epoch) year.
   1964  if (intYear != yearFromEraYear) {
   1965    ToCStringBuf yearCbuf;
   1966    const char* yearStr = NumberToCString(&yearCbuf, intYear);
   1967 
   1968    ToCStringBuf fromEraCbuf;
   1969    const char* fromEraStr = NumberToCString(&fromEraCbuf, yearFromEraYear);
   1970 
   1971    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1972                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE_YEAR,
   1973                              yearStr, fromEraStr);
   1974    return false;
   1975  }
   1976  return true;
   1977 }
   1978 
   1979 /**
   1980 * CalendarResolveFields ( calendar, fields, type )
   1981 *
   1982 * > The operation throws a TypeError exception if the properties of fields are
   1983 * > internally inconsistent within the calendar [...]. For example:
   1984 * >
   1985 * > If "month" and "monthCode" in the calendar [...] do not identify the same
   1986 * > month.
   1987 */
   1988 static bool CalendarFieldMonthCodeMatchesMonth(JSContext* cx,
   1989                                               Handle<CalendarFields> fields,
   1990                                               const icu4x::capi::Date* date,
   1991                                               int32_t month) {
   1992  int32_t ordinal = OrdinalMonth(date);
   1993 
   1994  // The user requested month must match the actual ordinal month.
   1995  if (month != ordinal) {
   1996    ToCStringBuf cbuf;
   1997    const char* monthStr = NumberToCString(&cbuf, fields.month());
   1998 
   1999    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   2000                             JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE_MONTHCODE,
   2001                             MonthCodeString{fields.monthCode()}.toCString(),
   2002                             monthStr);
   2003    return false;
   2004  }
   2005  return true;
   2006 }
   2007 
   2008 static ISODate ToISODate(const icu4x::capi::Date* date) {
   2009  UniqueICU4XIsoDate isoDate{icu4x::capi::icu4x_Date_to_iso_mv1(date)};
   2010  MOZ_ASSERT(isoDate, "unexpected null-pointer result");
   2011 
   2012  int32_t isoYear = icu4x::capi::icu4x_IsoDate_year_mv1(isoDate.get());
   2013 
   2014  int32_t isoMonth = icu4x::capi::icu4x_IsoDate_month_mv1(isoDate.get());
   2015  MOZ_ASSERT(1 <= isoMonth && isoMonth <= 12);
   2016 
   2017  int32_t isoDay = icu4x::capi::icu4x_IsoDate_day_of_month_mv1(isoDate.get());
   2018  MOZ_ASSERT(1 <= isoDay && isoDay <= ::ISODaysInMonth(isoYear, isoMonth));
   2019 
   2020  return {isoYear, isoMonth, isoDay};
   2021 }
   2022 
   2023 static UniqueICU4XDate CreateDateFrom(JSContext* cx, CalendarId calendar,
   2024                                      const icu4x::capi::Calendar* cal,
   2025                                      const EraYears& eraYears,
   2026                                      const Month& month, int32_t day,
   2027                                      Handle<CalendarFields> fields,
   2028                                      TemporalOverflow overflow) {
   2029  // Use |eraYear| if present, so we can more easily check for consistent
   2030  // |year| and |eraYear| fields.
   2031  auto eraYear = eraYears.fromEra ? *eraYears.fromEra : *eraYears.fromEpoch;
   2032 
   2033  UniqueICU4XDate date;
   2034  if (month.code != MonthCode{}) {
   2035    date = CreateDateFromCodes(cx, calendar, cal, eraYear, month.code, day,
   2036                               overflow);
   2037  } else {
   2038    date = CreateDateFrom(cx, calendar, cal, eraYear, month.ordinal, day,
   2039                          overflow);
   2040  }
   2041  if (!date) {
   2042    return nullptr;
   2043  }
   2044 
   2045  // |year| and |eraYear| must be consistent.
   2046  if (eraYears.fromEpoch && eraYears.fromEra) {
   2047    if (!CalendarFieldEraYearMatchesYear(cx, calendar, fields, date.get())) {
   2048      return nullptr;
   2049    }
   2050  }
   2051 
   2052  // |month| and |monthCode| must be consistent.
   2053  if (month.code != MonthCode{} && month.ordinal > 0) {
   2054    if (!CalendarFieldMonthCodeMatchesMonth(cx, fields, date.get(),
   2055                                            month.ordinal)) {
   2056      return nullptr;
   2057    }
   2058  }
   2059 
   2060  return date;
   2061 }
   2062 
   2063 /**
   2064 * RegulateISODate ( year, month, day, overflow )
   2065 */
   2066 static bool RegulateISODate(JSContext* cx, int32_t year, double month,
   2067                            double day, TemporalOverflow overflow,
   2068                            ISODate* result) {
   2069  MOZ_ASSERT(IsInteger(month));
   2070  MOZ_ASSERT(IsInteger(day));
   2071 
   2072  // Step 1.
   2073  if (overflow == TemporalOverflow::Constrain) {
   2074    // Step 1.a.
   2075    int32_t m = int32_t(std::clamp(month, 1.0, 12.0));
   2076 
   2077    // Step 1.b.
   2078    double daysInMonth = double(::ISODaysInMonth(year, m));
   2079 
   2080    // Step 1.c.
   2081    int32_t d = int32_t(std::clamp(day, 1.0, daysInMonth));
   2082 
   2083    // Step 3. (Inlined call to CreateISODateRecord.)
   2084    *result = {year, m, d};
   2085    return true;
   2086  }
   2087 
   2088  // Step 2.a.
   2089  MOZ_ASSERT(overflow == TemporalOverflow::Reject);
   2090 
   2091  // Step 2.b.
   2092  if (!ThrowIfInvalidISODate(cx, year, month, day)) {
   2093    return false;
   2094  }
   2095 
   2096  // Step 3. (Inlined call to CreateISODateRecord.)
   2097  *result = {year, int32_t(month), int32_t(day)};
   2098  return true;
   2099 }
   2100 
   2101 /**
   2102 * NonISOCalendarDateToISO ( calendar, fields, overflow )
   2103 */
   2104 static bool NonISOCalendarDateToISO(JSContext* cx, CalendarId calendar,
   2105                                    Handle<CalendarFields> fields,
   2106                                    TemporalOverflow overflow,
   2107                                    ISODate* result) {
   2108  EraYears eraYears;
   2109  if (!CalendarFieldYear(cx, calendar, fields, &eraYears)) {
   2110    return false;
   2111  }
   2112 
   2113  Month month;
   2114  if (!CalendarFieldMonth(cx, calendar, fields, overflow, &month)) {
   2115    return false;
   2116  }
   2117 
   2118  int32_t day;
   2119  if (!CalendarFieldDay(cx, calendar, fields, overflow, &day)) {
   2120    return false;
   2121  }
   2122 
   2123  auto cal = CreateICU4XCalendar(calendar);
   2124  auto date = CreateDateFrom(cx, calendar, cal.get(), eraYears, month, day,
   2125                             fields, overflow);
   2126  if (!date) {
   2127    return false;
   2128  }
   2129 
   2130  *result = ToISODate(date.get());
   2131  return true;
   2132 }
   2133 
   2134 /**
   2135 * CalendarDateToISO ( calendar, fields, overflow )
   2136 */
   2137 static bool CalendarDateToISO(JSContext* cx, CalendarId calendar,
   2138                              Handle<CalendarFields> fields,
   2139                              TemporalOverflow overflow, ISODate* result) {
   2140  // Step 1.
   2141  if (calendar == CalendarId::ISO8601) {
   2142    // Step 1.a.
   2143    MOZ_ASSERT(fields.has(CalendarField::Year));
   2144    MOZ_ASSERT(fields.has(CalendarField::Month) ||
   2145               fields.has(CalendarField::MonthCode));
   2146    MOZ_ASSERT(fields.has(CalendarField::Day));
   2147 
   2148    // Remaining steps from CalendarResolveFields to resolve the month.
   2149    double month;
   2150    if (!ISOCalendarResolveMonth(cx, fields, &month)) {
   2151      return false;
   2152    }
   2153 
   2154    int32_t intYear;
   2155    if (!mozilla::NumberEqualsInt32(fields.year(), &intYear)) {
   2156      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2157                                JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   2158      return false;
   2159    }
   2160 
   2161    // Step 1.b.
   2162    return RegulateISODate(cx, intYear, month, fields.day(), overflow, result);
   2163  }
   2164 
   2165  // Step 2.
   2166  return NonISOCalendarDateToISO(cx, calendar, fields, overflow, result);
   2167 }
   2168 
   2169 /**
   2170 * CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow )
   2171 */
   2172 static bool NonISOMonthDayToISOReferenceDate(JSContext* cx, CalendarId calendar,
   2173                                             icu4x::capi::Calendar* cal,
   2174                                             ISODate startISODate,
   2175                                             ISODate endISODate,
   2176                                             MonthCode monthCode, int32_t day,
   2177                                             UniqueICU4XDate& resultDate) {
   2178  MOZ_ASSERT(startISODate != endISODate);
   2179 
   2180  int32_t direction = startISODate > endISODate ? -1 : 1;
   2181 
   2182  auto fromIsoDate = CreateICU4XDate(cx, startISODate, calendar, cal);
   2183  if (!fromIsoDate) {
   2184    return false;
   2185  }
   2186 
   2187  auto toIsoDate = CreateICU4XDate(cx, endISODate, calendar, cal);
   2188  if (!toIsoDate) {
   2189    return false;
   2190  }
   2191 
   2192  // Find the calendar year for the ISO start date.
   2193  int32_t calendarYear = CalendarDateYear(calendar, fromIsoDate.get());
   2194 
   2195  // Find the calendar year for the ISO end date.
   2196  int32_t toCalendarYear = CalendarDateYear(calendar, toIsoDate.get());
   2197 
   2198  while (direction < 0 ? calendarYear >= toCalendarYear
   2199                       : calendarYear <= toCalendarYear) {
   2200    // This loop can run for a long time.
   2201    if (!CheckForInterrupt(cx)) {
   2202      return false;
   2203    }
   2204 
   2205    auto candidateYear = CalendarEraYear(calendar, calendarYear);
   2206 
   2207    auto result =
   2208        CreateDateFromCodes(calendar, cal, candidateYear, monthCode, day);
   2209    if (result.isOk()) {
   2210      auto isoDate = ToISODate(result.inspect().get());
   2211 
   2212      // Make sure the resolved date is before |startISODate|.
   2213      if (direction < 0 ? isoDate > startISODate : isoDate < startISODate) {
   2214        calendarYear += direction;
   2215        continue;
   2216      }
   2217 
   2218      // Stop searching if |endISODate| was reached.
   2219      if (direction < 0 ? isoDate < endISODate : isoDate > endISODate) {
   2220        resultDate = nullptr;
   2221        return true;
   2222      }
   2223 
   2224      resultDate = result.unwrap();
   2225      return true;
   2226    }
   2227 
   2228    switch (result.inspectErr()) {
   2229      case CalendarError::UnknownMonthCode: {
   2230        MOZ_ASSERT(CalendarHasLeapMonths(calendar));
   2231        MOZ_ASSERT(monthCode.isLeapMonth());
   2232 
   2233        // Try the next candidate year if the requested leap month doesn't
   2234        // occur in the current year.
   2235        calendarYear += direction;
   2236        continue;
   2237      }
   2238 
   2239      case CalendarError::OutOfRange: {
   2240        // ICU4X throws an out-of-range error when:
   2241        // 1. month > monthsInYear(year), or
   2242        // 2. days > daysInMonthOf(year, month).
   2243        //
   2244        // Case 1 can't happen for month-codes, so it doesn't apply here.
   2245        // Case 2 can only happen when |day| is larger than the minimum number
   2246        // of days in the month.
   2247        MOZ_ASSERT(day > CalendarDaysInMonth(calendar, monthCode).first);
   2248 
   2249        // Try next candidate year to find an earlier year which can fulfill
   2250        // the input request.
   2251        calendarYear += direction;
   2252        continue;
   2253      }
   2254 
   2255      case CalendarError::UnknownEra:
   2256        MOZ_ASSERT(false, "unexpected calendar error");
   2257        break;
   2258 
   2259      case CalendarError::Generic:
   2260        break;
   2261    }
   2262 
   2263    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2264                              JSMSG_TEMPORAL_CALENDAR_INTERNAL_ERROR);
   2265    return false;
   2266  }
   2267 
   2268  resultDate = nullptr;
   2269  return true;
   2270 }
   2271 
   2272 /**
   2273 * NonISOMonthDayToISOReferenceDate ( calendar, fields, overflow )
   2274 */
   2275 static bool NonISOMonthDayToISOReferenceDate(JSContext* cx, CalendarId calendar,
   2276                                             Handle<CalendarFields> fields,
   2277                                             TemporalOverflow overflow,
   2278                                             ISODate* result) {
   2279  EraYears eraYears;
   2280  if (fields.has(CalendarField::Year) || fields.has(CalendarField::EraYear)) {
   2281    if (!CalendarFieldYear(cx, calendar, fields, &eraYears)) {
   2282      return false;
   2283    }
   2284  } else {
   2285    MOZ_ASSERT(fields.has(CalendarField::MonthCode));
   2286  }
   2287 
   2288  Month month;
   2289  if (!CalendarFieldMonth(cx, calendar, fields, overflow, &month)) {
   2290    return false;
   2291  }
   2292 
   2293  int32_t day;
   2294  if (!CalendarFieldDay(cx, calendar, fields, overflow, &day)) {
   2295    return false;
   2296  }
   2297 
   2298  auto cal = CreateICU4XCalendar(calendar);
   2299 
   2300  // We first have to compute the month-code if it wasn't provided to us.
   2301  auto monthCode = month.code;
   2302  if (fields.has(CalendarField::Year) || fields.has(CalendarField::EraYear)) {
   2303    auto date = CreateDateFrom(cx, calendar, cal.get(), eraYears, month, day,
   2304                               fields, overflow);
   2305    if (!date) {
   2306      return false;
   2307    }
   2308 
   2309    // This operation throws a RangeError if the ISO 8601 year corresponding to
   2310    // `fields.[[Year]]` is outside the valid limits.
   2311    auto isoDate = ToISODate(date.get());
   2312    if (!ISODateWithinLimits(isoDate)) {
   2313      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2314                                JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   2315      return false;
   2316    }
   2317 
   2318    if (!fields.has(CalendarField::MonthCode)) {
   2319      monthCode = CalendarDateMonthCode(calendar, date.get());
   2320    }
   2321    MOZ_ASSERT(monthCode != MonthCode{});
   2322 
   2323    if (overflow == TemporalOverflow::Constrain) {
   2324      // Call into ICU4X if `day` exceeds the minimum number of days.
   2325      int32_t minDaysInMonth = CalendarDaysInMonth(calendar, monthCode).first;
   2326      if (day > minDaysInMonth) {
   2327        day = DayOfMonth(date.get());
   2328      }
   2329    } else {
   2330      MOZ_ASSERT(overflow == TemporalOverflow::Reject);
   2331      MOZ_ASSERT(day == DayOfMonth(date.get()));
   2332    }
   2333  } else {
   2334    MOZ_ASSERT(monthCode != MonthCode{});
   2335 
   2336    // Constrain `day` to maximum possible day of the input month.
   2337    int32_t maxDaysInMonth = CalendarDaysInMonth(calendar, monthCode).second;
   2338    if (overflow == TemporalOverflow::Constrain) {
   2339      day = std::min(day, maxDaysInMonth);
   2340    } else {
   2341      MOZ_ASSERT(overflow == TemporalOverflow::Reject);
   2342 
   2343      if (day > maxDaysInMonth) {
   2344        ReportCalendarFieldOverflow(cx, "day", day);
   2345        return false;
   2346      }
   2347    }
   2348  }
   2349 
   2350  constexpr ISODate candidates[][2] = {
   2351      // The reference date is the latest ISO 8601 date corresponding to the
   2352      // calendar date that is between January 1, 1900 and December 31, 1972
   2353      // inclusive.
   2354      {ISODate{1972, 12, 31}, ISODate{1900, 1, 1}},
   2355 
   2356      // If there is no such date, it is the earliest ISO 8601 date
   2357      // corresponding to the calendar date between January 1, 1973 and
   2358      // December 31, 2035.
   2359      {ISODate{1973, 1, 1}, ISODate{2035, 12, 31}},
   2360 
   2361      // If there is still no such date, it is the latest ISO 8601 date
   2362      // corresponding to the calendar date on or before December 31, 1899.
   2363      //
   2364      // Year -8000 is sufficient to find all possible month-days, even for
   2365      // rare cases like `{calendar: "chinese", monthCode: "M09L", day: 30}`.
   2366      {ISODate{1899, 12, 31}, ISODate{-8000, 1, 1}},
   2367  };
   2368 
   2369  UniqueICU4XDate date;
   2370  for (auto& [start, end] : candidates) {
   2371    if (!NonISOMonthDayToISOReferenceDate(cx, calendar, cal.get(), start, end,
   2372                                          monthCode, day, date)) {
   2373      return false;
   2374    }
   2375    if (date) {
   2376      break;
   2377    }
   2378  }
   2379 
   2380  // We shouldn't end up here with |maxIterations == 10'000|, but just in case
   2381  // still handle this case and report an error.
   2382  if (!date) {
   2383    ReportCalendarFieldOverflow(cx, "day", day);
   2384    return false;
   2385  }
   2386 
   2387  *result = ToISODate(date.get());
   2388  return true;
   2389 }
   2390 
   2391 /**
   2392 * CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow )
   2393 */
   2394 static bool CalendarMonthDayToISOReferenceDate(JSContext* cx,
   2395                                               CalendarId calendar,
   2396                                               Handle<CalendarFields> fields,
   2397                                               TemporalOverflow overflow,
   2398                                               ISODate* result) {
   2399  // Step 1.
   2400  if (calendar == CalendarId::ISO8601) {
   2401    // Step 1.a.
   2402    MOZ_ASSERT(fields.has(CalendarField::Month) ||
   2403               fields.has(CalendarField::MonthCode));
   2404    MOZ_ASSERT(fields.has(CalendarField::Day));
   2405 
   2406    // Remaining steps from CalendarResolveFields to resolve the month.
   2407    double month;
   2408    if (!ISOCalendarResolveMonth(cx, fields, &month)) {
   2409      return false;
   2410    }
   2411 
   2412    // Step 1.b.
   2413    int32_t referenceISOYear = 1972;
   2414 
   2415    // Step 1.c.
   2416    double year =
   2417        !fields.has(CalendarField::Year) ? referenceISOYear : fields.year();
   2418 
   2419    int32_t intYear;
   2420    if (!mozilla::NumberEqualsInt32(year, &intYear)) {
   2421      // Calendar cycles repeat every 400 years in the Gregorian calendar.
   2422      intYear = int32_t(std::fmod(year, 400));
   2423    }
   2424 
   2425    // Step 1.d.
   2426    ISODate regulated;
   2427    if (!RegulateISODate(cx, intYear, month, fields.day(), overflow,
   2428                         &regulated)) {
   2429      return false;
   2430    }
   2431 
   2432    // Step 1.e.
   2433    *result = {referenceISOYear, regulated.month, regulated.day};
   2434    return true;
   2435  }
   2436 
   2437  // Step 2.
   2438  return NonISOMonthDayToISOReferenceDate(cx, calendar, fields, overflow,
   2439                                          result);
   2440 }
   2441 
   2442 enum class FieldType { Date, YearMonth, MonthDay };
   2443 
   2444 /**
   2445 * NonISOResolveFields ( calendar, fields, type )
   2446 */
   2447 static bool NonISOResolveFields(JSContext* cx, CalendarId calendar,
   2448                                Handle<CalendarFields> fields, FieldType type) {
   2449  // Date and Month-Day require |day| to be present.
   2450  bool requireDay = type == FieldType::Date || type == FieldType::MonthDay;
   2451 
   2452  // Date and Year-Month require |year| (or |eraYear|) to be present.
   2453  // Month-Day requires |year| (or |eraYear|) if |monthCode| is absent.
   2454  // Month-Day requires |year| (or |eraYear|) if |month| is present, even if
   2455  // |monthCode| is also present.
   2456  bool requireYear = type == FieldType::Date || type == FieldType::YearMonth ||
   2457                     !fields.has(CalendarField::MonthCode) ||
   2458                     fields.has(CalendarField::Month);
   2459 
   2460  // Determine if any calendar fields are missing.
   2461  const char* missingField = nullptr;
   2462  if (!fields.has(CalendarField::MonthCode) &&
   2463      !fields.has(CalendarField::Month)) {
   2464    // |monthCode| or |month| must be present.
   2465    missingField = "monthCode";
   2466  } else if (requireDay && !fields.has(CalendarField::Day)) {
   2467    missingField = "day";
   2468  } else if (!CalendarSupportsEra(calendar)) {
   2469    if (requireYear && !fields.has(CalendarField::Year)) {
   2470      missingField = "year";
   2471    }
   2472  } else {
   2473    if (fields.has(CalendarField::Era) != fields.has(CalendarField::EraYear)) {
   2474      // |era| and |eraYear| must either both be present or both absent.
   2475      missingField = fields.has(CalendarField::Era) ? "eraYear" : "era";
   2476    } else if (requireYear && !fields.has(CalendarField::EraYear) &&
   2477               !fields.has(CalendarField::Year)) {
   2478      missingField = "eraYear";
   2479    }
   2480  }
   2481 
   2482  if (missingField) {
   2483    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2484                              JSMSG_TEMPORAL_CALENDAR_MISSING_FIELD,
   2485                              missingField);
   2486    return false;
   2487  }
   2488 
   2489  return true;
   2490 }
   2491 
   2492 /**
   2493 * CalendarResolveFields ( calendar, fields, type )
   2494 */
   2495 static bool CalendarResolveFields(JSContext* cx, CalendarId calendar,
   2496                                  Handle<CalendarFields> fields,
   2497                                  FieldType type) {
   2498  // Step 1.
   2499  if (calendar == CalendarId::ISO8601) {
   2500    // Steps 1.a-e.
   2501    const char* missingField = nullptr;
   2502    if ((type == FieldType::Date || type == FieldType::YearMonth) &&
   2503        !fields.has(CalendarField::Year)) {
   2504      missingField = "year";
   2505    } else if ((type == FieldType::Date || type == FieldType::MonthDay) &&
   2506               !fields.has(CalendarField::Day)) {
   2507      missingField = "day";
   2508    } else if (!fields.has(CalendarField::MonthCode) &&
   2509               !fields.has(CalendarField::Month)) {
   2510      missingField = "month";
   2511    }
   2512 
   2513    if (missingField) {
   2514      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2515                                JSMSG_TEMPORAL_CALENDAR_MISSING_FIELD,
   2516                                missingField);
   2517      return false;
   2518    }
   2519 
   2520    // Steps 1.f-n. (Handled in ISOCalendarResolveMonth.)
   2521 
   2522    return true;
   2523  }
   2524 
   2525  // Step 2.
   2526  return NonISOResolveFields(cx, calendar, fields, type);
   2527 }
   2528 
   2529 /**
   2530 * CalendarISOToDate ( calendar, isoDate )
   2531 * NonISOCalendarISOToDate ( calendar, isoDate )
   2532 * CalendarDateEra ( calendar, date )
   2533 *
   2534 * Return the Calendar Date Record's [[Era]] field.
   2535 */
   2536 bool js::temporal::CalendarEra(JSContext* cx, Handle<CalendarValue> calendar,
   2537                               const ISODate& date,
   2538                               MutableHandle<Value> result) {
   2539  auto calendarId = calendar.identifier();
   2540 
   2541  // Step 1.
   2542  if (calendarId == CalendarId::ISO8601) {
   2543    result.setUndefined();
   2544    return true;
   2545  }
   2546 
   2547  // Step 2.
   2548  if (!CalendarSupportsEra(calendarId)) {
   2549    result.setUndefined();
   2550    return true;
   2551  }
   2552 
   2553  auto era = EraCode::Standard;
   2554 
   2555  // Call into ICU4X if the calendar has more than one era.
   2556  auto eras = CalendarEras(calendarId);
   2557  if (eras.size() > 1) {
   2558    auto cal = CreateICU4XCalendar(calendarId);
   2559    auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2560    if (!dt) {
   2561      return false;
   2562    }
   2563 
   2564    if (!CalendarDateEra(cx, calendarId, dt.get(), &era)) {
   2565      return false;
   2566    }
   2567  } else {
   2568    MOZ_ASSERT(*eras.begin() == EraCode::Standard,
   2569               "single era calendars use only the standard era");
   2570  }
   2571 
   2572  auto* str = NewStringCopy<CanGC>(cx, CalendarEraName(calendarId, era));
   2573  if (!str) {
   2574    return false;
   2575  }
   2576 
   2577  result.setString(str);
   2578  return true;
   2579 }
   2580 
   2581 /**
   2582 * CalendarISOToDate ( calendar, isoDate )
   2583 * NonISOCalendarISOToDate ( calendar, isoDate )
   2584 * CalendarDateEraYear ( calendar, date )
   2585 *
   2586 * Return the Calendar Date Record's [[EraYear]] field.
   2587 */
   2588 bool js::temporal::CalendarEraYear(JSContext* cx,
   2589                                   Handle<CalendarValue> calendar,
   2590                                   const ISODate& date,
   2591                                   MutableHandle<Value> result) {
   2592  auto calendarId = calendar.identifier();
   2593 
   2594  // Step 1.
   2595  if (calendarId == CalendarId::ISO8601) {
   2596    result.setUndefined();
   2597    return true;
   2598  }
   2599 
   2600  // Step 2.
   2601  if (!CalendarSupportsEra(calendarId)) {
   2602    result.setUndefined();
   2603    return true;
   2604  }
   2605 
   2606  auto eras = CalendarEras(calendarId);
   2607  if (eras.size() == 1) {
   2608    // Return the calendar year for calendars with a single era.
   2609    return CalendarYear(cx, calendar, date, result);
   2610  }
   2611  MOZ_ASSERT(eras.size() > 1);
   2612 
   2613  auto cal = CreateICU4XCalendar(calendarId);
   2614  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2615  if (!dt) {
   2616    return false;
   2617  }
   2618 
   2619  int32_t year = icu4x::capi::icu4x_Date_era_year_or_related_iso_mv1(dt.get());
   2620  result.setInt32(year);
   2621  return true;
   2622 }
   2623 
   2624 /**
   2625 * CalendarISOToDate ( calendar, isoDate )
   2626 * NonISOCalendarISOToDate ( calendar, isoDate )
   2627 * CalendarDateArithmeticYear ( calendar, date )
   2628 *
   2629 * Return the Calendar Date Record's [[Year]] field.
   2630 */
   2631 bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
   2632                                const ISODate& date,
   2633                                MutableHandle<Value> result) {
   2634  auto calendarId = calendar.identifier();
   2635 
   2636  // Step 1.
   2637  if (calendarId == CalendarId::ISO8601) {
   2638    result.setInt32(date.year);
   2639    return true;
   2640  }
   2641 
   2642  // Step 2.
   2643  auto cal = CreateICU4XCalendar(calendarId);
   2644  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2645  if (!dt) {
   2646    return false;
   2647  }
   2648 
   2649  int32_t year = CalendarDateYear(calendarId, dt.get());
   2650  result.setInt32(year);
   2651  return true;
   2652 }
   2653 
   2654 /**
   2655 * CalendarISOToDate ( calendar, isoDate )
   2656 * NonISOCalendarISOToDate ( calendar, isoDate )
   2657 *
   2658 * Return the Calendar Date Record's [[Month]] field.
   2659 */
   2660 bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
   2661                                 const ISODate& date,
   2662                                 MutableHandle<Value> result) {
   2663  auto calendarId = calendar.identifier();
   2664 
   2665  // Step 1.
   2666  if (calendarId == CalendarId::ISO8601) {
   2667    result.setInt32(date.month);
   2668    return true;
   2669  }
   2670 
   2671  // Step 2.
   2672  auto cal = CreateICU4XCalendar(calendarId);
   2673  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2674  if (!dt) {
   2675    return false;
   2676  }
   2677 
   2678  int32_t month = OrdinalMonth(dt.get());
   2679  result.setInt32(month);
   2680  return true;
   2681 }
   2682 
   2683 /**
   2684 * CalendarISOToDate ( calendar, isoDate )
   2685 * NonISOCalendarISOToDate ( calendar, isoDate )
   2686 *
   2687 * Return the Calendar Date Record's [[MonthCode]] field.
   2688 */
   2689 bool js::temporal::CalendarMonthCode(JSContext* cx,
   2690                                     Handle<CalendarValue> calendar,
   2691                                     const ISODate& date,
   2692                                     MutableHandle<Value> result) {
   2693  auto calendarId = calendar.identifier();
   2694 
   2695  // Step 1.
   2696  if (calendarId == CalendarId::ISO8601) {
   2697    // Steps 1.a-b.
   2698    auto monthCode = MonthCode{date.month};
   2699    JSString* str = NewStringCopy<CanGC>(cx, std::string_view{monthCode});
   2700    if (!str) {
   2701      return false;
   2702    }
   2703 
   2704    result.setString(str);
   2705    return true;
   2706  }
   2707 
   2708  // Step 2.
   2709  auto cal = CreateICU4XCalendar(calendarId);
   2710  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2711  if (!dt) {
   2712    return false;
   2713  }
   2714 
   2715  auto monthCode = CalendarDateMonthCode(calendarId, dt.get());
   2716  auto* str = NewStringCopy<CanGC>(cx, std::string_view{monthCode});
   2717  if (!str) {
   2718    return false;
   2719  }
   2720 
   2721  result.setString(str);
   2722  return true;
   2723 }
   2724 
   2725 /**
   2726 * CalendarISOToDate ( calendar, isoDate )
   2727 * NonISOCalendarISOToDate ( calendar, isoDate )
   2728 *
   2729 * Return the Calendar Date Record's [[Day]] field.
   2730 */
   2731 bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
   2732                               const ISODate& date,
   2733                               MutableHandle<Value> result) {
   2734  auto calendarId = calendar.identifier();
   2735 
   2736  // Step 1.
   2737  if (calendarId == CalendarId::ISO8601) {
   2738    result.setInt32(date.day);
   2739    return true;
   2740  }
   2741 
   2742  // Step 2.
   2743  auto cal = CreateICU4XCalendar(calendarId);
   2744  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2745  if (!dt) {
   2746    return false;
   2747  }
   2748 
   2749  int32_t day = DayOfMonth(dt.get());
   2750  result.setInt32(day);
   2751  return true;
   2752 }
   2753 
   2754 /**
   2755 * CalendarISOToDate ( calendar, isoDate )
   2756 * NonISOCalendarISOToDate ( calendar, isoDate )
   2757 *
   2758 * Return the Calendar Date Record's [[DayOfWeek]] field.
   2759 */
   2760 bool js::temporal::CalendarDayOfWeek(JSContext* cx,
   2761                                     Handle<CalendarValue> calendar,
   2762                                     const ISODate& date,
   2763                                     MutableHandle<Value> result) {
   2764  auto calendarId = calendar.identifier();
   2765 
   2766  // Step 1.
   2767  if (calendarId == CalendarId::ISO8601) {
   2768    result.setInt32(ISODayOfWeek(date));
   2769    return true;
   2770  }
   2771 
   2772  // Step 2.
   2773  auto cal = CreateICU4XCalendar(calendarId);
   2774  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2775  if (!dt) {
   2776    return false;
   2777  }
   2778 
   2779  // Week day codes are correctly ordered.
   2780  static_assert(icu4x::capi::Weekday_Monday == 1);
   2781  static_assert(icu4x::capi::Weekday_Tuesday == 2);
   2782  static_assert(icu4x::capi::Weekday_Wednesday == 3);
   2783  static_assert(icu4x::capi::Weekday_Thursday == 4);
   2784  static_assert(icu4x::capi::Weekday_Friday == 5);
   2785  static_assert(icu4x::capi::Weekday_Saturday == 6);
   2786  static_assert(icu4x::capi::Weekday_Sunday == 7);
   2787 
   2788  icu4x::capi::Weekday day = icu4x::capi::icu4x_Date_day_of_week_mv1(dt.get());
   2789  result.setInt32(static_cast<int32_t>(day));
   2790  return true;
   2791 }
   2792 
   2793 /**
   2794 * CalendarISOToDate ( calendar, isoDate )
   2795 * NonISOCalendarISOToDate ( calendar, isoDate )
   2796 *
   2797 * Return the Calendar Date Record's [[DayOfYear]] field.
   2798 */
   2799 bool js::temporal::CalendarDayOfYear(JSContext* cx,
   2800                                     Handle<CalendarValue> calendar,
   2801                                     const ISODate& date,
   2802                                     MutableHandle<Value> result) {
   2803  auto calendarId = calendar.identifier();
   2804 
   2805  // Step 1.
   2806  if (calendarId == CalendarId::ISO8601) {
   2807    result.setInt32(ISODayOfYear(date));
   2808    return true;
   2809  }
   2810 
   2811  // Step 2.
   2812  auto cal = CreateICU4XCalendar(calendarId);
   2813  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2814  if (!dt) {
   2815    return false;
   2816  }
   2817 
   2818  int32_t day = DayOfYear(dt.get());
   2819  result.setInt32(day);
   2820  return true;
   2821 }
   2822 
   2823 /**
   2824 * CalendarISOToDate ( calendar, isoDate )
   2825 * NonISOCalendarISOToDate ( calendar, isoDate )
   2826 *
   2827 * Return the Calendar Date Record's [[WeekOfYear]].[[Week]] field.
   2828 */
   2829 bool js::temporal::CalendarWeekOfYear(JSContext* cx,
   2830                                      Handle<CalendarValue> calendar,
   2831                                      const ISODate& date,
   2832                                      MutableHandle<Value> result) {
   2833  auto calendarId = calendar.identifier();
   2834 
   2835  // Step 1.
   2836  if (calendarId == CalendarId::ISO8601) {
   2837    result.setInt32(ISOWeekOfYear(date).week);
   2838    return true;
   2839  }
   2840 
   2841  // Step 2.
   2842  //
   2843  // Non-Gregorian calendars don't get week-of-year support for now.
   2844  //
   2845  // https://github.com/tc39/proposal-temporal/issues/3096
   2846  // https://github.com/tc39/proposal-intl-era-monthcode/issues/15
   2847  result.setUndefined();
   2848  return true;
   2849 }
   2850 
   2851 /**
   2852 * CalendarISOToDate ( calendar, isoDate )
   2853 * NonISOCalendarISOToDate ( calendar, isoDate )
   2854 *
   2855 * Return the Calendar Date Record's [[WeekOfYear]].[[Year]] field.
   2856 */
   2857 bool js::temporal::CalendarYearOfWeek(JSContext* cx,
   2858                                      Handle<CalendarValue> calendar,
   2859                                      const ISODate& date,
   2860                                      MutableHandle<Value> result) {
   2861  auto calendarId = calendar.identifier();
   2862 
   2863  // Step 1.
   2864  if (calendarId == CalendarId::ISO8601) {
   2865    result.setInt32(ISOWeekOfYear(date).year);
   2866    return true;
   2867  }
   2868 
   2869  // Step 2.
   2870  //
   2871  // Non-ISO8601 calendars don't get year-of-week support for now.
   2872  //
   2873  // https://github.com/tc39/proposal-temporal/issues/3096
   2874  // https://github.com/tc39/proposal-intl-era-monthcode/issues/15
   2875  result.setUndefined();
   2876  return true;
   2877 }
   2878 
   2879 /**
   2880 * CalendarISOToDate ( calendar, isoDate )
   2881 * NonISOCalendarISOToDate ( calendar, isoDate )
   2882 *
   2883 * Return the Calendar Date Record's [[DaysInWeek]] field.
   2884 */
   2885 bool js::temporal::CalendarDaysInWeek(JSContext* cx,
   2886                                      Handle<CalendarValue> calendar,
   2887                                      const ISODate& date,
   2888                                      MutableHandle<Value> result) {
   2889  // All supported ICU4X calendars use a 7-day week and so does the ISO 8601
   2890  // calendar.
   2891  //
   2892  // This function isn't supported through the ICU4X FFI, so we have to
   2893  // hardcode the result.
   2894 
   2895  // Step 1-2.
   2896  result.setInt32(7);
   2897  return true;
   2898 }
   2899 
   2900 /**
   2901 * CalendarISOToDate ( calendar, isoDate )
   2902 * NonISOCalendarISOToDate ( calendar, isoDate )
   2903 *
   2904 * Return the Calendar Date Record's [[DaysInMonth]] field.
   2905 */
   2906 bool js::temporal::CalendarDaysInMonth(JSContext* cx,
   2907                                       Handle<CalendarValue> calendar,
   2908                                       const ISODate& date,
   2909                                       MutableHandle<Value> result) {
   2910  auto calendarId = calendar.identifier();
   2911 
   2912  // Step 1.
   2913  if (calendarId == CalendarId::ISO8601) {
   2914    result.setInt32(::ISODaysInMonth(date.year, date.month));
   2915    return true;
   2916  }
   2917 
   2918  // Step 2.
   2919  auto cal = CreateICU4XCalendar(calendarId);
   2920  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2921  if (!dt) {
   2922    return false;
   2923  }
   2924 
   2925  int32_t days = DaysInMonth(dt.get());
   2926  result.setInt32(days);
   2927  return true;
   2928 }
   2929 
   2930 /**
   2931 * CalendarISOToDate ( calendar, isoDate )
   2932 * NonISOCalendarISOToDate ( calendar, isoDate )
   2933 *
   2934 * Return the Calendar Date Record's [[DaysInYear]] field.
   2935 */
   2936 bool js::temporal::CalendarDaysInYear(JSContext* cx,
   2937                                      Handle<CalendarValue> calendar,
   2938                                      const ISODate& date,
   2939                                      MutableHandle<Value> result) {
   2940  auto calendarId = calendar.identifier();
   2941 
   2942  // Step 1.
   2943  if (calendarId == CalendarId::ISO8601) {
   2944    result.setInt32(ISODaysInYear(date.year));
   2945    return true;
   2946  }
   2947 
   2948  // Step 2.
   2949  auto cal = CreateICU4XCalendar(calendarId);
   2950  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2951  if (!dt) {
   2952    return false;
   2953  }
   2954 
   2955  int32_t days = DaysInYear(dt.get());
   2956  result.setInt32(days);
   2957  return true;
   2958 }
   2959 
   2960 /**
   2961 * CalendarISOToDate ( calendar, isoDate )
   2962 * NonISOCalendarISOToDate ( calendar, isoDate )
   2963 *
   2964 * Return the Calendar Date Record's [[MonthsInYear]] field.
   2965 */
   2966 bool js::temporal::CalendarMonthsInYear(JSContext* cx,
   2967                                        Handle<CalendarValue> calendar,
   2968                                        const ISODate& date,
   2969                                        MutableHandle<Value> result) {
   2970  auto calendarId = calendar.identifier();
   2971 
   2972  // Step 1.
   2973  if (calendarId == CalendarId::ISO8601) {
   2974    result.setInt32(12);
   2975    return true;
   2976  }
   2977 
   2978  // Step 2
   2979  auto cal = CreateICU4XCalendar(calendarId);
   2980  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   2981  if (!dt) {
   2982    return false;
   2983  }
   2984 
   2985  int32_t months = MonthsInYear(dt.get());
   2986  result.setInt32(months);
   2987  return true;
   2988 }
   2989 
   2990 /**
   2991 * CalendarISOToDate ( calendar, isoDate )
   2992 * NonISOCalendarISOToDate ( calendar, isoDate )
   2993 *
   2994 * Return the Calendar Date Record's [[InLeapYear]] field.
   2995 */
   2996 bool js::temporal::CalendarInLeapYear(JSContext* cx,
   2997                                      Handle<CalendarValue> calendar,
   2998                                      const ISODate& date,
   2999                                      MutableHandle<Value> result) {
   3000  auto calendarId = calendar.identifier();
   3001 
   3002  // Step 1.
   3003  if (calendarId == CalendarId::ISO8601) {
   3004    result.setBoolean(IsISOLeapYear(date.year));
   3005    return true;
   3006  }
   3007 
   3008  // Step 2.
   3009 
   3010  // FIXME: Not supported in ICU4X.
   3011  //
   3012  // https://github.com/unicode-org/icu4x/issues/5654
   3013 
   3014  auto cal = CreateICU4XCalendar(calendarId);
   3015  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   3016  if (!dt) {
   3017    return false;
   3018  }
   3019 
   3020  bool inLeapYear = false;
   3021  switch (calendarId) {
   3022    case CalendarId::ISO8601:
   3023    case CalendarId::Buddhist:
   3024    case CalendarId::Gregorian:
   3025    case CalendarId::Japanese:
   3026    case CalendarId::Coptic:
   3027    case CalendarId::Ethiopian:
   3028    case CalendarId::EthiopianAmeteAlem:
   3029    case CalendarId::Indian:
   3030    case CalendarId::Persian:
   3031    case CalendarId::ROC: {
   3032      MOZ_ASSERT(!CalendarHasLeapMonths(calendarId));
   3033 
   3034      // Solar calendars have either 365 or 366 days per year.
   3035      int32_t days = DaysInYear(dt.get());
   3036      MOZ_ASSERT(days == 365 || days == 366);
   3037 
   3038      // Leap years have 366 days.
   3039      inLeapYear = days == 366;
   3040      break;
   3041    }
   3042 
   3043    case CalendarId::IslamicCivil:
   3044    case CalendarId::IslamicTabular:
   3045    case CalendarId::IslamicUmmAlQura: {
   3046      MOZ_ASSERT(!CalendarHasLeapMonths(calendarId));
   3047 
   3048      // Lunar Islamic calendars have either 354 or 355 days per year.
   3049      //
   3050      // Allow 353 days to workaround
   3051      // <https://github.com/unicode-org/icu4x/issues/4930>.
   3052      int32_t days = DaysInYear(dt.get());
   3053      MOZ_ASSERT(days == 353 || days == 354 || days == 355);
   3054 
   3055      // Leap years have 355 days.
   3056      inLeapYear = days == 355;
   3057      break;
   3058    }
   3059 
   3060    case CalendarId::Chinese:
   3061    case CalendarId::Dangi:
   3062    case CalendarId::Hebrew: {
   3063      MOZ_ASSERT(CalendarHasLeapMonths(calendarId));
   3064 
   3065      // Calendars with separate leap months have either 12 or 13 months per
   3066      // year.
   3067      int32_t months = MonthsInYear(dt.get());
   3068      MOZ_ASSERT(months == 12 || months == 13);
   3069 
   3070      // Leap years have 13 months.
   3071      inLeapYear = months == 13;
   3072      break;
   3073    }
   3074  }
   3075 
   3076  result.setBoolean(inLeapYear);
   3077  return true;
   3078 }
   3079 
   3080 enum class DateFieldType { Date, YearMonth, MonthDay };
   3081 
   3082 /**
   3083 * ISODateToFields ( calendar, isoDate, type )
   3084 */
   3085 static bool ISODateToFields(JSContext* cx, Handle<CalendarValue> calendar,
   3086                            const ISODate& date, DateFieldType type,
   3087                            MutableHandle<CalendarFields> result) {
   3088  auto calendarId = calendar.identifier();
   3089 
   3090  // Step 1.
   3091  result.set(CalendarFields{});
   3092 
   3093  // Steps 2-6. (Optimization for the ISO 8601 calendar.)
   3094  if (calendarId == CalendarId::ISO8601) {
   3095    // Step 2. (Not applicable in our implementation.)
   3096 
   3097    // Step 3.
   3098    result.setMonthCode(MonthCode{date.month});
   3099 
   3100    // Step 4.
   3101    if (type == DateFieldType::MonthDay || type == DateFieldType::Date) {
   3102      result.setDay(date.day);
   3103    }
   3104 
   3105    // Step 5.
   3106    if (type == DateFieldType::YearMonth || type == DateFieldType::Date) {
   3107      result.setYear(date.year);
   3108    }
   3109 
   3110    // Step 6.
   3111    return true;
   3112  }
   3113 
   3114  // Step 2.
   3115  auto cal = CreateICU4XCalendar(calendarId);
   3116  auto dt = CreateICU4XDate(cx, date, calendarId, cal.get());
   3117  if (!dt) {
   3118    return false;
   3119  }
   3120 
   3121  // Step 3.
   3122  auto monthCode = CalendarDateMonthCode(calendarId, dt.get());
   3123  result.setMonthCode(monthCode);
   3124 
   3125  // Step 4.
   3126  if (type == DateFieldType::MonthDay || type == DateFieldType::Date) {
   3127    int32_t day = DayOfMonth(dt.get());
   3128    result.setDay(day);
   3129  }
   3130 
   3131  // Step 5.
   3132  if (type == DateFieldType::YearMonth || type == DateFieldType::Date) {
   3133    int32_t year = CalendarDateYear(calendarId, dt.get());
   3134    result.setYear(year);
   3135  }
   3136 
   3137  // Step 6.
   3138  return true;
   3139 }
   3140 
   3141 /**
   3142 * ISODateToFields ( calendar, isoDate, type )
   3143 */
   3144 bool js::temporal::ISODateToFields(JSContext* cx, Handle<PlainDate> date,
   3145                                   MutableHandle<CalendarFields> result) {
   3146  return ISODateToFields(cx, date.calendar(), date, DateFieldType::Date,
   3147                         result);
   3148 }
   3149 
   3150 /**
   3151 * ISODateToFields ( calendar, isoDate, type )
   3152 */
   3153 bool js::temporal::ISODateToFields(JSContext* cx,
   3154                                   Handle<PlainDateTime> dateTime,
   3155                                   MutableHandle<CalendarFields> result) {
   3156  return ISODateToFields(cx, dateTime.calendar(), dateTime.date(),
   3157                         DateFieldType::Date, result);
   3158 }
   3159 
   3160 /**
   3161 * ISODateToFields ( calendar, isoDate, type )
   3162 */
   3163 bool js::temporal::ISODateToFields(JSContext* cx,
   3164                                   Handle<PlainMonthDay> monthDay,
   3165                                   MutableHandle<CalendarFields> result) {
   3166  return ISODateToFields(cx, monthDay.calendar(), monthDay.date(),
   3167                         DateFieldType::MonthDay, result);
   3168 }
   3169 
   3170 /**
   3171 * ISODateToFields ( calendar, isoDate, type )
   3172 */
   3173 bool js::temporal::ISODateToFields(JSContext* cx,
   3174                                   Handle<PlainYearMonth> yearMonth,
   3175                                   MutableHandle<CalendarFields> result) {
   3176  return ISODateToFields(cx, yearMonth.calendar(), yearMonth.date(),
   3177                         DateFieldType::YearMonth, result);
   3178 }
   3179 
   3180 /**
   3181 * CalendarDateFromFields ( calendar, fields, overflow )
   3182 */
   3183 bool js::temporal::CalendarDateFromFields(JSContext* cx,
   3184                                          Handle<CalendarValue> calendar,
   3185                                          Handle<CalendarFields> fields,
   3186                                          TemporalOverflow overflow,
   3187                                          MutableHandle<PlainDate> result) {
   3188  auto calendarId = calendar.identifier();
   3189 
   3190  // Step 1.
   3191  if (!CalendarResolveFields(cx, calendarId, fields, FieldType::Date)) {
   3192    return false;
   3193  }
   3194 
   3195  // Step 2.
   3196  ISODate date;
   3197  if (!CalendarDateToISO(cx, calendarId, fields, overflow, &date)) {
   3198    return false;
   3199  }
   3200 
   3201  // Steps 3-4.
   3202  return CreateTemporalDate(cx, date, calendar, result);
   3203 }
   3204 
   3205 /**
   3206 * CalendarYearMonthFromFields ( calendar, fields, overflow )
   3207 */
   3208 bool js::temporal::CalendarYearMonthFromFields(
   3209    JSContext* cx, Handle<CalendarValue> calendar,
   3210    Handle<CalendarFields> fields, TemporalOverflow overflow,
   3211    MutableHandle<PlainYearMonth> result) {
   3212  auto calendarId = calendar.identifier();
   3213 
   3214  // Step 2.
   3215  if (!CalendarResolveFields(cx, calendarId, fields, FieldType::YearMonth)) {
   3216    return false;
   3217  }
   3218 
   3219  // Step 1. (Reordered)
   3220  Rooted<CalendarFields> resolvedFields(cx, CalendarFields{fields});
   3221  resolvedFields.setDay(1);
   3222 
   3223  // Step 3.
   3224  ISODate date;
   3225  if (!CalendarDateToISO(cx, calendarId, resolvedFields, overflow, &date)) {
   3226    return false;
   3227  }
   3228 
   3229  // Steps 4-5.
   3230  return CreateTemporalYearMonth(cx, date, calendar, result);
   3231 }
   3232 
   3233 /**
   3234 * CalendarMonthDayFromFields ( calendar, fields, overflow )
   3235 */
   3236 bool js::temporal::CalendarMonthDayFromFields(
   3237    JSContext* cx, Handle<CalendarValue> calendar,
   3238    Handle<CalendarFields> fields, TemporalOverflow overflow,
   3239    MutableHandle<PlainMonthDay> result) {
   3240  auto calendarId = calendar.identifier();
   3241 
   3242  // Step 1.
   3243  if (!CalendarResolveFields(cx, calendarId, fields, FieldType::MonthDay)) {
   3244    return false;
   3245  }
   3246 
   3247  // Step 2.
   3248  ISODate date;
   3249  if (!CalendarMonthDayToISOReferenceDate(cx, calendarId, fields, overflow,
   3250                                          &date)) {
   3251    return false;
   3252  }
   3253 
   3254  // Step 3-4.
   3255  return CreateTemporalMonthDay(cx, date, calendar, result);
   3256 }
   3257 
   3258 /**
   3259 * Mathematical Operations, "modulo" notation.
   3260 */
   3261 static int32_t NonNegativeModulo(int64_t x, int32_t y) {
   3262  MOZ_ASSERT(y > 0);
   3263 
   3264  int32_t result = mozilla::AssertedCast<int32_t>(x % y);
   3265  return (result < 0) ? (result + y) : result;
   3266 }
   3267 
   3268 /**
   3269 * RegulateISODate ( year, month, day, overflow )
   3270 *
   3271 * With |overflow = "constrain"|.
   3272 */
   3273 static ISODate ConstrainISODate(const ISODate& date) {
   3274  const auto& [year, month, day] = date;
   3275 
   3276  // Step 1.a.
   3277  int32_t m = std::clamp(month, 1, 12);
   3278 
   3279  // Step 1.b.
   3280  int32_t daysInMonth = ::ISODaysInMonth(year, m);
   3281 
   3282  // Step 1.c.
   3283  int32_t d = std::clamp(day, 1, daysInMonth);
   3284 
   3285  // Step 3.
   3286  return {year, m, d};
   3287 }
   3288 
   3289 /**
   3290 * RegulateISODate ( year, month, day, overflow )
   3291 */
   3292 static bool RegulateISODate(JSContext* cx, const ISODate& date,
   3293                            TemporalOverflow overflow, ISODate* result) {
   3294  // Step 1.
   3295  if (overflow == TemporalOverflow::Constrain) {
   3296    // Steps 1.a-c and 3.
   3297    *result = ConstrainISODate(date);
   3298    return true;
   3299  }
   3300 
   3301  // Step 2.a.
   3302  MOZ_ASSERT(overflow == TemporalOverflow::Reject);
   3303 
   3304  // Step 2.b.
   3305  if (!ThrowIfInvalidISODate(cx, date)) {
   3306    return false;
   3307  }
   3308 
   3309  // Step 3. (Inlined call to CreateISODateRecord.)
   3310  *result = date;
   3311  return true;
   3312 }
   3313 
   3314 struct BalancedYearMonth final {
   3315  int64_t year = 0;
   3316  int32_t month = 0;
   3317 };
   3318 
   3319 /**
   3320 * BalanceISOYearMonth ( year, month )
   3321 */
   3322 static BalancedYearMonth BalanceISOYearMonth(int64_t year, int64_t month) {
   3323  MOZ_ASSERT(std::abs(year) < (int64_t(1) << 33),
   3324             "year is the addition of plain-date year with duration years");
   3325  MOZ_ASSERT(std::abs(month) < (int64_t(1) << 33),
   3326             "month is the addition of plain-date month with duration months");
   3327 
   3328  // Step 1. (Not applicable in our implementation.)
   3329 
   3330  // Step 2.
   3331  int64_t balancedYear = year + temporal::FloorDiv(month - 1, 12);
   3332 
   3333  // Step 3.
   3334  int32_t balancedMonth = NonNegativeModulo(month - 1, 12) + 1;
   3335  MOZ_ASSERT(1 <= balancedMonth && balancedMonth <= 12);
   3336 
   3337  // Step 4.
   3338  return {balancedYear, balancedMonth};
   3339 }
   3340 
   3341 static BalancedYearMonth BalanceYearMonth(int64_t year, int64_t month,
   3342                                          int32_t monthsPerYear) {
   3343  MOZ_ASSERT(std::abs(year) < (int64_t(1) << 33),
   3344             "year is the addition of plain-date year with duration years");
   3345  MOZ_ASSERT(std::abs(month) < (int64_t(1) << 33),
   3346             "month is the addition of plain-date month with duration months");
   3347 
   3348  int64_t balancedYear = year + temporal::FloorDiv(month - 1, monthsPerYear);
   3349 
   3350  int32_t balancedMonth = NonNegativeModulo(month - 1, monthsPerYear) + 1;
   3351  MOZ_ASSERT(1 <= balancedMonth && balancedMonth <= monthsPerYear);
   3352 
   3353  return {balancedYear, balancedMonth};
   3354 }
   3355 
   3356 /**
   3357 * CalendarDateAdd ( calendar, isoDate, duration, overflow )
   3358 */
   3359 static bool AddISODate(JSContext* cx, const ISODate& isoDate,
   3360                       const DateDuration& duration, TemporalOverflow overflow,
   3361                       ISODate* result) {
   3362  MOZ_ASSERT(ISODateWithinLimits(isoDate));
   3363  MOZ_ASSERT(IsValidDuration(duration));
   3364 
   3365  // Step 1.a.
   3366  auto yearMonth = BalanceISOYearMonth(isoDate.year + duration.years,
   3367                                       isoDate.month + duration.months);
   3368  MOZ_ASSERT(1 <= yearMonth.month && yearMonth.month <= 12);
   3369 
   3370  auto balancedYear = mozilla::CheckedInt<int32_t>(yearMonth.year);
   3371  if (!balancedYear.isValid()) {
   3372    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3373                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   3374    return false;
   3375  }
   3376 
   3377  // Step 1.b.
   3378  ISODate regulated;
   3379  if (!RegulateISODate(cx, {balancedYear.value(), yearMonth.month, isoDate.day},
   3380                       overflow, &regulated)) {
   3381    return false;
   3382  }
   3383  if (!ISODateWithinLimits(regulated)) {
   3384    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3385                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   3386    return false;
   3387  }
   3388 
   3389  // Step 1.c.
   3390  int64_t days = duration.days + duration.weeks * 7;
   3391 
   3392  // Step 1.d.
   3393  ISODate balanced;
   3394  if (!BalanceISODate(cx, regulated, days, &balanced)) {
   3395    return false;
   3396  }
   3397  MOZ_ASSERT(IsValidISODate(balanced));
   3398 
   3399  *result = balanced;
   3400  return true;
   3401 }
   3402 
   3403 struct CalendarDate {
   3404  int32_t year = 0;
   3405  MonthCode monthCode = {};
   3406  int32_t day = 0;
   3407 };
   3408 
   3409 struct CalendarDateWithOrdinalMonth {
   3410  int32_t year = 0;
   3411  int32_t month = 0;
   3412  int32_t day = 0;
   3413 };
   3414 
   3415 /**
   3416 * CompareISODate adjusted for calendar dates.
   3417 */
   3418 static int32_t CompareCalendarDate(const CalendarDate& one,
   3419                                   const CalendarDate& two) {
   3420  if (one.year != two.year) {
   3421    return one.year < two.year ? -1 : 1;
   3422  }
   3423  if (one.monthCode != two.monthCode) {
   3424    return one.monthCode < two.monthCode ? -1 : 1;
   3425  }
   3426  if (one.day != two.day) {
   3427    return one.day < two.day ? -1 : 1;
   3428  }
   3429  return 0;
   3430 }
   3431 
   3432 /**
   3433 * CompareISODate adjusted for calendar dates.
   3434 */
   3435 static int32_t CompareCalendarDate(const CalendarDateWithOrdinalMonth& one,
   3436                                   const CalendarDateWithOrdinalMonth& two) {
   3437  return CompareISODate(ISODate{one.year, one.month, one.day},
   3438                        ISODate{two.year, two.month, two.day});
   3439 }
   3440 
   3441 static CalendarDate ToCalendarDate(CalendarId calendarId,
   3442                                   const icu4x::capi::Date* dt) {
   3443  int32_t year = CalendarDateYear(calendarId, dt);
   3444  auto monthCode = CalendarDateMonthCode(calendarId, dt);
   3445  int32_t day = DayOfMonth(dt);
   3446 
   3447  return {year, monthCode, day};
   3448 }
   3449 
   3450 static CalendarDateWithOrdinalMonth ToCalendarDateWithOrdinalMonth(
   3451    CalendarId calendarId, const icu4x::capi::Date* dt) {
   3452  MOZ_ASSERT(!CalendarHasLeapMonths(calendarId));
   3453 
   3454  int32_t year = CalendarDateYear(calendarId, dt);
   3455  int32_t month = OrdinalMonth(dt);
   3456  int32_t day = DayOfMonth(dt);
   3457 
   3458  return {year, month, day};
   3459 }
   3460 
   3461 static bool AddYearMonthDuration(
   3462    JSContext* cx, CalendarId calendarId,
   3463    const CalendarDateWithOrdinalMonth& calendarDate,
   3464    const DateDuration& duration, CalendarDate* result) {
   3465  MOZ_ASSERT(!CalendarHasLeapMonths(calendarId));
   3466  MOZ_ASSERT(IsValidDuration(duration));
   3467 
   3468  auto [year, month, day] = calendarDate;
   3469 
   3470  // Months per year are fixed, so we can directly compute the final number of
   3471  // years.
   3472  auto yearMonth =
   3473      BalanceYearMonth(year + duration.years, month + duration.months,
   3474                       CalendarMonthsPerYear(calendarId));
   3475 
   3476  auto balancedYear = mozilla::CheckedInt<int32_t>(yearMonth.year);
   3477  if (!balancedYear.isValid()) {
   3478    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3479                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   3480    return false;
   3481  }
   3482 
   3483  *result = {balancedYear.value(), MonthCode{yearMonth.month}, day};
   3484  return true;
   3485 }
   3486 
   3487 static bool AddYearMonthDuration(JSContext* cx, CalendarId calendarId,
   3488                                 const icu4x::capi::Calendar* calendar,
   3489                                 const CalendarDate& calendarDate,
   3490                                 const DateDuration& duration,
   3491                                 CalendarDate* result) {
   3492  MOZ_ASSERT(CalendarHasLeapMonths(calendarId));
   3493  MOZ_ASSERT(IsValidDuration(duration));
   3494 
   3495  auto [year, monthCode, day] = calendarDate;
   3496 
   3497  // Add all duration years.
   3498  auto durationYear = mozilla::CheckedInt<int32_t>(year) + duration.years;
   3499  if (!durationYear.isValid()) {
   3500    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3501                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   3502    return false;
   3503  }
   3504  year = durationYear.value();
   3505 
   3506  // Months per year are variable, so we have construct a new date for each
   3507  // year to balance the years and months.
   3508  int64_t months = duration.months;
   3509  if (months != 0) {
   3510    auto eraYear = CalendarEraYear(calendarId, year);
   3511    auto firstDayOfMonth =
   3512        CreateDateFromCodes(cx, calendarId, calendar, eraYear, monthCode, 1,
   3513                            TemporalOverflow::Constrain);
   3514    if (!firstDayOfMonth) {
   3515      return false;
   3516    }
   3517 
   3518    if (months > 0) {
   3519      while (true) {
   3520        // Check if adding |months| is still in the current year.
   3521        int32_t month = OrdinalMonth(firstDayOfMonth.get());
   3522        int32_t monthsInYear = MonthsInYear(firstDayOfMonth.get());
   3523        if (month + months <= monthsInYear) {
   3524          break;
   3525        }
   3526 
   3527        // We've crossed a year boundary. Increase |year| and adjust |months|.
   3528        year += 1;
   3529        months -= (monthsInYear - month + 1);
   3530 
   3531        // Restart the loop with the first month of the next year.
   3532        eraYear = CalendarEraYear(calendarId, year);
   3533        firstDayOfMonth = CreateDateFrom(cx, calendarId, calendar, eraYear, 1,
   3534                                         1, TemporalOverflow::Constrain);
   3535        if (!firstDayOfMonth) {
   3536          return false;
   3537        }
   3538      }
   3539    } else {
   3540      int32_t monthsPerYear = CalendarMonthsPerYear(calendarId);
   3541 
   3542      while (true) {
   3543        // Check if subtracting |months| is still in the current year.
   3544        int32_t month = OrdinalMonth(firstDayOfMonth.get());
   3545        if (month + months >= 1) {
   3546          break;
   3547        }
   3548 
   3549        // We've crossed a year boundary. Decrease |year| and adjust |months|.
   3550        year -= 1;
   3551        months += month;
   3552 
   3553        // Restart the loop with the last month of the previous year.
   3554        eraYear = CalendarEraYear(calendarId, year);
   3555        firstDayOfMonth =
   3556            CreateDateFrom(cx, calendarId, calendar, eraYear, monthsPerYear, 1,
   3557                           TemporalOverflow::Constrain);
   3558        if (!firstDayOfMonth) {
   3559          return false;
   3560        }
   3561      }
   3562    }
   3563 
   3564    // Compute the actual month to find the correct month code.
   3565    int32_t month = OrdinalMonth(firstDayOfMonth.get()) + months;
   3566    firstDayOfMonth = CreateDateFrom(cx, calendarId, calendar, eraYear, month,
   3567                                     1, TemporalOverflow::Constrain);
   3568    if (!firstDayOfMonth) {
   3569      return false;
   3570    }
   3571 
   3572    monthCode = CalendarDateMonthCode(calendarId, firstDayOfMonth.get());
   3573  }
   3574 
   3575  *result = {year, monthCode, day};
   3576  return true;
   3577 }
   3578 
   3579 static bool AddNonISODate(JSContext* cx, CalendarId calendarId,
   3580                          const ISODate& isoDate, const DateDuration& duration,
   3581                          TemporalOverflow overflow, ISODate* result) {
   3582  MOZ_ASSERT(ISODateWithinLimits(isoDate));
   3583  MOZ_ASSERT(IsValidDuration(duration));
   3584 
   3585  auto cal = CreateICU4XCalendar(calendarId);
   3586 
   3587  auto dt = CreateICU4XDate(cx, isoDate, calendarId, cal.get());
   3588  if (!dt) {
   3589    return false;
   3590  }
   3591 
   3592  CalendarDate calendarDate;
   3593  if (!CalendarHasLeapMonths(calendarId)) {
   3594    auto date = ToCalendarDateWithOrdinalMonth(calendarId, dt.get());
   3595    if (!AddYearMonthDuration(cx, calendarId, date, duration, &calendarDate)) {
   3596      return false;
   3597    }
   3598  } else {
   3599    auto date = ToCalendarDate(calendarId, dt.get());
   3600    if (!AddYearMonthDuration(cx, calendarId, cal.get(), date, duration,
   3601                              &calendarDate)) {
   3602      return false;
   3603    }
   3604  }
   3605 
   3606  // Regulate according to |overflow|.
   3607  auto eraYear = CalendarEraYear(calendarId, calendarDate.year);
   3608  auto regulated =
   3609      CreateDateFromCodes(cx, calendarId, cal.get(), eraYear,
   3610                          calendarDate.monthCode, calendarDate.day, overflow);
   3611  if (!regulated) {
   3612    return false;
   3613  }
   3614 
   3615  // Compute the corresponding ISO date.
   3616  auto regulatedIso = ToISODate(regulated.get());
   3617  if (!ISODateWithinLimits(regulatedIso)) {
   3618    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3619                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   3620    return false;
   3621  }
   3622 
   3623  // Add duration days and weeks.
   3624  int64_t days = duration.days + duration.weeks * 7;
   3625 
   3626  // Adding days isn't calendar-specific, so we can use BalanceISODate.
   3627  ISODate balancedIso;
   3628  if (!BalanceISODate(cx, regulatedIso, days, &balancedIso)) {
   3629    return false;
   3630  }
   3631  MOZ_ASSERT(IsValidISODate(balancedIso));
   3632 
   3633  *result = balancedIso;
   3634  return true;
   3635 }
   3636 
   3637 /**
   3638 * NonISODateAdd ( calendar, isoDate, duration, overflow )
   3639 */
   3640 static bool NonISODateAdd(JSContext* cx, CalendarId calendarId,
   3641                          const ISODate& isoDate, const DateDuration& duration,
   3642                          TemporalOverflow overflow, ISODate* result) {
   3643  // ICU4X doesn't yet provide a public API for CalendarDateAdd.
   3644  //
   3645  // https://github.com/unicode-org/icu4x/issues/3964
   3646 
   3647  // If neither |years| nor |months| are present, just delegate to the ISO 8601
   3648  // calendar version. This works because all supported calendars use a 7-days
   3649  // week.
   3650  if (duration.years == 0 && duration.months == 0) {
   3651    return AddISODate(cx, isoDate, duration, overflow, result);
   3652  }
   3653 
   3654  switch (calendarId) {
   3655    case CalendarId::ISO8601:
   3656    case CalendarId::Buddhist:
   3657    case CalendarId::Gregorian:
   3658    case CalendarId::Japanese:
   3659    case CalendarId::ROC:
   3660      // Use the ISO 8601 calendar if the calendar system starts its year at the
   3661      // same time as the ISO 8601 calendar and all months exactly match the
   3662      // ISO 8601 calendar months.
   3663      return AddISODate(cx, isoDate, duration, overflow, result);
   3664 
   3665    case CalendarId::Chinese:
   3666    case CalendarId::Coptic:
   3667    case CalendarId::Dangi:
   3668    case CalendarId::Ethiopian:
   3669    case CalendarId::EthiopianAmeteAlem:
   3670    case CalendarId::Hebrew:
   3671    case CalendarId::Indian:
   3672    case CalendarId::IslamicCivil:
   3673    case CalendarId::IslamicTabular:
   3674    case CalendarId::IslamicUmmAlQura:
   3675    case CalendarId::Persian:
   3676      return AddNonISODate(cx, calendarId, isoDate, duration, overflow, result);
   3677  }
   3678  MOZ_CRASH("invalid calendar id");
   3679 }
   3680 
   3681 /**
   3682 * CalendarDateAdd ( calendar, isoDate, duration, overflow )
   3683 */
   3684 bool js::temporal::CalendarDateAdd(JSContext* cx,
   3685                                   Handle<CalendarValue> calendar,
   3686                                   const ISODate& isoDate,
   3687                                   const DateDuration& duration,
   3688                                   TemporalOverflow overflow, ISODate* result) {
   3689  MOZ_ASSERT(ISODateWithinLimits(isoDate));
   3690  MOZ_ASSERT(IsValidDuration(duration));
   3691 
   3692  auto calendarId = calendar.identifier();
   3693 
   3694  // Steps 1-2.
   3695  if (calendarId == CalendarId::ISO8601) {
   3696    if (!AddISODate(cx, isoDate, duration, overflow, result)) {
   3697      return false;
   3698    }
   3699  } else {
   3700    if (!NonISODateAdd(cx, calendarId, isoDate, duration, overflow, result)) {
   3701      return false;
   3702    }
   3703  }
   3704 
   3705  // Step 3.
   3706  if (!ISODateWithinLimits(*result)) {
   3707    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3708                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   3709    return false;
   3710  }
   3711 
   3712  // Step 4.
   3713  return true;
   3714 }
   3715 
   3716 /**
   3717 * CalendarDateUntil ( calendar, one, two, largestUnit )
   3718 */
   3719 static DateDuration DifferenceISODate(const ISODate& one, const ISODate& two,
   3720                                      TemporalUnit largestUnit) {
   3721  MOZ_ASSERT(IsValidISODate(one));
   3722  MOZ_ASSERT(IsValidISODate(two));
   3723 
   3724  // Both inputs are also within the date limits.
   3725  MOZ_ASSERT(ISODateWithinLimits(one));
   3726  MOZ_ASSERT(ISODateWithinLimits(two));
   3727 
   3728  MOZ_ASSERT(TemporalUnit::Year <= largestUnit &&
   3729             largestUnit <= TemporalUnit::Day);
   3730 
   3731  // Step 1.a.
   3732  int32_t sign = -CompareISODate(one, two);
   3733 
   3734  // Step 1.b.
   3735  if (sign == 0) {
   3736    return {};
   3737  }
   3738 
   3739  // Step 1.c.
   3740  int32_t years = 0;
   3741 
   3742  // Step 1.e. (Reordered)
   3743  int32_t months = 0;
   3744 
   3745  // Steps 1.d and 1.f.
   3746  if (largestUnit == TemporalUnit::Year || largestUnit == TemporalUnit::Month) {
   3747    years = two.year - one.year;
   3748    months = two.month - one.month;
   3749 
   3750    auto intermediate = ISODate{one.year + years, one.month, one.day};
   3751    if (CompareISODate(intermediate, two) * sign > 0) {
   3752      years -= sign;
   3753      months += 12 * sign;
   3754    }
   3755 
   3756    intermediate = ISODate{one.year + years, one.month + months, one.day};
   3757    if (intermediate.month > 12) {
   3758      intermediate.month -= 12;
   3759      intermediate.year += 1;
   3760    } else if (intermediate.month < 1) {
   3761      intermediate.month += 12;
   3762      intermediate.year -= 1;
   3763    }
   3764    if (CompareISODate(intermediate, two) * sign > 0) {
   3765      months -= sign;
   3766    }
   3767 
   3768    if (largestUnit == TemporalUnit::Month) {
   3769      months += years * 12;
   3770      years = 0;
   3771    }
   3772  }
   3773 
   3774  // Balance intermediate result per ISODateSurpasses.
   3775  auto intermediate = BalanceISOYearMonth(one.year + years, one.month + months);
   3776  auto constrained = ConstrainISODate(
   3777      ISODate{int32_t(intermediate.year), intermediate.month, one.day});
   3778 
   3779  // Step 1.g.
   3780  int64_t weeks = 0;
   3781 
   3782  // Steps 1.i-k.
   3783  int64_t days = MakeDay(two) - MakeDay(constrained);
   3784 
   3785  // Step 1.h. (Weeks computed from days.)
   3786  if (largestUnit == TemporalUnit::Week) {
   3787    weeks = days / 7;
   3788    days %= 7;
   3789  }
   3790 
   3791  // Step 1.l.
   3792  auto result = DateDuration{
   3793      int64_t(years),
   3794      int64_t(months),
   3795      int64_t(weeks),
   3796      int64_t(days),
   3797  };
   3798  MOZ_ASSERT(IsValidDuration(result));
   3799  return result;
   3800 }
   3801 
   3802 static bool DifferenceNonISODate(JSContext* cx, CalendarId calendarId,
   3803                                 const ISODate& one, const ISODate& two,
   3804                                 TemporalUnit largestUnit,
   3805                                 DateDuration* result) {
   3806  // Both inputs are also within the date limits.
   3807  MOZ_ASSERT(ISODateWithinLimits(one));
   3808  MOZ_ASSERT(ISODateWithinLimits(two));
   3809 
   3810  MOZ_ASSERT(TemporalUnit::Year <= largestUnit &&
   3811             largestUnit <= TemporalUnit::Month);
   3812 
   3813  if (one == two) {
   3814    *result = {};
   3815    return true;
   3816  }
   3817 
   3818  auto cal = CreateICU4XCalendar(calendarId);
   3819 
   3820  auto dtOne = CreateICU4XDate(cx, one, calendarId, cal.get());
   3821  if (!dtOne) {
   3822    return false;
   3823  }
   3824 
   3825  auto dtTwo = CreateICU4XDate(cx, two, calendarId, cal.get());
   3826  if (!dtTwo) {
   3827    return false;
   3828  }
   3829 
   3830  int32_t years = 0;
   3831  int32_t months = 0;
   3832 
   3833  ISODate constrainedIso;
   3834  if (!CalendarHasLeapMonths(calendarId)) {
   3835    // If the months per year are fixed, we can use a modified DifferenceISODate
   3836    // implementation to compute the date duration.
   3837    int32_t monthsPerYear = CalendarMonthsPerYear(calendarId);
   3838 
   3839    auto oneDate = ToCalendarDateWithOrdinalMonth(calendarId, dtOne.get());
   3840    auto twoDate = ToCalendarDateWithOrdinalMonth(calendarId, dtTwo.get());
   3841 
   3842    int32_t sign = -CompareCalendarDate(oneDate, twoDate);
   3843    MOZ_ASSERT(sign != 0);
   3844 
   3845    years = twoDate.year - oneDate.year;
   3846    months = twoDate.month - oneDate.month;
   3847 
   3848    // If |oneDate + years| surpasses |twoDate|, reduce |years| by one and add
   3849    // |monthsPerYear| to |months|. The next step will balance the intermediate
   3850    // result.
   3851    auto intermediate = CalendarDateWithOrdinalMonth{
   3852        oneDate.year + years, oneDate.month, oneDate.day};
   3853    if (CompareCalendarDate(intermediate, twoDate) * sign > 0) {
   3854      years -= sign;
   3855      months += monthsPerYear * sign;
   3856    }
   3857 
   3858    // Add both |years| and |months| and then balance the intermediate result to
   3859    // ensure its month is within the valid bounds.
   3860    intermediate = CalendarDateWithOrdinalMonth{
   3861        oneDate.year + years, oneDate.month + months, oneDate.day};
   3862    if (intermediate.month > monthsPerYear) {
   3863      intermediate.month -= monthsPerYear;
   3864      intermediate.year += 1;
   3865    } else if (intermediate.month < 1) {
   3866      intermediate.month += monthsPerYear;
   3867      intermediate.year -= 1;
   3868    }
   3869 
   3870    // If |intermediate| surpasses |twoDate|, reduce |month| by one.
   3871    if (CompareCalendarDate(intermediate, twoDate) * sign > 0) {
   3872      months -= sign;
   3873    }
   3874 
   3875    // Convert years to months if necessary.
   3876    if (largestUnit == TemporalUnit::Month) {
   3877      months += years * monthsPerYear;
   3878      years = 0;
   3879    }
   3880 
   3881    // Constrain to a proper date.
   3882    auto balanced = BalanceYearMonth(oneDate.year + years,
   3883                                     oneDate.month + months, monthsPerYear);
   3884 
   3885    auto eraYear = CalendarEraYear(calendarId, balanced.year);
   3886    auto constrained =
   3887        CreateDateFrom(cx, calendarId, cal.get(), eraYear, balanced.month,
   3888                       oneDate.day, TemporalOverflow::Constrain);
   3889    if (!constrained) {
   3890      return false;
   3891    }
   3892    constrainedIso = ToISODate(constrained.get());
   3893 
   3894    MOZ_ASSERT(CompareISODate(constrainedIso, two) * sign <= 0,
   3895               "constrained doesn't surpass two");
   3896  } else {
   3897    auto oneDate = ToCalendarDate(calendarId, dtOne.get());
   3898    auto twoDate = ToCalendarDate(calendarId, dtTwo.get());
   3899 
   3900    int32_t sign = -CompareCalendarDate(oneDate, twoDate);
   3901    MOZ_ASSERT(sign != 0);
   3902 
   3903    years = twoDate.year - oneDate.year;
   3904 
   3905    // If |oneDate + years| surpasses |twoDate|, reduce |years| by one and add
   3906    // |monthsPerYear| to |months|. The next step will balance the intermediate
   3907    // result.
   3908    auto eraYear = CalendarEraYear(calendarId, oneDate.year + years);
   3909    auto constrained = CreateDateFromCodes(cx, calendarId, cal.get(), eraYear,
   3910                                           oneDate.monthCode, oneDate.day,
   3911                                           TemporalOverflow::Constrain);
   3912    if (!constrained) {
   3913      return false;
   3914    }
   3915 
   3916    auto constrainedDate = ToCalendarDate(calendarId, constrained.get());
   3917    if (CompareCalendarDate(constrainedDate, twoDate) * sign > 0) {
   3918      years -= sign;
   3919    }
   3920 
   3921    // Add as many months as possible without surpassing |twoDate|.
   3922    while (true) {
   3923      CalendarDate intermediateDate;
   3924      if (!AddYearMonthDuration(cx, calendarId, cal.get(), oneDate,
   3925                                {years, months + sign}, &intermediateDate)) {
   3926        return false;
   3927      }
   3928      if (CompareCalendarDate(intermediateDate, twoDate) * sign > 0) {
   3929        break;
   3930      }
   3931      months += sign;
   3932      constrainedDate = intermediateDate;
   3933    }
   3934    MOZ_ASSERT(std::abs(months) < CalendarMonthsPerYear(calendarId));
   3935 
   3936    // Convert years to months if necessary.
   3937    if (largestUnit == TemporalUnit::Month && years != 0) {
   3938      auto monthsUntilEndOfYear = [](const icu4x::capi::Date* date) {
   3939        int32_t month = OrdinalMonth(date);
   3940        int32_t monthsInYear = MonthsInYear(date);
   3941        MOZ_ASSERT(1 <= month && month <= monthsInYear);
   3942 
   3943        return monthsInYear - month + 1;
   3944      };
   3945 
   3946      auto monthsSinceStartOfYear = [](const icu4x::capi::Date* date) {
   3947        return OrdinalMonth(date) - 1;
   3948      };
   3949 
   3950      // Add months until end of year resp. since start of year.
   3951      if (sign > 0) {
   3952        months += monthsUntilEndOfYear(dtOne.get());
   3953      } else {
   3954        months -= monthsSinceStartOfYear(dtOne.get());
   3955      }
   3956 
   3957      // Months in full year.
   3958      for (int32_t y = sign; y != years; y += sign) {
   3959        auto eraYear = CalendarEraYear(calendarId, oneDate.year + y);
   3960        auto dt =
   3961            CreateDateFromCodes(cx, calendarId, cal.get(), eraYear,
   3962                                MonthCode{1}, 1, TemporalOverflow::Constrain);
   3963        if (!dt) {
   3964          return false;
   3965        }
   3966        months += MonthsInYear(dt.get()) * sign;
   3967      }
   3968 
   3969      // Add months since start of year resp. until end of year.
   3970      auto eraYear = CalendarEraYear(calendarId, oneDate.year + years);
   3971      auto dt = CreateDateFromCodes(cx, calendarId, cal.get(), eraYear,
   3972                                    oneDate.monthCode, 1,
   3973                                    TemporalOverflow::Constrain);
   3974      if (!dt) {
   3975        return false;
   3976      }
   3977      if (sign > 0) {
   3978        months += monthsSinceStartOfYear(dt.get());
   3979      } else {
   3980        months -= monthsUntilEndOfYear(dt.get());
   3981      }
   3982 
   3983      years = 0;
   3984    }
   3985 
   3986    eraYear = CalendarEraYear(calendarId, constrainedDate.year);
   3987    constrained = CreateDateFromCodes(
   3988        cx, calendarId, cal.get(), eraYear, constrainedDate.monthCode,
   3989        constrainedDate.day, TemporalOverflow::Constrain);
   3990    if (!constrained) {
   3991      return false;
   3992    }
   3993    constrainedIso = ToISODate(constrained.get());
   3994 
   3995    MOZ_ASSERT(CompareISODate(constrainedIso, two) * sign <= 0,
   3996               "constrained doesn't surpass two");
   3997  }
   3998 
   3999  int64_t days = MakeDay(two) - MakeDay(constrainedIso);
   4000 
   4001  *result = DateDuration{
   4002      int64_t(years),
   4003      int64_t(months),
   4004      0,
   4005      int64_t(days),
   4006  };
   4007  MOZ_ASSERT(IsValidDuration(*result));
   4008  return true;
   4009 }
   4010 
   4011 /**
   4012 * NonISODateUntil ( calendar, one, two, largestUnit )
   4013 */
   4014 static bool NonISODateUntil(JSContext* cx, CalendarId calendarId,
   4015                            const ISODate& one, const ISODate& two,
   4016                            TemporalUnit largestUnit, DateDuration* result) {
   4017  // ICU4X doesn't yet provide a public API for CalendarDateUntil.
   4018  //
   4019  // https://github.com/unicode-org/icu4x/issues/3964
   4020 
   4021  // Delegate to the ISO 8601 calendar for "weeks" and "days". This works
   4022  // because all supported calendars use a 7-days week.
   4023  if (largestUnit >= TemporalUnit::Week) {
   4024    *result = DifferenceISODate(one, two, largestUnit);
   4025    return true;
   4026  }
   4027 
   4028  switch (calendarId) {
   4029    case CalendarId::ISO8601:
   4030    case CalendarId::Buddhist:
   4031    case CalendarId::Gregorian:
   4032    case CalendarId::Japanese:
   4033    case CalendarId::ROC:
   4034      // Use the ISO 8601 calendar if the calendar system starts its year at the
   4035      // same time as the ISO 8601 calendar and all months exactly match the
   4036      // ISO 8601 calendar months.
   4037      *result = DifferenceISODate(one, two, largestUnit);
   4038      return true;
   4039 
   4040    case CalendarId::Chinese:
   4041    case CalendarId::Coptic:
   4042    case CalendarId::Dangi:
   4043    case CalendarId::Ethiopian:
   4044    case CalendarId::EthiopianAmeteAlem:
   4045    case CalendarId::Hebrew:
   4046    case CalendarId::Indian:
   4047    case CalendarId::IslamicCivil:
   4048    case CalendarId::IslamicTabular:
   4049    case CalendarId::IslamicUmmAlQura:
   4050    case CalendarId::Persian:
   4051      return DifferenceNonISODate(cx, calendarId, one, two, largestUnit,
   4052                                  result);
   4053  }
   4054  MOZ_CRASH("invalid calendar id");
   4055 }
   4056 
   4057 /**
   4058 * CalendarDateUntil ( calendar, one, two, largestUnit )
   4059 */
   4060 bool js::temporal::CalendarDateUntil(JSContext* cx,
   4061                                     Handle<CalendarValue> calendar,
   4062                                     const ISODate& one, const ISODate& two,
   4063                                     TemporalUnit largestUnit,
   4064                                     DateDuration* result) {
   4065  MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
   4066 
   4067  auto calendarId = calendar.identifier();
   4068 
   4069  // Step 1.
   4070  if (calendarId == CalendarId::ISO8601) {
   4071    *result = DifferenceISODate(one, two, largestUnit);
   4072    return true;
   4073  }
   4074 
   4075  // Step 2.
   4076  return NonISODateUntil(cx, calendarId, one, two, largestUnit, result);
   4077 }