tor-browser

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

PlainDate.cpp (50091B)


      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/PlainDate.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/CheckedInt.h"
     11 
     12 #include <cmath>
     13 #include <cstdlib>
     14 #include <stdint.h>
     15 
     16 #include "jsdate.h"
     17 #include "jsnum.h"
     18 #include "jspubtd.h"
     19 #include "jstypes.h"
     20 #include "NamespaceImports.h"
     21 
     22 #include "builtin/intl/DateTimeFormat.h"
     23 #include "builtin/temporal/Calendar.h"
     24 #include "builtin/temporal/CalendarFields.h"
     25 #include "builtin/temporal/Duration.h"
     26 #include "builtin/temporal/Instant.h"
     27 #include "builtin/temporal/PlainDateTime.h"
     28 #include "builtin/temporal/PlainMonthDay.h"
     29 #include "builtin/temporal/PlainTime.h"
     30 #include "builtin/temporal/PlainYearMonth.h"
     31 #include "builtin/temporal/Temporal.h"
     32 #include "builtin/temporal/TemporalParser.h"
     33 #include "builtin/temporal/TemporalRoundingMode.h"
     34 #include "builtin/temporal/TemporalTypes.h"
     35 #include "builtin/temporal/TemporalUnit.h"
     36 #include "builtin/temporal/TimeZone.h"
     37 #include "builtin/temporal/ToString.h"
     38 #include "builtin/temporal/ZonedDateTime.h"
     39 #include "gc/AllocKind.h"
     40 #include "gc/Barrier.h"
     41 #include "gc/GCEnum.h"
     42 #include "js/CallArgs.h"
     43 #include "js/CallNonGenericMethod.h"
     44 #include "js/Class.h"
     45 #include "js/ErrorReport.h"
     46 #include "js/friend/ErrorMessages.h"
     47 #include "js/PropertyDescriptor.h"
     48 #include "js/PropertySpec.h"
     49 #include "js/RootingAPI.h"
     50 #include "js/TypeDecls.h"
     51 #include "js/Value.h"
     52 #include "vm/BytecodeUtil.h"
     53 #include "vm/GlobalObject.h"
     54 #include "vm/JSAtomState.h"
     55 #include "vm/JSContext.h"
     56 #include "vm/JSObject.h"
     57 #include "vm/PlainObject.h"
     58 #include "vm/StringType.h"
     59 
     60 #include "vm/JSObject-inl.h"
     61 #include "vm/NativeObject-inl.h"
     62 #include "vm/ObjectOperations-inl.h"
     63 
     64 using namespace js;
     65 using namespace js::temporal;
     66 
     67 static inline bool IsPlainDate(Handle<Value> v) {
     68  return v.isObject() && v.toObject().is<PlainDateObject>();
     69 }
     70 
     71 #ifdef DEBUG
     72 /**
     73 * IsValidISODate ( year, month, day )
     74 */
     75 bool js::temporal::IsValidISODate(const ISODate& date) {
     76  const auto& [year, month, day] = date;
     77 
     78  // Step 1.
     79  if (month < 1 || month > 12) {
     80    return false;
     81  }
     82 
     83  // Step 2.
     84  int32_t daysInMonth = js::temporal::ISODaysInMonth(year, month);
     85 
     86  // Steps 3-4.
     87  return 1 <= day && day <= daysInMonth;
     88 }
     89 #endif
     90 
     91 /**
     92 * ISODateWithinLimits ( isoDate )
     93 */
     94 bool js::temporal::ISODateWithinLimits(const ISODate& isoDate) {
     95  MOZ_ASSERT(IsValidISODate(isoDate));
     96 
     97  constexpr auto min = ISODate::min();
     98  constexpr auto max = ISODate::max();
     99 
    100  const auto& year = isoDate.year;
    101 
    102  // Fast-path when the input is definitely in range.
    103  if (min.year < year && year < max.year) {
    104    return true;
    105  }
    106 
    107  // Check |isoDate| is within the valid limits.
    108  if (year < 0) {
    109    return isoDate >= min;
    110  }
    111  return isoDate <= max;
    112 }
    113 
    114 static void ReportInvalidDateValue(JSContext* cx, const char* name, int32_t min,
    115                                   int32_t max, double num) {
    116  Int32ToCStringBuf minCbuf;
    117  const char* minStr = Int32ToCString(&minCbuf, min);
    118 
    119  Int32ToCStringBuf maxCbuf;
    120  const char* maxStr = Int32ToCString(&maxCbuf, max);
    121 
    122  ToCStringBuf numCbuf;
    123  const char* numStr = NumberToCString(&numCbuf, num);
    124 
    125  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    126                            JSMSG_TEMPORAL_PLAIN_DATE_INVALID_VALUE, name,
    127                            minStr, maxStr, numStr);
    128 }
    129 
    130 template <typename T>
    131 static inline bool ThrowIfInvalidDateValue(JSContext* cx, const char* name,
    132                                           int32_t min, int32_t max, T num) {
    133  if (min <= num && num <= max) {
    134    return true;
    135  }
    136  ReportInvalidDateValue(cx, name, min, max, num);
    137  return false;
    138 }
    139 
    140 /**
    141 * IsValidISODate ( year, month, day )
    142 */
    143 template <typename T>
    144 static bool ThrowIfInvalidISODate(JSContext* cx, T year, T month, T day) {
    145  static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
    146 
    147  // Step 1.
    148  MOZ_ASSERT(IsInteger(year));
    149  MOZ_ASSERT(IsInteger(month));
    150  MOZ_ASSERT(IsInteger(day));
    151 
    152  if constexpr (std::is_same_v<T, double>) {
    153    if (!ThrowIfInvalidDateValue(cx, "year", INT32_MIN, INT32_MAX, year)) {
    154      return false;
    155    }
    156  }
    157 
    158  // Step 2.
    159  if (!ThrowIfInvalidDateValue(cx, "month", 1, 12, month)) {
    160    return false;
    161  }
    162 
    163  // Step 3.
    164  int32_t daysInMonth =
    165      js::temporal::ISODaysInMonth(int32_t(year), int32_t(month));
    166 
    167  // Steps 4-5.
    168  return ThrowIfInvalidDateValue(cx, "day", 1, daysInMonth, day);
    169 }
    170 
    171 /**
    172 * IsValidISODate ( year, month, day )
    173 */
    174 bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, const ISODate& date) {
    175  const auto& [year, month, day] = date;
    176  return ::ThrowIfInvalidISODate(cx, year, month, day);
    177 }
    178 
    179 /**
    180 * IsValidISODate ( year, month, day )
    181 */
    182 bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, double year,
    183                                         double month, double day) {
    184  return ::ThrowIfInvalidISODate(cx, year, month, day);
    185 }
    186 
    187 /**
    188 * CreateTemporalDate ( isoDate, calendar [ , newTarget ] )
    189 */
    190 static PlainDateObject* CreateTemporalDate(JSContext* cx, const CallArgs& args,
    191                                           const ISODate& isoDate,
    192                                           Handle<CalendarValue> calendar) {
    193  MOZ_ASSERT(IsValidISODate(isoDate));
    194 
    195  // Step 1.
    196  if (!ISODateWithinLimits(isoDate)) {
    197    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    198                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
    199    return nullptr;
    200  }
    201 
    202  // Steps 2-3.
    203  Rooted<JSObject*> proto(cx);
    204  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainDate,
    205                                          &proto)) {
    206    return nullptr;
    207  }
    208 
    209  auto* object = NewObjectWithClassProto<PlainDateObject>(cx, proto);
    210  if (!object) {
    211    return nullptr;
    212  }
    213 
    214  // Step 4.
    215  auto packedDate = PackedDate::pack(isoDate);
    216  object->initFixedSlot(PlainDateObject::PACKED_DATE_SLOT,
    217                        PrivateUint32Value(packedDate.value));
    218 
    219  // Step 5.
    220  object->initFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toSlotValue());
    221 
    222  // Step 6.
    223  return object;
    224 }
    225 
    226 /**
    227 * CreateTemporalDate ( isoDate, calendar [ , newTarget ] )
    228 */
    229 PlainDateObject* js::temporal::CreateTemporalDate(
    230    JSContext* cx, const ISODate& isoDate, Handle<CalendarValue> calendar) {
    231  MOZ_ASSERT(IsValidISODate(isoDate));
    232 
    233  // Step 1.
    234  if (!ISODateWithinLimits(isoDate)) {
    235    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    236                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
    237    return nullptr;
    238  }
    239 
    240  // Steps 2-3.
    241  auto* object = NewBuiltinClassInstance<PlainDateObject>(cx);
    242  if (!object) {
    243    return nullptr;
    244  }
    245 
    246  // Step 4.
    247  auto packedDate = PackedDate::pack(isoDate);
    248  object->initFixedSlot(PlainDateObject::PACKED_DATE_SLOT,
    249                        PrivateUint32Value(packedDate.value));
    250 
    251  // Step 5.
    252  object->initFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toSlotValue());
    253 
    254  // Step 6.
    255  return object;
    256 }
    257 
    258 /**
    259 * CreateTemporalDate ( isoDate, calendar [ , newTarget ] )
    260 */
    261 PlainDateObject* js::temporal::CreateTemporalDate(JSContext* cx,
    262                                                  Handle<PlainDate> date) {
    263  MOZ_ASSERT(ISODateWithinLimits(date));
    264  return CreateTemporalDate(cx, date, date.calendar());
    265 }
    266 
    267 /**
    268 * CreateTemporalDate ( isoDate, calendar [ , newTarget ] )
    269 */
    270 bool js::temporal::CreateTemporalDate(JSContext* cx, const ISODate& isoDate,
    271                                      Handle<CalendarValue> calendar,
    272                                      MutableHandle<PlainDate> result) {
    273  MOZ_ASSERT(IsValidISODate(isoDate));
    274 
    275  // Step 1.
    276  if (!ISODateWithinLimits(isoDate)) {
    277    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    278                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
    279    return false;
    280  }
    281 
    282  // Steps 2-6.
    283  result.set(PlainDate{isoDate, calendar});
    284  return true;
    285 }
    286 
    287 struct DateOptions {
    288  TemporalOverflow overflow = TemporalOverflow::Constrain;
    289 };
    290 
    291 /**
    292 * ToTemporalDate ( item [ , options ] )
    293 */
    294 static bool ToTemporalDateOptions(JSContext* cx, Handle<Value> options,
    295                                  DateOptions* result) {
    296  if (options.isUndefined()) {
    297    *result = {};
    298    return true;
    299  }
    300 
    301  // NOTE: |options| are only passed from `Temporal.PlainDate.from`.
    302 
    303  Rooted<JSObject*> resolvedOptions(
    304      cx, RequireObjectArg(cx, "options", "from", options));
    305  if (!resolvedOptions) {
    306    return false;
    307  }
    308 
    309  auto overflow = TemporalOverflow::Constrain;
    310  if (!GetTemporalOverflowOption(cx, resolvedOptions, &overflow)) {
    311    return false;
    312  }
    313 
    314  *result = {overflow};
    315  return true;
    316 }
    317 
    318 /**
    319 * ToTemporalDate ( item [ , options ] )
    320 */
    321 static bool ToTemporalDate(JSContext* cx, Handle<JSObject*> item,
    322                           Handle<Value> options,
    323                           MutableHandle<PlainDate> result) {
    324  // Step 1. (Not applicable in our implementation.)
    325 
    326  // Step 2.a.
    327  if (auto* plainDate = item->maybeUnwrapIf<PlainDateObject>()) {
    328    auto date = plainDate->date();
    329    Rooted<CalendarValue> calendar(cx, plainDate->calendar());
    330    if (!calendar.wrap(cx)) {
    331      return false;
    332    }
    333 
    334    // Steps 2.a.i-ii.
    335    DateOptions ignoredOptions;
    336    if (!ToTemporalDateOptions(cx, options, &ignoredOptions)) {
    337      return false;
    338    }
    339 
    340    // Step 2.a.iii.
    341    result.set(PlainDate{date, calendar});
    342    return true;
    343  }
    344 
    345  // Step 2.b.
    346  if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) {
    347    auto epochNs = zonedDateTime->epochNanoseconds();
    348    Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
    349    Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
    350 
    351    if (!timeZone.wrap(cx)) {
    352      return false;
    353    }
    354    if (!calendar.wrap(cx)) {
    355      return false;
    356    }
    357 
    358    // Steps 2.b.ii.
    359    ISODateTime isoDateTime;
    360    if (!GetISODateTimeFor(cx, timeZone, epochNs, &isoDateTime)) {
    361      return false;
    362    }
    363 
    364    // Steps 2.b.ii-iii.
    365    DateOptions ignoredOptions;
    366    if (!ToTemporalDateOptions(cx, options, &ignoredOptions)) {
    367      return false;
    368    }
    369 
    370    // Step 2.b.iv.
    371    result.set(PlainDate{isoDateTime.date, calendar});
    372    return true;
    373  }
    374 
    375  // Step 2.c.
    376  if (auto* dateTime = item->maybeUnwrapIf<PlainDateTimeObject>()) {
    377    auto date = dateTime->date();
    378    Rooted<CalendarValue> calendar(cx, dateTime->calendar());
    379    if (!calendar.wrap(cx)) {
    380      return false;
    381    }
    382 
    383    // Steps 2.c.i-ii.
    384    DateOptions ignoredOptions;
    385    if (!ToTemporalDateOptions(cx, options, &ignoredOptions)) {
    386      return false;
    387    }
    388 
    389    // Step 2.c.iii.
    390    result.set(PlainDate{date, calendar});
    391    return true;
    392  }
    393 
    394  // Step 2.d.
    395  Rooted<CalendarValue> calendar(cx);
    396  if (!GetTemporalCalendarWithISODefault(cx, item, &calendar)) {
    397    return false;
    398  }
    399 
    400  // Step 2.e.
    401  Rooted<CalendarFields> fields(cx);
    402  if (!PrepareCalendarFields(cx, calendar, item,
    403                             {
    404                                 CalendarField::Year,
    405                                 CalendarField::Month,
    406                                 CalendarField::MonthCode,
    407                                 CalendarField::Day,
    408                             },
    409                             &fields)) {
    410    return false;
    411  }
    412 
    413  // Steps 2.f-g.
    414  DateOptions resolvedOptions;
    415  if (!ToTemporalDateOptions(cx, options, &resolvedOptions)) {
    416    return false;
    417  }
    418  auto [overflow] = resolvedOptions;
    419 
    420  // Steps 2.h-i.
    421  return CalendarDateFromFields(cx, calendar, fields, overflow, result);
    422 }
    423 
    424 /**
    425 * ToTemporalDate ( item [ , options ] )
    426 */
    427 static bool ToTemporalDate(JSContext* cx, Handle<Value> item,
    428                           Handle<Value> options,
    429                           MutableHandle<PlainDate> result) {
    430  // Step 1. (Not applicable in our implementation.)
    431 
    432  // Step 2.
    433  if (item.isObject()) {
    434    Rooted<JSObject*> itemObj(cx, &item.toObject());
    435    return ToTemporalDate(cx, itemObj, options, result);
    436  }
    437 
    438  // Step 3.
    439  if (!item.isString()) {
    440    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
    441                     nullptr, "not a string");
    442    return false;
    443  }
    444  Rooted<JSString*> string(cx, item.toString());
    445 
    446  // Step 4.
    447  ISODateTime dateTime;
    448  Rooted<JSString*> calendarString(cx);
    449  if (!ParseTemporalDateTimeString(cx, string, &dateTime, &calendarString)) {
    450    return false;
    451  }
    452  MOZ_ASSERT(IsValidISODate(dateTime.date));
    453 
    454  // Steps 5-7.
    455  Rooted<CalendarValue> calendar(cx, CalendarValue(CalendarId::ISO8601));
    456  if (calendarString) {
    457    if (!CanonicalizeCalendar(cx, calendarString, &calendar)) {
    458      return false;
    459    }
    460  }
    461 
    462  // Steps 8-9.
    463  DateOptions ignoredOptions;
    464  if (!ToTemporalDateOptions(cx, options, &ignoredOptions)) {
    465    return false;
    466  }
    467 
    468  // Steps 10-11.
    469  return CreateTemporalDate(cx, dateTime.date, calendar, result);
    470 }
    471 
    472 /**
    473 * ToTemporalDate ( item [ , options ] )
    474 */
    475 static bool ToTemporalDate(JSContext* cx, Handle<Value> item,
    476                           MutableHandle<PlainDate> result) {
    477  return ToTemporalDate(cx, item, UndefinedHandleValue, result);
    478 }
    479 
    480 static bool IsValidISODateEpochMilliseconds(int64_t epochMilliseconds) {
    481  // Epoch nanoseconds limits, adjusted to the range supported by ISODate.
    482  constexpr auto oneDay = EpochDuration::fromDays(1);
    483  constexpr auto min = EpochNanoseconds::min() - oneDay;
    484  constexpr auto max = EpochNanoseconds::max() + oneDay;
    485 
    486  // NB: Minimum limit is inclusive, whereas maximim limit is exclusive.
    487  auto epochNs = EpochNanoseconds::fromMilliseconds(epochMilliseconds);
    488  return min <= epochNs && epochNs < max;
    489 }
    490 
    491 /**
    492 * BalanceISODate ( year, month, day )
    493 */
    494 bool js::temporal::BalanceISODate(JSContext* cx, const ISODate& date,
    495                                  int64_t days, ISODate* result) {
    496  MOZ_ASSERT(IsValidISODate(date));
    497  MOZ_ASSERT(ISODateWithinLimits(date));
    498 
    499  // Step 1.
    500  auto epochDays = MakeDay(date) + mozilla::CheckedInt64{days};
    501 
    502  // Step 2.
    503  auto epochMilliseconds = epochDays * ToMilliseconds(TemporalUnit::Day);
    504  if (!epochMilliseconds.isValid() ||
    505      !IsValidISODateEpochMilliseconds(epochMilliseconds.value())) {
    506    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    507                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
    508    return false;
    509  }
    510 
    511  // Step 3.
    512  auto [year, month, day] = ToYearMonthDay(epochMilliseconds.value());
    513 
    514  *result = ISODate{year, month + 1, day};
    515  MOZ_ASSERT(IsValidISODate(*result));
    516  MOZ_ASSERT(ISODateWithinLimits(*result));
    517 
    518  return true;
    519 }
    520 
    521 /**
    522 * BalanceISODate ( year, month, day )
    523 */
    524 ISODate js::temporal::BalanceISODate(const ISODate& date, int32_t days) {
    525  MOZ_ASSERT(IsValidISODate(date));
    526  MOZ_ASSERT(ISODateWithinLimits(date));
    527  MOZ_ASSERT(std::abs(days) <= 400'000'000, "days limit for ToYearMonthDay");
    528 
    529  // Step 1.
    530  int32_t epochDays = MakeDay(date) + days;
    531 
    532  // Step 2.
    533  int64_t epochMilliseconds = epochDays * ToMilliseconds(TemporalUnit::Day);
    534 
    535  // Step 3.
    536  auto [year, month, day] = ToYearMonthDay(epochMilliseconds);
    537 
    538  // NB: The returned date is possibly outside the valid limits!
    539  auto result = ISODate{year, month + 1, day};
    540  MOZ_ASSERT(IsValidISODate(result));
    541 
    542  return result;
    543 }
    544 
    545 /**
    546 * CompareISODate ( y1, m1, d1, y2, m2, d2 )
    547 */
    548 int32_t js::temporal::CompareISODate(const ISODate& one, const ISODate& two) {
    549  // Steps 1-2.
    550  if (one.year != two.year) {
    551    return one.year < two.year ? -1 : 1;
    552  }
    553 
    554  // Steps 3-4.
    555  if (one.month != two.month) {
    556    return one.month < two.month ? -1 : 1;
    557  }
    558 
    559  // Steps 5-6.
    560  if (one.day != two.day) {
    561    return one.day < two.day ? -1 : 1;
    562  }
    563 
    564  // Step 7.
    565  return 0;
    566 }
    567 
    568 /**
    569 * DifferenceTemporalPlainDate ( operation, temporalDate, other, options )
    570 */
    571 static bool DifferenceTemporalPlainDate(JSContext* cx,
    572                                        TemporalDifference operation,
    573                                        const CallArgs& args) {
    574  Rooted<PlainDate> temporalDate(
    575      cx, &args.thisv().toObject().as<PlainDateObject>());
    576 
    577  // Step 1.
    578  Rooted<PlainDate> other(cx);
    579  if (!ToTemporalDate(cx, args.get(0), &other)) {
    580    return false;
    581  }
    582 
    583  // Step 2.
    584  if (!CalendarEquals(temporalDate.calendar(), other.calendar())) {
    585    JS_ReportErrorNumberASCII(
    586        cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
    587        CalendarIdentifier(temporalDate.calendar()).data(),
    588        CalendarIdentifier(other.calendar()).data());
    589    return false;
    590  }
    591 
    592  // Steps 3-4.
    593  DifferenceSettings settings;
    594  if (args.hasDefined(1)) {
    595    // Step 3.
    596    Rooted<JSObject*> options(
    597        cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
    598    if (!options) {
    599      return false;
    600    }
    601 
    602    // Step 4.
    603    if (!GetDifferenceSettings(cx, operation, options, TemporalUnitGroup::Date,
    604                               TemporalUnit::Day, TemporalUnit::Day,
    605                               &settings)) {
    606      return false;
    607    }
    608  } else {
    609    // Steps 3-4.
    610    settings = {
    611        TemporalUnit::Day,
    612        TemporalUnit::Day,
    613        TemporalRoundingMode::Trunc,
    614        Increment{1},
    615    };
    616  }
    617 
    618  // Step 5.
    619  if (temporalDate.date() == other.date()) {
    620    auto* obj = CreateTemporalDuration(cx, {});
    621    if (!obj) {
    622      return false;
    623    }
    624 
    625    args.rval().setObject(*obj);
    626    return true;
    627  }
    628 
    629  // Step 6.
    630  DateDuration dateDifference;
    631  if (!CalendarDateUntil(cx, temporalDate.calendar(), temporalDate.date(),
    632                         other.date(), settings.largestUnit, &dateDifference)) {
    633    return false;
    634  }
    635 
    636  // Step 7.
    637  auto duration = InternalDuration{dateDifference, {}};
    638 
    639  // Step 8.
    640  if (settings.smallestUnit != TemporalUnit::Day ||
    641      settings.roundingIncrement != Increment{1}) {
    642    // Step 8.a.
    643    auto isoDateTime = ISODateTime{temporalDate.date(), {}};
    644 
    645    // Step 8.b.
    646    auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime);
    647 
    648    // Step 8.c.
    649    auto isoDateTimeOther = ISODateTime{other.date(), {}};
    650 
    651    // Step 8.d.
    652    auto destEpochNs = GetUTCEpochNanoseconds(isoDateTimeOther);
    653 
    654    // Step 8.e.
    655    Rooted<TimeZoneValue> timeZone(cx, TimeZoneValue{});
    656    if (!RoundRelativeDuration(cx, duration, originEpochNs, destEpochNs,
    657                               isoDateTime, timeZone, temporalDate.calendar(),
    658                               settings.largestUnit, settings.roundingIncrement,
    659                               settings.smallestUnit, settings.roundingMode,
    660                               &duration)) {
    661      return false;
    662    }
    663  }
    664  MOZ_ASSERT(IsValidDuration(duration));
    665  MOZ_ASSERT(duration.time == TimeDuration{});
    666 
    667  // Step 9. (Inlined TemporalDurationFromInternal)
    668  auto result = duration.date.toDuration();
    669 
    670  // Step 10.
    671  if (operation == TemporalDifference::Since) {
    672    result = result.negate();
    673  }
    674  MOZ_ASSERT(IsValidDuration(result));
    675 
    676  // Step 11.
    677  auto* obj = CreateTemporalDuration(cx, result);
    678  if (!obj) {
    679    return false;
    680  }
    681 
    682  args.rval().setObject(*obj);
    683  return true;
    684 }
    685 
    686 /**
    687 * AddDurationToDate ( operation, temporalDate, temporalDurationLike, options )
    688 */
    689 static bool AddDurationToDate(JSContext* cx, TemporalAddDuration operation,
    690                              const CallArgs& args) {
    691  Rooted<PlainDate> temporalDate(
    692      cx, &args.thisv().toObject().as<PlainDateObject>());
    693 
    694  // Step 1.
    695  auto calendar = temporalDate.calendar();
    696 
    697  // Step 2.
    698  Duration duration;
    699  if (!ToTemporalDuration(cx, args.get(0), &duration)) {
    700    return false;
    701  }
    702 
    703  // Step 3.
    704  if (operation == TemporalAddDuration::Subtract) {
    705    duration = duration.negate();
    706  }
    707 
    708  // Step 4.
    709  auto dateDuration = ToDateDurationRecordWithoutTime(duration);
    710 
    711  // Steps 6-7.
    712  auto overflow = TemporalOverflow::Constrain;
    713  if (args.hasDefined(1)) {
    714    // Step 6.
    715    Rooted<JSObject*> options(
    716        cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
    717    if (!options) {
    718      return false;
    719    }
    720 
    721    // Step 7.
    722    if (!GetTemporalOverflowOption(cx, options, &overflow)) {
    723      return false;
    724    }
    725  }
    726 
    727  // Step 8.
    728  ISODate result;
    729  if (!CalendarDateAdd(cx, calendar, temporalDate.date(), dateDuration,
    730                       overflow, &result)) {
    731    return false;
    732  }
    733 
    734  // Step 9.
    735  auto* obj = CreateTemporalDate(cx, result, calendar);
    736  if (!obj) {
    737    return false;
    738  }
    739 
    740  args.rval().setObject(*obj);
    741  return true;
    742 }
    743 
    744 /**
    745 * Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendar ] )
    746 */
    747 static bool PlainDateConstructor(JSContext* cx, unsigned argc, Value* vp) {
    748  CallArgs args = CallArgsFromVp(argc, vp);
    749 
    750  // Step 1.
    751  if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainDate")) {
    752    return false;
    753  }
    754 
    755  // Step 2.
    756  double isoYear;
    757  if (!ToIntegerWithTruncation(cx, args.get(0), "year", &isoYear)) {
    758    return false;
    759  }
    760 
    761  // Step 3.
    762  double isoMonth;
    763  if (!ToIntegerWithTruncation(cx, args.get(1), "month", &isoMonth)) {
    764    return false;
    765  }
    766 
    767  // Step 4.
    768  double isoDay;
    769  if (!ToIntegerWithTruncation(cx, args.get(2), "day", &isoDay)) {
    770    return false;
    771  }
    772 
    773  // Steps 5-7.
    774  Rooted<CalendarValue> calendar(cx, CalendarValue(CalendarId::ISO8601));
    775  if (args.hasDefined(3)) {
    776    // Step 6.
    777    if (!args[3].isString()) {
    778      ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, args[3],
    779                       nullptr, "not a string");
    780      return false;
    781    }
    782 
    783    // Step 7.
    784    Rooted<JSString*> calendarString(cx, args[3].toString());
    785    if (!CanonicalizeCalendar(cx, calendarString, &calendar)) {
    786      return false;
    787    }
    788  }
    789 
    790  // Step 8.
    791  if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) {
    792    return false;
    793  }
    794 
    795  // Step 9.
    796  auto isoDate = ISODate{int32_t(isoYear), int32_t(isoMonth), int32_t(isoDay)};
    797 
    798  // Step 10.
    799  auto* temporalDate = CreateTemporalDate(cx, args, isoDate, calendar);
    800  if (!temporalDate) {
    801    return false;
    802  }
    803 
    804  args.rval().setObject(*temporalDate);
    805  return true;
    806 }
    807 
    808 /**
    809 * Temporal.PlainDate.from ( item [ , options ] )
    810 */
    811 static bool PlainDate_from(JSContext* cx, unsigned argc, Value* vp) {
    812  CallArgs args = CallArgsFromVp(argc, vp);
    813 
    814  // Step 1.
    815  Rooted<PlainDate> date(cx);
    816  if (!ToTemporalDate(cx, args.get(0), args.get(1), &date)) {
    817    return false;
    818  }
    819 
    820  auto* result = CreateTemporalDate(cx, date);
    821  if (!result) {
    822    return false;
    823  }
    824 
    825  args.rval().setObject(*result);
    826  return true;
    827 }
    828 
    829 /**
    830 * Temporal.PlainDate.compare ( one, two )
    831 */
    832 static bool PlainDate_compare(JSContext* cx, unsigned argc, Value* vp) {
    833  CallArgs args = CallArgsFromVp(argc, vp);
    834 
    835  // Step 1.
    836  Rooted<PlainDate> one(cx);
    837  if (!ToTemporalDate(cx, args.get(0), &one)) {
    838    return false;
    839  }
    840 
    841  // Step 2.
    842  Rooted<PlainDate> two(cx);
    843  if (!ToTemporalDate(cx, args.get(1), &two)) {
    844    return false;
    845  }
    846 
    847  // Step 3.
    848  args.rval().setInt32(CompareISODate(one, two));
    849  return true;
    850 }
    851 
    852 /**
    853 * get Temporal.PlainDate.prototype.calendarId
    854 */
    855 static bool PlainDate_calendarId(JSContext* cx, const CallArgs& args) {
    856  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    857 
    858  // Step 3.
    859  auto* str =
    860      NewStringCopy<CanGC>(cx, CalendarIdentifier(temporalDate->calendar()));
    861  if (!str) {
    862    return false;
    863  }
    864 
    865  args.rval().setString(str);
    866  return true;
    867 }
    868 
    869 /**
    870 * get Temporal.PlainDate.prototype.calendarId
    871 */
    872 static bool PlainDate_calendarId(JSContext* cx, unsigned argc, Value* vp) {
    873  // Steps 1-2.
    874  CallArgs args = CallArgsFromVp(argc, vp);
    875  return CallNonGenericMethod<IsPlainDate, PlainDate_calendarId>(cx, args);
    876 }
    877 
    878 /**
    879 * get Temporal.PlainDate.prototype.era
    880 */
    881 static bool PlainDate_era(JSContext* cx, const CallArgs& args) {
    882  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    883  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
    884 
    885  // Step 3.
    886  return CalendarEra(cx, calendar, temporalDate->date(), args.rval());
    887 }
    888 
    889 /**
    890 * get Temporal.PlainDate.prototype.era
    891 */
    892 static bool PlainDate_era(JSContext* cx, unsigned argc, Value* vp) {
    893  // Steps 1-2.
    894  CallArgs args = CallArgsFromVp(argc, vp);
    895  return CallNonGenericMethod<IsPlainDate, PlainDate_era>(cx, args);
    896 }
    897 
    898 /**
    899 * get Temporal.PlainDate.prototype.eraYear
    900 */
    901 static bool PlainDate_eraYear(JSContext* cx, const CallArgs& args) {
    902  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    903  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
    904 
    905  // Steps 3-5.
    906  return CalendarEraYear(cx, calendar, temporalDate->date(), args.rval());
    907 }
    908 
    909 /**
    910 * get Temporal.PlainDate.prototype.eraYear
    911 */
    912 static bool PlainDate_eraYear(JSContext* cx, unsigned argc, Value* vp) {
    913  // Steps 1-2.
    914  CallArgs args = CallArgsFromVp(argc, vp);
    915  return CallNonGenericMethod<IsPlainDate, PlainDate_eraYear>(cx, args);
    916 }
    917 
    918 /**
    919 * get Temporal.PlainDate.prototype.year
    920 */
    921 static bool PlainDate_year(JSContext* cx, const CallArgs& args) {
    922  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    923  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
    924 
    925  // Step 3.
    926  return CalendarYear(cx, calendar, temporalDate->date(), args.rval());
    927 }
    928 
    929 /**
    930 * get Temporal.PlainDate.prototype.year
    931 */
    932 static bool PlainDate_year(JSContext* cx, unsigned argc, Value* vp) {
    933  // Steps 1-2.
    934  CallArgs args = CallArgsFromVp(argc, vp);
    935  return CallNonGenericMethod<IsPlainDate, PlainDate_year>(cx, args);
    936 }
    937 
    938 /**
    939 * get Temporal.PlainDate.prototype.month
    940 */
    941 static bool PlainDate_month(JSContext* cx, const CallArgs& args) {
    942  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    943  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
    944 
    945  // Step 3.
    946  return CalendarMonth(cx, calendar, temporalDate->date(), args.rval());
    947 }
    948 
    949 /**
    950 * get Temporal.PlainDate.prototype.month
    951 */
    952 static bool PlainDate_month(JSContext* cx, unsigned argc, Value* vp) {
    953  // Steps 1-2.
    954  CallArgs args = CallArgsFromVp(argc, vp);
    955  return CallNonGenericMethod<IsPlainDate, PlainDate_month>(cx, args);
    956 }
    957 
    958 /**
    959 * get Temporal.PlainDate.prototype.monthCode
    960 */
    961 static bool PlainDate_monthCode(JSContext* cx, const CallArgs& args) {
    962  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    963  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
    964 
    965  // Step 3.
    966  return CalendarMonthCode(cx, calendar, temporalDate->date(), args.rval());
    967 }
    968 
    969 /**
    970 * get Temporal.PlainDate.prototype.monthCode
    971 */
    972 static bool PlainDate_monthCode(JSContext* cx, unsigned argc, Value* vp) {
    973  // Steps 1-2.
    974  CallArgs args = CallArgsFromVp(argc, vp);
    975  return CallNonGenericMethod<IsPlainDate, PlainDate_monthCode>(cx, args);
    976 }
    977 
    978 /**
    979 * get Temporal.PlainDate.prototype.day
    980 */
    981 static bool PlainDate_day(JSContext* cx, const CallArgs& args) {
    982  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
    983  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
    984 
    985  // Step 3.
    986  return CalendarDay(cx, calendar, temporalDate->date(), args.rval());
    987 }
    988 
    989 /**
    990 * get Temporal.PlainDate.prototype.day
    991 */
    992 static bool PlainDate_day(JSContext* cx, unsigned argc, Value* vp) {
    993  // Steps 1-2.
    994  CallArgs args = CallArgsFromVp(argc, vp);
    995  return CallNonGenericMethod<IsPlainDate, PlainDate_day>(cx, args);
    996 }
    997 
    998 /**
    999 * get Temporal.PlainDate.prototype.dayOfWeek
   1000 */
   1001 static bool PlainDate_dayOfWeek(JSContext* cx, const CallArgs& args) {
   1002  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1003  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1004 
   1005  // Step 3.
   1006  return CalendarDayOfWeek(cx, calendar, temporalDate->date(), args.rval());
   1007 }
   1008 
   1009 /**
   1010 * get Temporal.PlainDate.prototype.dayOfWeek
   1011 */
   1012 static bool PlainDate_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
   1013  // Steps 1-2.
   1014  CallArgs args = CallArgsFromVp(argc, vp);
   1015  return CallNonGenericMethod<IsPlainDate, PlainDate_dayOfWeek>(cx, args);
   1016 }
   1017 
   1018 /**
   1019 * get Temporal.PlainDate.prototype.dayOfYear
   1020 */
   1021 static bool PlainDate_dayOfYear(JSContext* cx, const CallArgs& args) {
   1022  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1023  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1024 
   1025  // Step 3.
   1026  return CalendarDayOfYear(cx, calendar, temporalDate->date(), args.rval());
   1027 }
   1028 
   1029 /**
   1030 * get Temporal.PlainDate.prototype.dayOfYear
   1031 */
   1032 static bool PlainDate_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
   1033  // Steps 1-2.
   1034  CallArgs args = CallArgsFromVp(argc, vp);
   1035  return CallNonGenericMethod<IsPlainDate, PlainDate_dayOfYear>(cx, args);
   1036 }
   1037 
   1038 /**
   1039 * get Temporal.PlainDate.prototype.weekOfYear
   1040 */
   1041 static bool PlainDate_weekOfYear(JSContext* cx, const CallArgs& args) {
   1042  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1043  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1044 
   1045  // Steps 3-5.
   1046  return CalendarWeekOfYear(cx, calendar, temporalDate->date(), args.rval());
   1047 }
   1048 
   1049 /**
   1050 * get Temporal.PlainDate.prototype.weekOfYear
   1051 */
   1052 static bool PlainDate_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
   1053  // Steps 1-2.
   1054  CallArgs args = CallArgsFromVp(argc, vp);
   1055  return CallNonGenericMethod<IsPlainDate, PlainDate_weekOfYear>(cx, args);
   1056 }
   1057 
   1058 /**
   1059 * get Temporal.PlainDate.prototype.yearOfWeek
   1060 */
   1061 static bool PlainDate_yearOfWeek(JSContext* cx, const CallArgs& args) {
   1062  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1063  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1064 
   1065  // Steps 3-5.
   1066  return CalendarYearOfWeek(cx, calendar, temporalDate->date(), args.rval());
   1067 }
   1068 
   1069 /**
   1070 * get Temporal.PlainDate.prototype.yearOfWeek
   1071 */
   1072 static bool PlainDate_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
   1073  // Steps 1-2.
   1074  CallArgs args = CallArgsFromVp(argc, vp);
   1075  return CallNonGenericMethod<IsPlainDate, PlainDate_yearOfWeek>(cx, args);
   1076 }
   1077 
   1078 /**
   1079 * get Temporal.PlainDate.prototype.daysInWeek
   1080 */
   1081 static bool PlainDate_daysInWeek(JSContext* cx, const CallArgs& args) {
   1082  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1083  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1084 
   1085  // Step 3.
   1086  return CalendarDaysInWeek(cx, calendar, temporalDate->date(), args.rval());
   1087 }
   1088 
   1089 /**
   1090 * get Temporal.PlainDate.prototype.daysInWeek
   1091 */
   1092 static bool PlainDate_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
   1093  // Steps 1-2.
   1094  CallArgs args = CallArgsFromVp(argc, vp);
   1095  return CallNonGenericMethod<IsPlainDate, PlainDate_daysInWeek>(cx, args);
   1096 }
   1097 
   1098 /**
   1099 * get Temporal.PlainDate.prototype.daysInMonth
   1100 */
   1101 static bool PlainDate_daysInMonth(JSContext* cx, const CallArgs& args) {
   1102  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1103  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1104 
   1105  // Step 3.
   1106  return CalendarDaysInMonth(cx, calendar, temporalDate->date(), args.rval());
   1107 }
   1108 
   1109 /**
   1110 * get Temporal.PlainDate.prototype.daysInMonth
   1111 */
   1112 static bool PlainDate_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
   1113  // Steps 1-2.
   1114  CallArgs args = CallArgsFromVp(argc, vp);
   1115  return CallNonGenericMethod<IsPlainDate, PlainDate_daysInMonth>(cx, args);
   1116 }
   1117 
   1118 /**
   1119 * get Temporal.PlainDate.prototype.daysInYear
   1120 */
   1121 static bool PlainDate_daysInYear(JSContext* cx, const CallArgs& args) {
   1122  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1123  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1124 
   1125  // Step 3.
   1126  return CalendarDaysInYear(cx, calendar, temporalDate->date(), args.rval());
   1127 }
   1128 
   1129 /**
   1130 * get Temporal.PlainDate.prototype.daysInYear
   1131 */
   1132 static bool PlainDate_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
   1133  // Steps 1-2.
   1134  CallArgs args = CallArgsFromVp(argc, vp);
   1135  return CallNonGenericMethod<IsPlainDate, PlainDate_daysInYear>(cx, args);
   1136 }
   1137 
   1138 /**
   1139 * get Temporal.PlainDate.prototype.monthsInYear
   1140 */
   1141 static bool PlainDate_monthsInYear(JSContext* cx, const CallArgs& args) {
   1142  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1143  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1144 
   1145  // Step 3.
   1146  return CalendarMonthsInYear(cx, calendar, temporalDate->date(), args.rval());
   1147 }
   1148 
   1149 /**
   1150 * get Temporal.PlainDate.prototype.monthsInYear
   1151 */
   1152 static bool PlainDate_monthsInYear(JSContext* cx, unsigned argc, Value* vp) {
   1153  // Steps 1-2.
   1154  CallArgs args = CallArgsFromVp(argc, vp);
   1155  return CallNonGenericMethod<IsPlainDate, PlainDate_monthsInYear>(cx, args);
   1156 }
   1157 
   1158 /**
   1159 * get Temporal.PlainDate.prototype.inLeapYear
   1160 */
   1161 static bool PlainDate_inLeapYear(JSContext* cx, const CallArgs& args) {
   1162  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1163  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1164 
   1165  // Step 3.
   1166  return CalendarInLeapYear(cx, calendar, temporalDate->date(), args.rval());
   1167 }
   1168 
   1169 /**
   1170 * get Temporal.PlainDate.prototype.inLeapYear
   1171 */
   1172 static bool PlainDate_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
   1173  // Steps 1-2.
   1174  CallArgs args = CallArgsFromVp(argc, vp);
   1175  return CallNonGenericMethod<IsPlainDate, PlainDate_inLeapYear>(cx, args);
   1176 }
   1177 
   1178 /**
   1179 * Temporal.PlainDate.prototype.toPlainYearMonth ( )
   1180 */
   1181 static bool PlainDate_toPlainYearMonth(JSContext* cx, const CallArgs& args) {
   1182  Rooted<PlainDate> temporalDate(
   1183      cx, &args.thisv().toObject().as<PlainDateObject>());
   1184 
   1185  // Step 3.
   1186  auto calendar = temporalDate.calendar();
   1187 
   1188  // Step 4.
   1189  Rooted<CalendarFields> fields(cx);
   1190  if (!ISODateToFields(cx, temporalDate, &fields)) {
   1191    return false;
   1192  }
   1193 
   1194  // Step 5.
   1195  Rooted<PlainYearMonth> result(cx);
   1196  if (!CalendarYearMonthFromFields(cx, calendar, fields,
   1197                                   TemporalOverflow::Constrain, &result)) {
   1198    return false;
   1199  }
   1200 
   1201  // Steps 6-7.
   1202  auto* obj = CreateTemporalYearMonth(cx, result);
   1203  if (!obj) {
   1204    return false;
   1205  }
   1206 
   1207  args.rval().setObject(*obj);
   1208  return true;
   1209 }
   1210 
   1211 /**
   1212 * Temporal.PlainDate.prototype.toPlainYearMonth ( )
   1213 */
   1214 static bool PlainDate_toPlainYearMonth(JSContext* cx, unsigned argc,
   1215                                       Value* vp) {
   1216  // Steps 1-2.
   1217  CallArgs args = CallArgsFromVp(argc, vp);
   1218  return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainYearMonth>(cx,
   1219                                                                       args);
   1220 }
   1221 
   1222 /**
   1223 * Temporal.PlainDate.prototype.toPlainMonthDay ( )
   1224 */
   1225 static bool PlainDate_toPlainMonthDay(JSContext* cx, const CallArgs& args) {
   1226  Rooted<PlainDate> temporalDate(
   1227      cx, &args.thisv().toObject().as<PlainDateObject>());
   1228 
   1229  // Step 3.
   1230  auto calendar = temporalDate.calendar();
   1231 
   1232  // Step 4.
   1233  Rooted<CalendarFields> fields(cx);
   1234  if (!ISODateToFields(cx, temporalDate, &fields)) {
   1235    return false;
   1236  }
   1237 
   1238  // Step 5.
   1239  Rooted<PlainMonthDay> result(cx);
   1240  if (!CalendarMonthDayFromFields(cx, calendar, fields,
   1241                                  TemporalOverflow::Constrain, &result)) {
   1242    return false;
   1243  }
   1244 
   1245  // Steps 6-7.
   1246  auto* obj = CreateTemporalMonthDay(cx, result);
   1247  if (!obj) {
   1248    return false;
   1249  }
   1250 
   1251  args.rval().setObject(*obj);
   1252  return true;
   1253 }
   1254 
   1255 /**
   1256 * Temporal.PlainDate.prototype.toPlainMonthDay ( )
   1257 */
   1258 static bool PlainDate_toPlainMonthDay(JSContext* cx, unsigned argc, Value* vp) {
   1259  // Steps 1-2.
   1260  CallArgs args = CallArgsFromVp(argc, vp);
   1261  return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainMonthDay>(cx, args);
   1262 }
   1263 
   1264 /**
   1265 * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
   1266 */
   1267 static bool PlainDate_toPlainDateTime(JSContext* cx, const CallArgs& args) {
   1268  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1269  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1270 
   1271  // Step 4. (Reordered)
   1272  //
   1273  // Default initialize the time component to all zero.
   1274  auto isoDateTime = ISODateTime{temporalDate->date(), {}};
   1275 
   1276  // Step 3. (Inlined ToTemporalTimeOrMidnight)
   1277  if (args.hasDefined(0)) {
   1278    if (!ToTemporalTime(cx, args[0], &isoDateTime.time)) {
   1279      return false;
   1280    }
   1281  }
   1282 
   1283  // Step 4. (Moved above)
   1284 
   1285  // Step 5.
   1286  auto* obj = CreateTemporalDateTime(cx, isoDateTime, calendar);
   1287  if (!obj) {
   1288    return false;
   1289  }
   1290 
   1291  args.rval().setObject(*obj);
   1292  return true;
   1293 }
   1294 
   1295 /**
   1296 * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
   1297 */
   1298 static bool PlainDate_toPlainDateTime(JSContext* cx, unsigned argc, Value* vp) {
   1299  // Steps 1-2.
   1300  CallArgs args = CallArgsFromVp(argc, vp);
   1301  return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainDateTime>(cx, args);
   1302 }
   1303 
   1304 /**
   1305 * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
   1306 */
   1307 static bool PlainDate_add(JSContext* cx, const CallArgs& args) {
   1308  // Step 3.
   1309  return AddDurationToDate(cx, TemporalAddDuration::Add, args);
   1310 }
   1311 
   1312 /**
   1313 * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
   1314 */
   1315 static bool PlainDate_add(JSContext* cx, unsigned argc, Value* vp) {
   1316  // Steps 1-2.
   1317  CallArgs args = CallArgsFromVp(argc, vp);
   1318  return CallNonGenericMethod<IsPlainDate, PlainDate_add>(cx, args);
   1319 }
   1320 
   1321 /**
   1322 * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
   1323 */
   1324 static bool PlainDate_subtract(JSContext* cx, const CallArgs& args) {
   1325  // Step 3.
   1326  return AddDurationToDate(cx, TemporalAddDuration::Subtract, args);
   1327 }
   1328 
   1329 /**
   1330 * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
   1331 */
   1332 static bool PlainDate_subtract(JSContext* cx, unsigned argc, Value* vp) {
   1333  // Steps 1-2.
   1334  CallArgs args = CallArgsFromVp(argc, vp);
   1335  return CallNonGenericMethod<IsPlainDate, PlainDate_subtract>(cx, args);
   1336 }
   1337 
   1338 /**
   1339 * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
   1340 */
   1341 static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
   1342  Rooted<PlainDate> temporalDate(
   1343      cx, &args.thisv().toObject().as<PlainDateObject>());
   1344 
   1345  // Step 3.
   1346  Rooted<JSObject*> temporalDateLike(
   1347      cx, RequireObjectArg(cx, "temporalDateLike", "with", args.get(0)));
   1348  if (!temporalDateLike) {
   1349    return false;
   1350  }
   1351  if (!ThrowIfTemporalLikeObject(cx, temporalDateLike)) {
   1352    return false;
   1353  }
   1354 
   1355  // Step 4.
   1356  auto calendar = temporalDate.calendar();
   1357 
   1358  // Step 5.
   1359  Rooted<CalendarFields> fields(cx);
   1360  if (!ISODateToFields(cx, temporalDate, &fields)) {
   1361    return false;
   1362  }
   1363 
   1364  // Step 6.
   1365  Rooted<CalendarFields> partialDate(cx);
   1366  if (!PreparePartialCalendarFields(cx, calendar, temporalDateLike,
   1367                                    {
   1368                                        CalendarField::Year,
   1369                                        CalendarField::Month,
   1370                                        CalendarField::MonthCode,
   1371                                        CalendarField::Day,
   1372                                    },
   1373                                    &partialDate)) {
   1374    return false;
   1375  }
   1376  MOZ_ASSERT(!partialDate.keys().isEmpty());
   1377 
   1378  // Step 7.
   1379  fields = CalendarMergeFields(calendar, fields, partialDate);
   1380 
   1381  // Steps 8-9.
   1382  auto overflow = TemporalOverflow::Constrain;
   1383  if (args.hasDefined(1)) {
   1384    // Step 8.
   1385    Rooted<JSObject*> options(cx,
   1386                              RequireObjectArg(cx, "options", "with", args[1]));
   1387    if (!options) {
   1388      return false;
   1389    }
   1390 
   1391    // Step 9.
   1392    if (!GetTemporalOverflowOption(cx, options, &overflow)) {
   1393      return false;
   1394    }
   1395  }
   1396 
   1397  // Step 10.
   1398  Rooted<PlainDate> date(cx);
   1399  if (!CalendarDateFromFields(cx, calendar, fields, overflow, &date)) {
   1400    return false;
   1401  }
   1402 
   1403  // Step 11.
   1404  auto* result = CreateTemporalDate(cx, date);
   1405  if (!result) {
   1406    return false;
   1407  }
   1408 
   1409  args.rval().setObject(*result);
   1410  return true;
   1411 }
   1412 
   1413 /**
   1414 * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
   1415 */
   1416 static bool PlainDate_with(JSContext* cx, unsigned argc, Value* vp) {
   1417  // Steps 1-2.
   1418  CallArgs args = CallArgsFromVp(argc, vp);
   1419  return CallNonGenericMethod<IsPlainDate, PlainDate_with>(cx, args);
   1420 }
   1421 
   1422 /**
   1423 * Temporal.PlainDate.prototype.withCalendar ( calendar )
   1424 */
   1425 static bool PlainDate_withCalendar(JSContext* cx, const CallArgs& args) {
   1426  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1427  auto date = temporalDate->date();
   1428 
   1429  // Step 3.
   1430  Rooted<CalendarValue> calendar(cx);
   1431  if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
   1432    return false;
   1433  }
   1434 
   1435  // Step 4.
   1436  auto* result = CreateTemporalDate(cx, date, calendar);
   1437  if (!result) {
   1438    return false;
   1439  }
   1440 
   1441  args.rval().setObject(*result);
   1442  return true;
   1443 }
   1444 
   1445 /**
   1446 * Temporal.PlainDate.prototype.withCalendar ( calendar )
   1447 */
   1448 static bool PlainDate_withCalendar(JSContext* cx, unsigned argc, Value* vp) {
   1449  // Steps 1-2.
   1450  CallArgs args = CallArgsFromVp(argc, vp);
   1451  return CallNonGenericMethod<IsPlainDate, PlainDate_withCalendar>(cx, args);
   1452 }
   1453 
   1454 /**
   1455 * Temporal.PlainDate.prototype.until ( other [ , options ] )
   1456 */
   1457 static bool PlainDate_until(JSContext* cx, const CallArgs& args) {
   1458  // Step 3.
   1459  return DifferenceTemporalPlainDate(cx, TemporalDifference::Until, args);
   1460 }
   1461 
   1462 /**
   1463 * Temporal.PlainDate.prototype.until ( other [ , options ] )
   1464 */
   1465 static bool PlainDate_until(JSContext* cx, unsigned argc, Value* vp) {
   1466  // Steps 1-2.
   1467  CallArgs args = CallArgsFromVp(argc, vp);
   1468  return CallNonGenericMethod<IsPlainDate, PlainDate_until>(cx, args);
   1469 }
   1470 
   1471 /**
   1472 * Temporal.PlainDate.prototype.since ( other [ , options ] )
   1473 */
   1474 static bool PlainDate_since(JSContext* cx, const CallArgs& args) {
   1475  // Step 3.
   1476  return DifferenceTemporalPlainDate(cx, TemporalDifference::Since, args);
   1477 }
   1478 
   1479 /**
   1480 * Temporal.PlainDate.prototype.since ( other [ , options ] )
   1481 */
   1482 static bool PlainDate_since(JSContext* cx, unsigned argc, Value* vp) {
   1483  // Steps 1-2.
   1484  CallArgs args = CallArgsFromVp(argc, vp);
   1485  return CallNonGenericMethod<IsPlainDate, PlainDate_since>(cx, args);
   1486 }
   1487 
   1488 /**
   1489 * Temporal.PlainDate.prototype.equals ( other )
   1490 */
   1491 static bool PlainDate_equals(JSContext* cx, const CallArgs& args) {
   1492  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1493  auto date = temporalDate->date();
   1494  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1495 
   1496  // Step 3.
   1497  Rooted<PlainDate> other(cx);
   1498  if (!ToTemporalDate(cx, args.get(0), &other)) {
   1499    return false;
   1500  }
   1501 
   1502  // Steps 4-5.
   1503  bool equals =
   1504      date == other.date() && CalendarEquals(calendar, other.calendar());
   1505 
   1506  args.rval().setBoolean(equals);
   1507  return true;
   1508 }
   1509 
   1510 /**
   1511 * Temporal.PlainDate.prototype.equals ( other )
   1512 */
   1513 static bool PlainDate_equals(JSContext* cx, unsigned argc, Value* vp) {
   1514  // Steps 1-2.
   1515  CallArgs args = CallArgsFromVp(argc, vp);
   1516  return CallNonGenericMethod<IsPlainDate, PlainDate_equals>(cx, args);
   1517 }
   1518 
   1519 /**
   1520 * Temporal.PlainDate.prototype.toZonedDateTime ( item )
   1521 *
   1522 * The |item| argument represents either a time zone or an options object. The
   1523 * following cases are supported:
   1524 * - |item| is an options object with `timeZone` and `plainTime` properties.
   1525 * - |item| is a time zone identifier string.
   1526 */
   1527 static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
   1528  auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
   1529  auto date = temporalDate->date();
   1530  Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
   1531 
   1532  // Steps 3-4
   1533  Rooted<TimeZoneValue> timeZone(cx);
   1534  Rooted<Value> temporalTime(cx);
   1535  if (args.get(0).isObject()) {
   1536    Rooted<JSObject*> item(cx, &args[0].toObject());
   1537 
   1538    // Step 3.a.
   1539    Rooted<Value> timeZoneLike(cx);
   1540    if (!GetProperty(cx, item, item, cx->names().timeZone, &timeZoneLike)) {
   1541      return false;
   1542    }
   1543 
   1544    // Steps 3.b-c.
   1545    if (timeZoneLike.isUndefined()) {
   1546      // Step 3.b.i.
   1547      if (!ToTemporalTimeZone(cx, args[0], &timeZone)) {
   1548        return false;
   1549      }
   1550 
   1551      // Step 3.b.ii.
   1552      MOZ_ASSERT(temporalTime.isUndefined());
   1553    } else {
   1554      // Step 3.c.i.
   1555      if (!ToTemporalTimeZone(cx, timeZoneLike, &timeZone)) {
   1556        return false;
   1557      }
   1558 
   1559      // Step 3.c.ii.
   1560      if (!GetProperty(cx, item, item, cx->names().plainTime, &temporalTime)) {
   1561        return false;
   1562      }
   1563    }
   1564  } else {
   1565    // Step 4.a.
   1566    if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
   1567      return false;
   1568    }
   1569 
   1570    // Step 4.b.
   1571    MOZ_ASSERT(temporalTime.isUndefined());
   1572  }
   1573 
   1574  // Steps 5-6.
   1575  EpochNanoseconds epochNs;
   1576  if (temporalTime.isUndefined()) {
   1577    // Step 5.a.
   1578    if (!GetStartOfDay(cx, timeZone, date, &epochNs)) {
   1579      return false;
   1580    }
   1581  } else {
   1582    // Step 6.a.
   1583    Time time;
   1584    if (!ToTemporalTime(cx, temporalTime, &time)) {
   1585      return false;
   1586    }
   1587 
   1588    // Step 6.b.
   1589    auto isoDateTime = ISODateTime{date, time};
   1590 
   1591    // Step 6.c.
   1592    if (!ISODateTimeWithinLimits(isoDateTime)) {
   1593      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1594                                JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
   1595      return false;
   1596    }
   1597 
   1598    // Step 6.d.
   1599    if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime,
   1600                                TemporalDisambiguation::Compatible, &epochNs)) {
   1601      return false;
   1602    }
   1603  }
   1604 
   1605  // Step 7.
   1606  auto* result = CreateTemporalZonedDateTime(cx, epochNs, timeZone, calendar);
   1607  if (!result) {
   1608    return false;
   1609  }
   1610 
   1611  args.rval().setObject(*result);
   1612  return true;
   1613 }
   1614 
   1615 /**
   1616 * Temporal.PlainDate.prototype.toZonedDateTime ( item )
   1617 */
   1618 static bool PlainDate_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
   1619  // Steps 1-2.
   1620  CallArgs args = CallArgsFromVp(argc, vp);
   1621  return CallNonGenericMethod<IsPlainDate, PlainDate_toZonedDateTime>(cx, args);
   1622 }
   1623 
   1624 /**
   1625 * Temporal.PlainDate.prototype.toString ( [ options ] )
   1626 */
   1627 static bool PlainDate_toString(JSContext* cx, const CallArgs& args) {
   1628  Rooted<PlainDateObject*> temporalDate(
   1629      cx, &args.thisv().toObject().as<PlainDateObject>());
   1630 
   1631  auto showCalendar = ShowCalendar::Auto;
   1632  if (args.hasDefined(0)) {
   1633    // Step 3.
   1634    Rooted<JSObject*> options(
   1635        cx, RequireObjectArg(cx, "options", "toString", args[0]));
   1636    if (!options) {
   1637      return false;
   1638    }
   1639 
   1640    // Step 4.
   1641    if (!GetTemporalShowCalendarNameOption(cx, options, &showCalendar)) {
   1642      return false;
   1643    }
   1644  }
   1645 
   1646  // Step 5.
   1647  JSString* str = TemporalDateToString(cx, temporalDate, showCalendar);
   1648  if (!str) {
   1649    return false;
   1650  }
   1651 
   1652  args.rval().setString(str);
   1653  return true;
   1654 }
   1655 
   1656 /**
   1657 * Temporal.PlainDate.prototype.toString ( [ options ] )
   1658 */
   1659 static bool PlainDate_toString(JSContext* cx, unsigned argc, Value* vp) {
   1660  // Steps 1-2.
   1661  CallArgs args = CallArgsFromVp(argc, vp);
   1662  return CallNonGenericMethod<IsPlainDate, PlainDate_toString>(cx, args);
   1663 }
   1664 
   1665 /**
   1666 * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
   1667 */
   1668 static bool PlainDate_toLocaleString(JSContext* cx, const CallArgs& args) {
   1669  // Steps 3-4.
   1670  return intl::TemporalObjectToLocaleString(cx, args,
   1671                                            intl::DateTimeFormatKind::Date);
   1672 }
   1673 
   1674 /**
   1675 * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
   1676 */
   1677 static bool PlainDate_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
   1678  // Steps 1-2.
   1679  CallArgs args = CallArgsFromVp(argc, vp);
   1680  return CallNonGenericMethod<IsPlainDate, PlainDate_toLocaleString>(cx, args);
   1681 }
   1682 
   1683 /**
   1684 * Temporal.PlainDate.prototype.toJSON ( )
   1685 */
   1686 static bool PlainDate_toJSON(JSContext* cx, const CallArgs& args) {
   1687  Rooted<PlainDateObject*> temporalDate(
   1688      cx, &args.thisv().toObject().as<PlainDateObject>());
   1689 
   1690  // Step 3.
   1691  JSString* str = TemporalDateToString(cx, temporalDate, ShowCalendar::Auto);
   1692  if (!str) {
   1693    return false;
   1694  }
   1695 
   1696  args.rval().setString(str);
   1697  return true;
   1698 }
   1699 
   1700 /**
   1701 * Temporal.PlainDate.prototype.toJSON ( )
   1702 */
   1703 static bool PlainDate_toJSON(JSContext* cx, unsigned argc, Value* vp) {
   1704  // Steps 1-2.
   1705  CallArgs args = CallArgsFromVp(argc, vp);
   1706  return CallNonGenericMethod<IsPlainDate, PlainDate_toJSON>(cx, args);
   1707 }
   1708 
   1709 /**
   1710 *  Temporal.PlainDate.prototype.valueOf ( )
   1711 */
   1712 static bool PlainDate_valueOf(JSContext* cx, unsigned argc, Value* vp) {
   1713  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
   1714                            "PlainDate", "primitive type");
   1715  return false;
   1716 }
   1717 
   1718 const JSClass PlainDateObject::class_ = {
   1719    "Temporal.PlainDate",
   1720    JSCLASS_HAS_RESERVED_SLOTS(PlainDateObject::SLOT_COUNT) |
   1721        JSCLASS_HAS_CACHED_PROTO(JSProto_PlainDate),
   1722    JS_NULL_CLASS_OPS,
   1723    &PlainDateObject::classSpec_,
   1724 };
   1725 
   1726 const JSClass& PlainDateObject::protoClass_ = PlainObject::class_;
   1727 
   1728 static const JSFunctionSpec PlainDate_methods[] = {
   1729    JS_FN("from", PlainDate_from, 1, 0),
   1730    JS_FN("compare", PlainDate_compare, 2, 0),
   1731    JS_FS_END,
   1732 };
   1733 
   1734 static const JSFunctionSpec PlainDate_prototype_methods[] = {
   1735    JS_FN("toPlainMonthDay", PlainDate_toPlainMonthDay, 0, 0),
   1736    JS_FN("toPlainYearMonth", PlainDate_toPlainYearMonth, 0, 0),
   1737    JS_FN("toPlainDateTime", PlainDate_toPlainDateTime, 0, 0),
   1738    JS_FN("add", PlainDate_add, 1, 0),
   1739    JS_FN("subtract", PlainDate_subtract, 1, 0),
   1740    JS_FN("with", PlainDate_with, 1, 0),
   1741    JS_FN("withCalendar", PlainDate_withCalendar, 1, 0),
   1742    JS_FN("until", PlainDate_until, 1, 0),
   1743    JS_FN("since", PlainDate_since, 1, 0),
   1744    JS_FN("equals", PlainDate_equals, 1, 0),
   1745    JS_FN("toZonedDateTime", PlainDate_toZonedDateTime, 1, 0),
   1746    JS_FN("toString", PlainDate_toString, 0, 0),
   1747    JS_FN("toLocaleString", PlainDate_toLocaleString, 0, 0),
   1748    JS_FN("toJSON", PlainDate_toJSON, 0, 0),
   1749    JS_FN("valueOf", PlainDate_valueOf, 0, 0),
   1750    JS_FS_END,
   1751 };
   1752 
   1753 static const JSPropertySpec PlainDate_prototype_properties[] = {
   1754    JS_PSG("calendarId", PlainDate_calendarId, 0),
   1755    JS_PSG("era", PlainDate_era, 0),
   1756    JS_PSG("eraYear", PlainDate_eraYear, 0),
   1757    JS_PSG("year", PlainDate_year, 0),
   1758    JS_PSG("month", PlainDate_month, 0),
   1759    JS_PSG("monthCode", PlainDate_monthCode, 0),
   1760    JS_PSG("day", PlainDate_day, 0),
   1761    JS_PSG("dayOfWeek", PlainDate_dayOfWeek, 0),
   1762    JS_PSG("dayOfYear", PlainDate_dayOfYear, 0),
   1763    JS_PSG("weekOfYear", PlainDate_weekOfYear, 0),
   1764    JS_PSG("yearOfWeek", PlainDate_yearOfWeek, 0),
   1765    JS_PSG("daysInWeek", PlainDate_daysInWeek, 0),
   1766    JS_PSG("daysInMonth", PlainDate_daysInMonth, 0),
   1767    JS_PSG("daysInYear", PlainDate_daysInYear, 0),
   1768    JS_PSG("monthsInYear", PlainDate_monthsInYear, 0),
   1769    JS_PSG("inLeapYear", PlainDate_inLeapYear, 0),
   1770    JS_STRING_SYM_PS(toStringTag, "Temporal.PlainDate", JSPROP_READONLY),
   1771    JS_PS_END,
   1772 };
   1773 
   1774 const ClassSpec PlainDateObject::classSpec_ = {
   1775    GenericCreateConstructor<PlainDateConstructor, 3, gc::AllocKind::FUNCTION>,
   1776    GenericCreatePrototype<PlainDateObject>,
   1777    PlainDate_methods,
   1778    nullptr,
   1779    PlainDate_prototype_methods,
   1780    PlainDate_prototype_properties,
   1781    nullptr,
   1782    ClassSpec::DontDefineConstructor,
   1783 };