tor-browser

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

ZonedDateTime.cpp (96290B)


      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/ZonedDateTime.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/Maybe.h"
     11 
     12 #include <algorithm>
     13 #include <cstdlib>
     14 
     15 #include "jspubtd.h"
     16 #include "NamespaceImports.h"
     17 
     18 #include "builtin/intl/DateTimeFormat.h"
     19 #include "builtin/temporal/Calendar.h"
     20 #include "builtin/temporal/CalendarFields.h"
     21 #include "builtin/temporal/Duration.h"
     22 #include "builtin/temporal/Instant.h"
     23 #include "builtin/temporal/PlainDate.h"
     24 #include "builtin/temporal/PlainDateTime.h"
     25 #include "builtin/temporal/PlainMonthDay.h"
     26 #include "builtin/temporal/PlainTime.h"
     27 #include "builtin/temporal/PlainYearMonth.h"
     28 #include "builtin/temporal/Temporal.h"
     29 #include "builtin/temporal/TemporalParser.h"
     30 #include "builtin/temporal/TemporalRoundingMode.h"
     31 #include "builtin/temporal/TemporalTypes.h"
     32 #include "builtin/temporal/TemporalUnit.h"
     33 #include "builtin/temporal/TimeZone.h"
     34 #include "builtin/temporal/ToString.h"
     35 #include "gc/AllocKind.h"
     36 #include "gc/Barrier.h"
     37 #include "gc/GCEnum.h"
     38 #include "js/CallArgs.h"
     39 #include "js/CallNonGenericMethod.h"
     40 #include "js/Class.h"
     41 #include "js/ErrorReport.h"
     42 #include "js/friend/ErrorMessages.h"
     43 #include "js/Printer.h"
     44 #include "js/PropertyDescriptor.h"
     45 #include "js/PropertySpec.h"
     46 #include "js/RootingAPI.h"
     47 #include "js/Value.h"
     48 #include "vm/BigIntType.h"
     49 #include "vm/BytecodeUtil.h"
     50 #include "vm/GlobalObject.h"
     51 #include "vm/Int128.h"
     52 #include "vm/JSAtomState.h"
     53 #include "vm/JSContext.h"
     54 #include "vm/JSObject.h"
     55 #include "vm/PlainObject.h"
     56 #include "vm/StringType.h"
     57 
     58 #include "vm/JSObject-inl.h"
     59 #include "vm/NativeObject-inl.h"
     60 
     61 using namespace js;
     62 using namespace js::temporal;
     63 
     64 static inline bool IsZonedDateTime(Handle<Value> v) {
     65  return v.isObject() && v.toObject().is<ZonedDateTimeObject>();
     66 }
     67 
     68 // Returns |RoundNumberToIncrement(offsetNanoseconds, 60 × 10^9, "halfExpand")|.
     69 static int64_t RoundNanosecondsToMinutesIncrement(int64_t offsetNanoseconds) {
     70  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
     71 
     72  constexpr int64_t increment = ToNanoseconds(TemporalUnit::Minute);
     73 
     74  int64_t quotient = offsetNanoseconds / increment;
     75  int64_t remainder = offsetNanoseconds % increment;
     76  if (std::abs(remainder * 2) >= increment) {
     77    quotient += (offsetNanoseconds > 0 ? 1 : -1);
     78  }
     79  return quotient * increment;
     80 }
     81 
     82 /**
     83 * InterpretISODateTimeOffset ( isoDate, time, offsetBehaviour,
     84 * offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour )
     85 */
     86 bool js::temporal::InterpretISODateTimeOffset(
     87    JSContext* cx, const ISODateTime& dateTime, OffsetBehaviour offsetBehaviour,
     88    int64_t offsetNanoseconds, Handle<TimeZoneValue> timeZone,
     89    TemporalDisambiguation disambiguation, TemporalOffset offsetOption,
     90    MatchBehaviour matchBehaviour, EpochNanoseconds* result) {
     91  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
     92  MOZ_ASSERT(IsValidISODateTime(dateTime));
     93 
     94  // FIXME: spec issue - avoid calling with date-time outside of limits
     95  // https://github.com/tc39/proposal-temporal/pull/3014
     96  if (!ISODateTimeWithinLimits(dateTime)) {
     97    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
     98                              JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
     99    return false;
    100  }
    101 
    102  // Steps 1-2. (Not applicable in our implementation.)
    103 
    104  // Step 3.
    105  if (offsetBehaviour == OffsetBehaviour::Wall ||
    106      (offsetBehaviour == OffsetBehaviour::Option &&
    107       offsetOption == TemporalOffset::Ignore)) {
    108    // Steps 3.a-b.
    109    return GetEpochNanosecondsFor(cx, timeZone, dateTime, disambiguation,
    110                                  result);
    111  }
    112 
    113  // Step 4.
    114  if (offsetBehaviour == OffsetBehaviour::Exact ||
    115      (offsetBehaviour == OffsetBehaviour::Option &&
    116       offsetOption == TemporalOffset::Use)) {
    117    // Step 4.a.
    118    auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime) -
    119                            EpochDuration::fromNanoseconds(offsetNanoseconds);
    120 
    121    // Step 4.b.
    122    if (!IsValidEpochNanoseconds(epochNanoseconds)) {
    123      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    124                                JSMSG_TEMPORAL_INSTANT_INVALID);
    125      return false;
    126    }
    127 
    128    // Step 4.c.
    129    *result = epochNanoseconds;
    130    return true;
    131  }
    132 
    133  // Step 5.
    134  MOZ_ASSERT(offsetBehaviour == OffsetBehaviour::Option);
    135 
    136  // Step 6.
    137  MOZ_ASSERT(offsetOption == TemporalOffset::Prefer ||
    138             offsetOption == TemporalOffset::Reject);
    139 
    140  // Step 7.
    141  PossibleEpochNanoseconds possibleEpochNs;
    142  if (!GetPossibleEpochNanoseconds(cx, timeZone, dateTime, &possibleEpochNs)) {
    143    return false;
    144  }
    145 
    146  // Step 8.a.
    147  for (const auto& candidate : possibleEpochNs) {
    148    // Step 8.a.i.
    149    int64_t candidateNanoseconds;
    150    if (!GetOffsetNanosecondsFor(cx, timeZone, candidate,
    151                                 &candidateNanoseconds)) {
    152      return false;
    153    }
    154    MOZ_ASSERT(std::abs(candidateNanoseconds) <
    155               ToNanoseconds(TemporalUnit::Day));
    156 
    157    // Step 8.a.ii.
    158    if (candidateNanoseconds == offsetNanoseconds) {
    159      *result = candidate;
    160      return true;
    161    }
    162 
    163    // Step 8.a.iii.
    164    if (matchBehaviour == MatchBehaviour::MatchMinutes) {
    165      // Step 8.a.iii.1.
    166      int64_t roundedCandidateNanoseconds =
    167          RoundNanosecondsToMinutesIncrement(candidateNanoseconds);
    168 
    169      // Step 8.a.iii.2.
    170      if (roundedCandidateNanoseconds == offsetNanoseconds) {
    171        // Step 8.a.iii.2.a.
    172        *result = candidate;
    173        return true;
    174      }
    175    }
    176  }
    177 
    178  // Step 9.
    179  if (offsetOption == TemporalOffset::Reject) {
    180    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    181                              JSMSG_TEMPORAL_ZONED_DATE_TIME_NO_TIME_FOUND);
    182    return false;
    183  }
    184 
    185  // Step 10.
    186  return DisambiguatePossibleEpochNanoseconds(cx, possibleEpochNs, timeZone,
    187                                              dateTime, disambiguation, result);
    188 }
    189 
    190 /**
    191 * InterpretISODateTimeOffset ( isoDate, time, offsetBehaviour,
    192 * offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour )
    193 */
    194 bool js::temporal::InterpretISODateTimeOffset(
    195    JSContext* cx, const ISODate& isoDate, OffsetBehaviour offsetBehaviour,
    196    int64_t offsetNanoseconds, Handle<TimeZoneValue> timeZone,
    197    TemporalDisambiguation disambiguation, TemporalOffset offsetOption,
    198    MatchBehaviour matchBehaviour, EpochNanoseconds* result) {
    199  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    200  MOZ_ASSERT(IsValidISODate(isoDate));
    201 
    202  // Step 1. (Not applicable in our implementation.)
    203 
    204  // Step 2.a.
    205  MOZ_ASSERT(offsetBehaviour == OffsetBehaviour::Wall);
    206 
    207  // Step 2.b.
    208  MOZ_ASSERT(offsetNanoseconds == 0);
    209 
    210  // Step 2.c.
    211  return GetStartOfDay(cx, timeZone, isoDate, result);
    212 }
    213 
    214 struct ZonedDateTimeOptions {
    215  TemporalDisambiguation disambiguation = TemporalDisambiguation::Compatible;
    216  TemporalOffset offset = TemporalOffset::Reject;
    217  TemporalOverflow overflow = TemporalOverflow::Constrain;
    218 };
    219 
    220 /**
    221 * ToTemporalZonedDateTime ( item [ , options ] )
    222 */
    223 static bool ToTemporalZonedDateTimeOptions(JSContext* cx, Handle<Value> options,
    224                                           ZonedDateTimeOptions* result) {
    225  if (options.isUndefined()) {
    226    *result = {};
    227    return true;
    228  }
    229 
    230  // NOTE: |options| are only passed from `Temporal.ZonedDateTime.from`.
    231 
    232  Rooted<JSObject*> resolvedOptions(
    233      cx, RequireObjectArg(cx, "options", "from", options));
    234  if (!resolvedOptions) {
    235    return false;
    236  }
    237 
    238  auto disambiguation = TemporalDisambiguation::Compatible;
    239  if (!GetTemporalDisambiguationOption(cx, resolvedOptions, &disambiguation)) {
    240    return false;
    241  }
    242 
    243  auto offset = TemporalOffset::Reject;
    244  if (!GetTemporalOffsetOption(cx, resolvedOptions, &offset)) {
    245    return false;
    246  }
    247 
    248  auto overflow = TemporalOverflow::Constrain;
    249  if (!GetTemporalOverflowOption(cx, resolvedOptions, &overflow)) {
    250    return false;
    251  }
    252 
    253  *result = {disambiguation, offset, overflow};
    254  return true;
    255 }
    256 
    257 /**
    258 * ToTemporalZonedDateTime ( item [ , options ] )
    259 */
    260 static bool ToTemporalZonedDateTime(JSContext* cx, Handle<JSObject*> item,
    261                                    Handle<Value> options,
    262                                    MutableHandle<ZonedDateTime> result) {
    263  // Step 1. (Not applicable in our implementation.)
    264 
    265  // Step 2. (Not applicable)
    266 
    267  // Step 3.
    268  auto matchBehaviour = MatchBehaviour::MatchExactly;
    269 
    270  // Step 4.a.
    271  if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) {
    272    auto epochNs = zonedDateTime->epochNanoseconds();
    273    Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
    274    Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
    275 
    276    if (!timeZone.wrap(cx)) {
    277      return false;
    278    }
    279    if (!calendar.wrap(cx)) {
    280      return false;
    281    }
    282 
    283    // Steps 4.a.i-v.
    284    ZonedDateTimeOptions ignoredOptions;
    285    if (!ToTemporalZonedDateTimeOptions(cx, options, &ignoredOptions)) {
    286      return false;
    287    }
    288 
    289    // Step 4.a.vi.
    290    result.set(ZonedDateTime{epochNs, timeZone, calendar});
    291    return true;
    292  }
    293 
    294  // Step 4.b.
    295  Rooted<CalendarValue> calendar(cx);
    296  if (!GetTemporalCalendarWithISODefault(cx, item, &calendar)) {
    297    return false;
    298  }
    299 
    300  // Step 4.c.
    301  Rooted<CalendarFields> fields(cx);
    302  if (!PrepareCalendarFields(cx, calendar, item,
    303                             {
    304                                 CalendarField::Year,
    305                                 CalendarField::Month,
    306                                 CalendarField::MonthCode,
    307                                 CalendarField::Day,
    308                                 CalendarField::Hour,
    309                                 CalendarField::Minute,
    310                                 CalendarField::Second,
    311                                 CalendarField::Millisecond,
    312                                 CalendarField::Microsecond,
    313                                 CalendarField::Nanosecond,
    314                                 CalendarField::Offset,
    315                                 CalendarField::TimeZone,
    316                             },
    317                             {CalendarField::TimeZone}, &fields)) {
    318    return false;
    319  }
    320 
    321  // Step 4.d.
    322  auto timeZone = fields.timeZone();
    323 
    324  // Step 4.e.
    325  auto offsetString = fields.offset();
    326 
    327  // Steps 4.f-i.
    328  ZonedDateTimeOptions resolvedOptions;
    329  if (!ToTemporalZonedDateTimeOptions(cx, options, &resolvedOptions)) {
    330    return false;
    331  }
    332  auto [disambiguation, offsetOption, overflow] = resolvedOptions;
    333 
    334  // Steps 4.j-l.
    335  ISODateTime dateTime;
    336  if (!InterpretTemporalDateTimeFields(cx, calendar, fields, overflow,
    337                                       &dateTime)) {
    338    return false;
    339  }
    340 
    341  // Steps 5-6. (Not applicable)
    342 
    343  // Steps 7-8.
    344  auto offsetBehaviour = !fields.has(CalendarField::Offset)
    345                             ? OffsetBehaviour::Wall
    346                             : OffsetBehaviour::Option;
    347 
    348  // Step 9.
    349  int64_t offsetNanoseconds = 0;
    350 
    351  // Step 10.
    352  if (offsetBehaviour == OffsetBehaviour::Option) {
    353    offsetNanoseconds = int64_t(offsetString);
    354  }
    355 
    356  // Step 11.
    357  EpochNanoseconds epochNanoseconds;
    358  if (!InterpretISODateTimeOffset(
    359          cx, dateTime, offsetBehaviour, offsetNanoseconds, timeZone,
    360          disambiguation, offsetOption, matchBehaviour, &epochNanoseconds)) {
    361    return false;
    362  }
    363  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
    364 
    365  // Step 12.
    366  result.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
    367  return true;
    368 }
    369 
    370 /**
    371 * ToTemporalZonedDateTime ( item [ , options ] )
    372 */
    373 static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
    374                                    Handle<Value> options,
    375                                    MutableHandle<ZonedDateTime> result) {
    376  // Step 1. (Not applicable in our implementation.)
    377 
    378  // Steps 2-3. (Not applicable)
    379 
    380  // Step 4.
    381  if (item.isObject()) {
    382    Rooted<JSObject*> itemObj(cx, &item.toObject());
    383    return ToTemporalZonedDateTime(cx, itemObj, options, result);
    384  }
    385 
    386  // Step 5.a.
    387  if (!item.isString()) {
    388    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
    389                     nullptr, "not a string");
    390    return false;
    391  }
    392  Rooted<JSString*> string(cx, item.toString());
    393 
    394  // Case 1: 19700101Z[+02:00]
    395  // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
    396  //
    397  // Case 2: 19700101+00:00[+02:00]
    398  // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" }
    399  //
    400  // Case 3: 19700101[+02:00]
    401  // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
    402  //
    403  // Case 4: 19700101Z[Europe/Berlin]
    404  // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
    405  //
    406  // Case 5: 19700101+00:00[Europe/Berlin]
    407  // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" }
    408  //
    409  // Case 6: 19700101[Europe/Berlin]
    410  // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
    411 
    412  // Steps 5.b-c.
    413  Rooted<ParsedZonedDateTime> parsed(cx);
    414  if (!ParseTemporalZonedDateTimeString(cx, string, &parsed)) {
    415    return false;
    416  }
    417 
    418  // Step 5.d.
    419  MOZ_ASSERT(parsed.timeZoneAnnotation());
    420 
    421  // Step 5.e.
    422  Rooted<TimeZoneValue> timeZone(cx);
    423  if (!ToTemporalTimeZone(cx, parsed.timeZoneAnnotation(), &timeZone)) {
    424    return false;
    425  }
    426 
    427  // Step 5.f. (Not applicable in our implementation.)
    428 
    429  // Step 5.g.
    430  bool hasUTCDesignator = parsed.timeZone().constructed<UTCTimeZone>();
    431 
    432  // Steps 5.h-i.
    433  Rooted<CalendarValue> calendar(cx, CalendarValue(CalendarId::ISO8601));
    434  if (parsed.calendar()) {
    435    if (!CanonicalizeCalendar(cx, parsed.calendar(), &calendar)) {
    436      return false;
    437    }
    438  }
    439 
    440  // Step 5.k.
    441  auto matchBehaviour = MatchBehaviour::MatchMinutes;
    442 
    443  // Step 5.l.
    444  if (parsed.timeZone().constructed<OffsetTimeZone>()) {
    445    // Steps 5.l.i-iii.
    446    if (parsed.timeZone().ref<OffsetTimeZone>().hasSubMinutePrecision) {
    447      matchBehaviour = MatchBehaviour::MatchExactly;
    448    }
    449  }
    450 
    451  // Steps 5.m-p.
    452  ZonedDateTimeOptions resolvedOptions;
    453  if (!ToTemporalZonedDateTimeOptions(cx, options, &resolvedOptions)) {
    454    return false;
    455  }
    456  auto [disambiguation, offsetOption, overflow] = resolvedOptions;
    457 
    458  // Steps 5.q-r.
    459  const auto& isoDateTime = parsed.dateTime();
    460 
    461  // Steps 6-8.
    462  auto offsetBehaviour = hasUTCDesignator            ? OffsetBehaviour::Exact
    463                         : parsed.timeZone().empty() ? OffsetBehaviour::Wall
    464                                                     : OffsetBehaviour::Option;
    465 
    466  // Step 9.
    467  int64_t offsetNanoseconds = 0;
    468 
    469  // Step 10.
    470  if (offsetBehaviour == OffsetBehaviour::Option) {
    471    MOZ_ASSERT(parsed.timeZone().constructed<OffsetTimeZone>());
    472    offsetNanoseconds = parsed.timeZone().ref<OffsetTimeZone>().offset;
    473  }
    474 
    475  // Step 11.
    476  EpochNanoseconds epochNanoseconds;
    477  if (parsed.isStartOfDay()) {
    478    if (!InterpretISODateTimeOffset(
    479            cx, isoDateTime.date, offsetBehaviour, offsetNanoseconds, timeZone,
    480            disambiguation, offsetOption, matchBehaviour, &epochNanoseconds)) {
    481      return false;
    482    }
    483  } else {
    484    if (!InterpretISODateTimeOffset(
    485            cx, isoDateTime, offsetBehaviour, offsetNanoseconds, timeZone,
    486            disambiguation, offsetOption, matchBehaviour, &epochNanoseconds)) {
    487      return false;
    488    }
    489  }
    490  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
    491 
    492  // Step 12.
    493  result.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
    494  return true;
    495 }
    496 
    497 /**
    498 * ToTemporalZonedDateTime ( item [ , options ] )
    499 */
    500 static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
    501                                    MutableHandle<ZonedDateTime> result) {
    502  return ToTemporalZonedDateTime(cx, item, UndefinedHandleValue, result);
    503 }
    504 
    505 /**
    506 * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
    507 * newTarget ] )
    508 */
    509 static ZonedDateTimeObject* CreateTemporalZonedDateTime(
    510    JSContext* cx, const CallArgs& args, Handle<BigInt*> epochNanoseconds,
    511    Handle<TimeZoneValue> timeZone, Handle<CalendarValue> calendar) {
    512  // Step 1.
    513  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
    514 
    515  // Steps 3-4.
    516  Rooted<JSObject*> proto(cx);
    517  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ZonedDateTime,
    518                                          &proto)) {
    519    return nullptr;
    520  }
    521 
    522  auto* object = NewObjectWithClassProto<ZonedDateTimeObject>(cx, proto);
    523  if (!object) {
    524    return nullptr;
    525  }
    526 
    527  // Step 4.
    528  auto epochNs = ToEpochNanoseconds(epochNanoseconds);
    529  object->initFixedSlot(ZonedDateTimeObject::SECONDS_SLOT,
    530                        NumberValue(epochNs.seconds));
    531  object->initFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT,
    532                        Int32Value(epochNs.nanoseconds));
    533 
    534  // Step 5.
    535  object->initFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT,
    536                        timeZone.toSlotValue());
    537 
    538  // Step 6.
    539  object->initFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT,
    540                        calendar.toSlotValue());
    541 
    542  // Step 7.
    543  return object;
    544 }
    545 
    546 /**
    547 * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
    548 * newTarget ] )
    549 */
    550 ZonedDateTimeObject* js::temporal::CreateTemporalZonedDateTime(
    551    JSContext* cx, const EpochNanoseconds& epochNanoseconds,
    552    Handle<TimeZoneValue> timeZone, Handle<CalendarValue> calendar) {
    553  // Step 1.
    554  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
    555 
    556  // Steps 2-3.
    557  auto* object = NewBuiltinClassInstance<ZonedDateTimeObject>(cx);
    558  if (!object) {
    559    return nullptr;
    560  }
    561 
    562  // Step 4.
    563  object->initFixedSlot(ZonedDateTimeObject::SECONDS_SLOT,
    564                        NumberValue(epochNanoseconds.seconds));
    565  object->initFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT,
    566                        Int32Value(epochNanoseconds.nanoseconds));
    567 
    568  // Step 5.
    569  object->initFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT,
    570                        timeZone.toSlotValue());
    571 
    572  // Step 6.
    573  object->initFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT,
    574                        calendar.toSlotValue());
    575 
    576  // Step 7.
    577  return object;
    578 }
    579 
    580 /**
    581 * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
    582 * newTarget ] )
    583 */
    584 static auto* CreateTemporalZonedDateTime(JSContext* cx,
    585                                         Handle<ZonedDateTime> zonedDateTime) {
    586  return CreateTemporalZonedDateTime(cx, zonedDateTime.epochNanoseconds(),
    587                                     zonedDateTime.timeZone(),
    588                                     zonedDateTime.calendar());
    589 }
    590 
    591 /**
    592 * AddZonedDateTime ( epochNanoseconds, timeZone, calendar, duration, overflow )
    593 */
    594 static bool AddZonedDateTime(JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
    595                             const InternalDuration& duration,
    596                             TemporalOverflow overflow,
    597                             EpochNanoseconds* result) {
    598  MOZ_ASSERT(IsValidDuration(duration));
    599 
    600  // Step 1.
    601  if (duration.date == DateDuration{}) {
    602    // Step 1.a.
    603    return AddInstant(cx, zonedDateTime.epochNanoseconds(), duration.time,
    604                      result);
    605  }
    606 
    607  // Step 2.
    608  ISODateTime isoDateTime;
    609  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
    610                         zonedDateTime.epochNanoseconds(), &isoDateTime)) {
    611    return false;
    612  }
    613 
    614  // Step 3.
    615  ISODate addedDate;
    616  if (!CalendarDateAdd(cx, zonedDateTime.calendar(), isoDateTime.date,
    617                       duration.date, overflow, &addedDate)) {
    618    return false;
    619  }
    620 
    621  // Step 4.
    622  auto intermediateDateTime = ISODateTime{addedDate, isoDateTime.time};
    623 
    624  // Step 5.
    625  if (!ISODateTimeWithinLimits(intermediateDateTime)) {
    626    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    627                              JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
    628    return false;
    629  }
    630 
    631  // Step 6.
    632  EpochNanoseconds intermediateNs;
    633  if (!GetEpochNanosecondsFor(
    634          cx, zonedDateTime.timeZone(), intermediateDateTime,
    635          TemporalDisambiguation::Compatible, &intermediateNs)) {
    636    return false;
    637  }
    638 
    639  // Step 7.
    640  return AddInstant(cx, intermediateNs, duration.time, result);
    641 }
    642 
    643 /**
    644 * AddZonedDateTime ( epochNanoseconds, timeZone, calendar, duration, overflow )
    645 */
    646 bool js::temporal::AddZonedDateTime(JSContext* cx,
    647                                    Handle<ZonedDateTime> zonedDateTime,
    648                                    const InternalDuration& duration,
    649                                    EpochNanoseconds* result) {
    650  return ::AddZonedDateTime(cx, zonedDateTime, duration,
    651                            TemporalOverflow::Constrain, result);
    652 }
    653 
    654 /**
    655 * DifferenceZonedDateTime ( ns1, ns2, timeZone, calendar, largestUnit )
    656 */
    657 static bool DifferenceZonedDateTime(JSContext* cx, const EpochNanoseconds& ns1,
    658                                    const EpochNanoseconds& ns2,
    659                                    Handle<TimeZoneValue> timeZone,
    660                                    Handle<CalendarValue> calendar,
    661                                    TemporalUnit largestUnit,
    662                                    InternalDuration* result) {
    663  MOZ_ASSERT(IsValidEpochNanoseconds(ns1));
    664  MOZ_ASSERT(IsValidEpochNanoseconds(ns2));
    665 
    666  // Steps 1.
    667  if (ns1 == ns2) {
    668    *result = InternalDuration{{}, {}};
    669    return true;
    670  }
    671 
    672  // Step 2.
    673  ISODateTime startDateTime;
    674  if (!GetISODateTimeFor(cx, timeZone, ns1, &startDateTime)) {
    675    return false;
    676  }
    677 
    678  // Steps 2-3.
    679  ISODateTime endDateTime;
    680  if (!GetISODateTimeFor(cx, timeZone, ns2, &endDateTime)) {
    681    return false;
    682  }
    683 
    684  // Step 4.
    685  if (CompareISODate(startDateTime.date, endDateTime.date) == 0) {
    686    // Step 4.a.
    687    auto timeDuration = TimeDurationFromEpochNanosecondsDifference(ns2, ns1);
    688 
    689    // Step 4.b.
    690    *result = {{}, timeDuration};
    691    return true;
    692  }
    693 
    694  // Step 5.
    695  int32_t sign = (ns2 - ns1 < EpochDuration{}) ? -1 : 1;
    696 
    697  // Step 6.
    698  int32_t maxDayCorrection = 1 + (sign > 0);
    699 
    700  // Step 7.
    701  int32_t dayCorrection = 0;
    702 
    703  // Step 8.
    704  auto timeDuration = DifferenceTime(startDateTime.time, endDateTime.time);
    705 
    706  // Step 9.
    707  if (TimeDurationSign(timeDuration) == -sign) {
    708    dayCorrection += 1;
    709  }
    710 
    711  // Steps 10-11.
    712  while (dayCorrection <= maxDayCorrection) {
    713    // Step 11.a.
    714    auto intermediateDate =
    715        BalanceISODate(endDateTime.date, -dayCorrection * sign);
    716 
    717    // Step 11.b.
    718    auto intermediateDateTime =
    719        ISODateTime{intermediateDate, startDateTime.time};
    720    if (!ISODateTimeWithinLimits(intermediateDateTime)) {
    721      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    722                                JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
    723      return false;
    724    }
    725 
    726    // Step 11.c.
    727    EpochNanoseconds intermediateNs;
    728    if (!GetEpochNanosecondsFor(cx, timeZone, intermediateDateTime,
    729                                TemporalDisambiguation::Compatible,
    730                                &intermediateNs)) {
    731      return false;
    732    }
    733 
    734    // Step 11.d.
    735    auto timeDuration =
    736        TimeDurationFromEpochNanosecondsDifference(ns2, intermediateNs);
    737 
    738    // Step 11.e.
    739    int32_t timeSign = TimeDurationSign(timeDuration);
    740 
    741    // Step 11.f.
    742    if (sign != -timeSign) {
    743      // Step 13.
    744      auto dateLargestUnit = std::min(largestUnit, TemporalUnit::Day);
    745 
    746      // Step 14.
    747      DateDuration dateDifference;
    748      if (!CalendarDateUntil(cx, calendar, startDateTime.date, intermediateDate,
    749                             dateLargestUnit, &dateDifference)) {
    750        return false;
    751      }
    752 
    753      // Step 15.
    754      MOZ_ASSERT(DateDurationSign(dateDifference) *
    755                     TimeDurationSign(timeDuration) >=
    756                 0);
    757      *result = {dateDifference, timeDuration};
    758      return true;
    759    }
    760 
    761    // Step 11.g.
    762    dayCorrection += 1;
    763  }
    764 
    765  // Step 12.
    766  JS_ReportErrorNumberASCII(
    767      cx, GetErrorMessage, nullptr,
    768      JSMSG_TEMPORAL_ZONED_DATE_TIME_INCONSISTENT_INSTANT);
    769  return false;
    770 }
    771 
    772 /**
    773 * DifferenceZonedDateTimeWithRounding ( ns1, ns2, timeZone, calendar,
    774 * largestUnit, roundingIncrement, smallestUnit, roundingMode )
    775 */
    776 bool js::temporal::DifferenceZonedDateTimeWithRounding(
    777    JSContext* cx, JS::Handle<ZonedDateTime> zonedDateTime,
    778    const EpochNanoseconds& ns2, const DifferenceSettings& settings,
    779    InternalDuration* result) {
    780  MOZ_ASSERT(IsValidEpochNanoseconds(ns2));
    781  MOZ_ASSERT(settings.smallestUnit >= settings.largestUnit);
    782 
    783  const auto& ns1 = zonedDateTime.epochNanoseconds();
    784  auto timeZone = zonedDateTime.timeZone();
    785  auto calendar = zonedDateTime.calendar();
    786 
    787  // Step 1.
    788  if (settings.largestUnit > TemporalUnit::Day) {
    789    // Step 1.a.
    790    auto difference =
    791        DifferenceInstant(ns1, ns2, settings.roundingIncrement,
    792                          settings.smallestUnit, settings.roundingMode);
    793    *result = InternalDuration{{}, difference};
    794    return true;
    795  }
    796 
    797  // Step 2.
    798  InternalDuration difference;
    799  if (!DifferenceZonedDateTime(cx, ns1, ns2, timeZone, calendar,
    800                               settings.largestUnit, &difference)) {
    801    return false;
    802  }
    803 
    804  // Step 3.
    805  if (settings.smallestUnit == TemporalUnit::Nanosecond &&
    806      settings.roundingIncrement == Increment{1}) {
    807    // Step 3.a.
    808    *result = difference;
    809    return true;
    810  }
    811 
    812  // Step 4.
    813  ISODateTime dateTime;
    814  if (!GetISODateTimeFor(cx, timeZone, ns1, &dateTime)) {
    815    return false;
    816  }
    817 
    818  // Step 5.
    819  return RoundRelativeDuration(
    820      cx, difference, ns1, ns2, dateTime, timeZone, calendar,
    821      settings.largestUnit, settings.roundingIncrement, settings.smallestUnit,
    822      settings.roundingMode, result);
    823 }
    824 
    825 /**
    826 * DifferenceZonedDateTimeWithTotal ( ns1, ns2, timeZone, calendar, unit )
    827 */
    828 bool js::temporal::DifferenceZonedDateTimeWithTotal(
    829    JSContext* cx, JS::Handle<ZonedDateTime> zonedDateTime,
    830    const EpochNanoseconds& ns2, TemporalUnit unit, double* result) {
    831  MOZ_ASSERT(IsValidEpochNanoseconds(ns2));
    832 
    833  const auto& ns1 = zonedDateTime.epochNanoseconds();
    834  auto timeZone = zonedDateTime.timeZone();
    835  auto calendar = zonedDateTime.calendar();
    836 
    837  // Step 1.
    838  if (unit > TemporalUnit::Day) {
    839    // Step 1.a.
    840    auto difference = TimeDurationFromEpochNanosecondsDifference(ns2, ns1);
    841    MOZ_ASSERT(IsValidEpochDuration(difference.to<EpochDuration>()));
    842 
    843    // Step 1.b.
    844    *result = TotalTimeDuration(difference, unit);
    845    return true;
    846  }
    847 
    848  // Step 2.
    849  InternalDuration difference;
    850  if (!DifferenceZonedDateTime(cx, ns1, ns2, timeZone, calendar, unit,
    851                               &difference)) {
    852    return false;
    853  }
    854 
    855  // Step 3.
    856  ISODateTime dateTime;
    857  if (!GetISODateTimeFor(cx, timeZone, ns1, &dateTime)) {
    858    return false;
    859  }
    860 
    861  // Step 5.
    862  return TotalRelativeDuration(cx, difference, ns1, ns2, dateTime, timeZone,
    863                               calendar, unit, result);
    864 }
    865 
    866 /**
    867 * DifferenceTemporalZonedDateTime ( operation, zonedDateTime, other, options )
    868 */
    869 static bool DifferenceTemporalZonedDateTime(JSContext* cx,
    870                                            TemporalDifference operation,
    871                                            const CallArgs& args) {
    872  Rooted<ZonedDateTime> zonedDateTime(
    873      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
    874 
    875  // Step 1.
    876  Rooted<ZonedDateTime> other(cx);
    877  if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) {
    878    return false;
    879  }
    880 
    881  // Step 2.
    882  if (!CalendarEquals(zonedDateTime.calendar(), other.calendar())) {
    883    JS_ReportErrorNumberASCII(
    884        cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
    885        CalendarIdentifier(zonedDateTime.calendar()).data(),
    886        CalendarIdentifier(other.calendar()).data());
    887    return false;
    888  }
    889 
    890  // Steps 3-4.
    891  DifferenceSettings settings;
    892  if (args.hasDefined(1)) {
    893    // Step 3.
    894    Rooted<JSObject*> options(
    895        cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
    896    if (!options) {
    897      return false;
    898    }
    899 
    900    // Step 4.
    901    if (!GetDifferenceSettings(
    902            cx, operation, options, TemporalUnitGroup::DateTime,
    903            TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) {
    904      return false;
    905    }
    906  } else {
    907    // Steps 3-4.
    908    settings = {
    909        TemporalUnit::Nanosecond,
    910        TemporalUnit::Hour,
    911        TemporalRoundingMode::Trunc,
    912        Increment{1},
    913    };
    914  }
    915 
    916  // Step 5.
    917  if (settings.largestUnit > TemporalUnit::Day) {
    918    MOZ_ASSERT(settings.smallestUnit >= settings.largestUnit);
    919 
    920    // Step 5.a.
    921    auto timeDuration =
    922        DifferenceInstant(zonedDateTime.epochNanoseconds(),
    923                          other.epochNanoseconds(), settings.roundingIncrement,
    924                          settings.smallestUnit, settings.roundingMode);
    925 
    926    // Step 5.b.
    927    Duration result;
    928    if (!TemporalDurationFromInternal(cx, timeDuration, settings.largestUnit,
    929                                      &result)) {
    930      return false;
    931    }
    932 
    933    // Step 5.c.
    934    if (operation == TemporalDifference::Since) {
    935      result = result.negate();
    936    }
    937 
    938    // Step 5.d.
    939    auto* obj = CreateTemporalDuration(cx, result);
    940    if (!obj) {
    941      return false;
    942    }
    943 
    944    args.rval().setObject(*obj);
    945    return true;
    946  }
    947 
    948  // Steps 6-7.
    949  if (!TimeZoneEquals(zonedDateTime.timeZone(), other.timeZone())) {
    950    if (auto one = QuoteString(cx, zonedDateTime.timeZone().identifier())) {
    951      if (auto two = QuoteString(cx, other.timeZone().identifier())) {
    952        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    953                                 JSMSG_TEMPORAL_TIMEZONE_INCOMPATIBLE,
    954                                 one.get(), two.get());
    955      }
    956    }
    957    return false;
    958  }
    959 
    960  // Step 8.
    961  if (zonedDateTime.epochNanoseconds() == other.epochNanoseconds()) {
    962    auto* obj = CreateTemporalDuration(cx, {});
    963    if (!obj) {
    964      return false;
    965    }
    966 
    967    args.rval().setObject(*obj);
    968    return true;
    969  }
    970 
    971  // Step 9.
    972  InternalDuration internalDuration;
    973  if (!DifferenceZonedDateTimeWithRounding(cx, zonedDateTime,
    974                                           other.epochNanoseconds(), settings,
    975                                           &internalDuration)) {
    976    return false;
    977  }
    978  MOZ_ASSERT(IsValidDuration(internalDuration));
    979 
    980  // Step 10.
    981  Duration result;
    982  if (!TemporalDurationFromInternal(cx, internalDuration, TemporalUnit::Hour,
    983                                    &result)) {
    984    return false;
    985  }
    986 
    987  // Step 11.
    988  if (operation == TemporalDifference::Since) {
    989    result = result.negate();
    990  }
    991 
    992  // Step 12.
    993  auto* obj = CreateTemporalDuration(cx, result);
    994  if (!obj) {
    995    return false;
    996  }
    997 
    998  args.rval().setObject(*obj);
    999  return true;
   1000 }
   1001 
   1002 /**
   1003 * AddDurationToZonedDateTime ( operation, zonedDateTime, temporalDurationLike,
   1004 * options )
   1005 */
   1006 static bool AddDurationToZonedDateTime(JSContext* cx,
   1007                                       TemporalAddDuration operation,
   1008                                       const CallArgs& args) {
   1009  Rooted<ZonedDateTime> zonedDateTime(
   1010      cx, &args.thisv().toObject().as<ZonedDateTimeObject>());
   1011 
   1012  // Step 1.
   1013  Duration duration;
   1014  if (!ToTemporalDuration(cx, args.get(0), &duration)) {
   1015    return false;
   1016  }
   1017 
   1018  // Step 2.
   1019  if (operation == TemporalAddDuration::Subtract) {
   1020    duration = duration.negate();
   1021  }
   1022 
   1023  // Steps 3-4.
   1024  auto overflow = TemporalOverflow::Constrain;
   1025  if (args.hasDefined(1)) {
   1026    // Step 3.
   1027    Rooted<JSObject*> options(
   1028        cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
   1029    if (!options) {
   1030      return false;
   1031    }
   1032 
   1033    // Step 4.
   1034    if (!GetTemporalOverflowOption(cx, options, &overflow)) {
   1035      return false;
   1036    }
   1037  }
   1038 
   1039  // Step 5.
   1040  auto calendar = zonedDateTime.calendar();
   1041 
   1042  // Step 6.
   1043  auto timeZone = zonedDateTime.timeZone();
   1044 
   1045  // Step 7.
   1046  auto internalDuration = ToInternalDurationRecord(duration);
   1047 
   1048  // Step 8.
   1049  EpochNanoseconds epochNanoseconds;
   1050  if (!::AddZonedDateTime(cx, zonedDateTime, internalDuration, overflow,
   1051                          &epochNanoseconds)) {
   1052    return false;
   1053  }
   1054  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
   1055 
   1056  // Step 9.
   1057  auto* result =
   1058      CreateTemporalZonedDateTime(cx, epochNanoseconds, timeZone, calendar);
   1059  if (!result) {
   1060    return false;
   1061  }
   1062 
   1063  args.rval().setObject(*result);
   1064  return true;
   1065 }
   1066 
   1067 /**
   1068 * FormatUTCOffsetNanoseconds ( offsetNanoseconds )
   1069 */
   1070 static JSString* FormatUTCOffsetNanoseconds(JSContext* cx,
   1071                                            int64_t offsetNanoseconds) {
   1072  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
   1073 
   1074  // Step 1.
   1075  char sign = offsetNanoseconds >= 0 ? '+' : '-';
   1076 
   1077  // Step 2.
   1078  int64_t absoluteNanoseconds = std::abs(offsetNanoseconds);
   1079 
   1080  // Step 6. (Reordered)
   1081  int32_t subSecondNanoseconds = int32_t(absoluteNanoseconds % 1'000'000'000);
   1082 
   1083  // Step 5. (Reordered)
   1084  int32_t quotient = int32_t(absoluteNanoseconds / 1'000'000'000);
   1085  int32_t second = quotient % 60;
   1086 
   1087  // Step 4. (Reordered)
   1088  quotient /= 60;
   1089  int32_t minute = quotient % 60;
   1090 
   1091  // Step 3.
   1092  int32_t hour = quotient / 60;
   1093  MOZ_ASSERT(hour < 24, "time zone offset mustn't exceed 24-hours");
   1094 
   1095  // Format: "sign hour{2} : minute{2} : second{2} . fractional{9}"
   1096  constexpr size_t maxLength = 1 + 2 + 1 + 2 + 1 + 2 + 1 + 9;
   1097  char result[maxLength];
   1098 
   1099  size_t n = 0;
   1100 
   1101  // Steps 7-8. (Inlined FormatTimeString).
   1102  result[n++] = sign;
   1103  result[n++] = char('0' + (hour / 10));
   1104  result[n++] = char('0' + (hour % 10));
   1105  result[n++] = ':';
   1106  result[n++] = char('0' + (minute / 10));
   1107  result[n++] = char('0' + (minute % 10));
   1108 
   1109  if (second != 0 || subSecondNanoseconds != 0) {
   1110    result[n++] = ':';
   1111    result[n++] = char('0' + (second / 10));
   1112    result[n++] = char('0' + (second % 10));
   1113 
   1114    if (uint32_t fractional = subSecondNanoseconds) {
   1115      result[n++] = '.';
   1116 
   1117      uint32_t k = 100'000'000;
   1118      do {
   1119        result[n++] = char('0' + (fractional / k));
   1120        fractional %= k;
   1121        k /= 10;
   1122      } while (fractional);
   1123    }
   1124  }
   1125 
   1126  MOZ_ASSERT(n <= maxLength);
   1127 
   1128  // Step 9.
   1129  return NewStringCopyN<CanGC>(cx, result, n);
   1130 }
   1131 
   1132 /**
   1133 * Temporal.ZonedDateTime ( epochNanoseconds, timeZone [ , calendar ] )
   1134 */
   1135 static bool ZonedDateTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
   1136  CallArgs args = CallArgsFromVp(argc, vp);
   1137 
   1138  // Step 1.
   1139  if (!ThrowIfNotConstructing(cx, args, "Temporal.ZonedDateTime")) {
   1140    return false;
   1141  }
   1142 
   1143  // Step 2.
   1144  Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
   1145  if (!epochNanoseconds) {
   1146    return false;
   1147  }
   1148 
   1149  // Step 3.
   1150  if (!IsValidEpochNanoseconds(epochNanoseconds)) {
   1151    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1152                              JSMSG_TEMPORAL_INSTANT_INVALID);
   1153    return false;
   1154  }
   1155 
   1156  // Step 4.
   1157  if (!args.get(1).isString()) {
   1158    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, args.get(1),
   1159                     nullptr, "not a string");
   1160    return false;
   1161  }
   1162 
   1163  // Step 5.
   1164  Rooted<JSString*> timeZoneString(cx, args[1].toString());
   1165  Rooted<ParsedTimeZone> timeZoneParse(cx);
   1166  if (!ParseTimeZoneIdentifier(cx, timeZoneString, &timeZoneParse)) {
   1167    return false;
   1168  }
   1169 
   1170  // Steps 6-7.
   1171  Rooted<TimeZoneValue> timeZone(cx);
   1172  if (!ToTemporalTimeZone(cx, timeZoneParse, &timeZone)) {
   1173    return false;
   1174  }
   1175 
   1176  // Steps 8-10.
   1177  Rooted<CalendarValue> calendar(cx, CalendarValue(CalendarId::ISO8601));
   1178  if (args.hasDefined(2)) {
   1179    // Step 9.
   1180    if (!args[2].isString()) {
   1181      ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, args[2],
   1182                       nullptr, "not a string");
   1183      return false;
   1184    }
   1185 
   1186    // Step 10.
   1187    Rooted<JSString*> calendarString(cx, args[2].toString());
   1188    if (!CanonicalizeCalendar(cx, calendarString, &calendar)) {
   1189      return false;
   1190    }
   1191  }
   1192 
   1193  // Step 11.
   1194  auto* obj = CreateTemporalZonedDateTime(cx, args, epochNanoseconds, timeZone,
   1195                                          calendar);
   1196  if (!obj) {
   1197    return false;
   1198  }
   1199 
   1200  args.rval().setObject(*obj);
   1201  return true;
   1202 }
   1203 
   1204 /**
   1205 * Temporal.ZonedDateTime.from ( item [ , options ] )
   1206 */
   1207 static bool ZonedDateTime_from(JSContext* cx, unsigned argc, Value* vp) {
   1208  CallArgs args = CallArgsFromVp(argc, vp);
   1209 
   1210  // Step 1.
   1211  Rooted<ZonedDateTime> zonedDateTime(cx);
   1212  if (!ToTemporalZonedDateTime(cx, args.get(0), args.get(1), &zonedDateTime)) {
   1213    return false;
   1214  }
   1215 
   1216  auto* result = CreateTemporalZonedDateTime(cx, zonedDateTime);
   1217  if (!result) {
   1218    return false;
   1219  }
   1220 
   1221  args.rval().setObject(*result);
   1222  return true;
   1223 }
   1224 
   1225 /**
   1226 * Temporal.ZonedDateTime.compare ( one, two )
   1227 */
   1228 static bool ZonedDateTime_compare(JSContext* cx, unsigned argc, Value* vp) {
   1229  CallArgs args = CallArgsFromVp(argc, vp);
   1230 
   1231  // Step 1.
   1232  Rooted<ZonedDateTime> one(cx);
   1233  if (!ToTemporalZonedDateTime(cx, args.get(0), &one)) {
   1234    return false;
   1235  }
   1236 
   1237  // Step 2.
   1238  Rooted<ZonedDateTime> two(cx);
   1239  if (!ToTemporalZonedDateTime(cx, args.get(1), &two)) {
   1240    return false;
   1241  }
   1242 
   1243  // Step 3.
   1244  const auto& oneNs = one.epochNanoseconds();
   1245  const auto& twoNs = two.epochNanoseconds();
   1246  args.rval().setInt32(oneNs > twoNs ? 1 : oneNs < twoNs ? -1 : 0);
   1247  return true;
   1248 }
   1249 
   1250 /**
   1251 * get Temporal.ZonedDateTime.prototype.calendarId
   1252 */
   1253 static bool ZonedDateTime_calendarId(JSContext* cx, const CallArgs& args) {
   1254  auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
   1255 
   1256  // Step 3.
   1257  auto* str =
   1258      NewStringCopy<CanGC>(cx, CalendarIdentifier(zonedDateTime->calendar()));
   1259  if (!str) {
   1260    return false;
   1261  }
   1262 
   1263  args.rval().setString(str);
   1264  return true;
   1265 }
   1266 
   1267 /**
   1268 * get Temporal.ZonedDateTime.prototype.calendarId
   1269 */
   1270 static bool ZonedDateTime_calendarId(JSContext* cx, unsigned argc, Value* vp) {
   1271  // Steps 1-2.
   1272  CallArgs args = CallArgsFromVp(argc, vp);
   1273  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_calendarId>(cx,
   1274                                                                         args);
   1275 }
   1276 
   1277 /**
   1278 * get Temporal.ZonedDateTime.prototype.timeZoneId
   1279 */
   1280 static bool ZonedDateTime_timeZoneId(JSContext* cx, const CallArgs& args) {
   1281  auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
   1282 
   1283  // Step 3.
   1284  args.rval().setString(zonedDateTime->timeZone().identifier());
   1285  return true;
   1286 }
   1287 
   1288 /**
   1289 * get Temporal.ZonedDateTime.prototype.timeZoneId
   1290 */
   1291 static bool ZonedDateTime_timeZoneId(JSContext* cx, unsigned argc, Value* vp) {
   1292  // Steps 1-2.
   1293  CallArgs args = CallArgsFromVp(argc, vp);
   1294  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_timeZoneId>(cx,
   1295                                                                         args);
   1296 }
   1297 
   1298 /**
   1299 * get Temporal.ZonedDateTime.prototype.era
   1300 */
   1301 static bool ZonedDateTime_era(JSContext* cx, const CallArgs& args) {
   1302  Rooted<ZonedDateTime> zonedDateTime(
   1303      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1304 
   1305  // Step 3.
   1306  ISODateTime dateTime;
   1307  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1308                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1309    return false;
   1310  }
   1311 
   1312  // Step 4.
   1313  return CalendarEra(cx, zonedDateTime.calendar(), dateTime.date, args.rval());
   1314 }
   1315 
   1316 /**
   1317 * get Temporal.ZonedDateTime.prototype.era
   1318 */
   1319 static bool ZonedDateTime_era(JSContext* cx, unsigned argc, Value* vp) {
   1320  // Steps 1-2.
   1321  CallArgs args = CallArgsFromVp(argc, vp);
   1322  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_era>(cx, args);
   1323 }
   1324 
   1325 /**
   1326 * get Temporal.ZonedDateTime.prototype.eraYear
   1327 */
   1328 static bool ZonedDateTime_eraYear(JSContext* cx, const CallArgs& args) {
   1329  Rooted<ZonedDateTime> zonedDateTime(
   1330      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1331 
   1332  // Step 3.
   1333  ISODateTime dateTime;
   1334  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1335                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1336    return false;
   1337  }
   1338 
   1339  // Steps 4-6.
   1340  return CalendarEraYear(cx, zonedDateTime.calendar(), dateTime.date,
   1341                         args.rval());
   1342 }
   1343 
   1344 /**
   1345 * get Temporal.ZonedDateTime.prototype.eraYear
   1346 */
   1347 static bool ZonedDateTime_eraYear(JSContext* cx, unsigned argc, Value* vp) {
   1348  // Steps 1-2.
   1349  CallArgs args = CallArgsFromVp(argc, vp);
   1350  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_eraYear>(cx, args);
   1351 }
   1352 
   1353 /**
   1354 * get Temporal.ZonedDateTime.prototype.year
   1355 */
   1356 static bool ZonedDateTime_year(JSContext* cx, const CallArgs& args) {
   1357  Rooted<ZonedDateTime> zonedDateTime(
   1358      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1359 
   1360  // Step 3.
   1361  ISODateTime dateTime;
   1362  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1363                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1364    return false;
   1365  }
   1366 
   1367  // Step 4.
   1368  return CalendarYear(cx, zonedDateTime.calendar(), dateTime.date, args.rval());
   1369 }
   1370 
   1371 /**
   1372 * get Temporal.ZonedDateTime.prototype.year
   1373 */
   1374 static bool ZonedDateTime_year(JSContext* cx, unsigned argc, Value* vp) {
   1375  // Steps 1-2.
   1376  CallArgs args = CallArgsFromVp(argc, vp);
   1377  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_year>(cx, args);
   1378 }
   1379 
   1380 /**
   1381 * get Temporal.ZonedDateTime.prototype.month
   1382 */
   1383 static bool ZonedDateTime_month(JSContext* cx, const CallArgs& args) {
   1384  Rooted<ZonedDateTime> zonedDateTime(
   1385      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1386 
   1387  // Step 3.
   1388  ISODateTime dateTime;
   1389  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1390                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1391    return false;
   1392  }
   1393 
   1394  // Step 4.
   1395  return CalendarMonth(cx, zonedDateTime.calendar(), dateTime.date,
   1396                       args.rval());
   1397 }
   1398 
   1399 /**
   1400 * get Temporal.ZonedDateTime.prototype.month
   1401 */
   1402 static bool ZonedDateTime_month(JSContext* cx, unsigned argc, Value* vp) {
   1403  // Steps 1-2.
   1404  CallArgs args = CallArgsFromVp(argc, vp);
   1405  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_month>(cx, args);
   1406 }
   1407 
   1408 /**
   1409 * get Temporal.ZonedDateTime.prototype.monthCode
   1410 */
   1411 static bool ZonedDateTime_monthCode(JSContext* cx, const CallArgs& args) {
   1412  Rooted<ZonedDateTime> zonedDateTime(
   1413      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1414 
   1415  // Step 3.
   1416  ISODateTime dateTime;
   1417  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1418                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1419    return false;
   1420  }
   1421 
   1422  // Step 4.
   1423  return CalendarMonthCode(cx, zonedDateTime.calendar(), dateTime.date,
   1424                           args.rval());
   1425 }
   1426 
   1427 /**
   1428 * get Temporal.ZonedDateTime.prototype.monthCode
   1429 */
   1430 static bool ZonedDateTime_monthCode(JSContext* cx, unsigned argc, Value* vp) {
   1431  // Steps 1-2.
   1432  CallArgs args = CallArgsFromVp(argc, vp);
   1433  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthCode>(cx,
   1434                                                                        args);
   1435 }
   1436 
   1437 /**
   1438 * get Temporal.ZonedDateTime.prototype.day
   1439 */
   1440 static bool ZonedDateTime_day(JSContext* cx, const CallArgs& args) {
   1441  Rooted<ZonedDateTime> zonedDateTime(
   1442      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1443 
   1444  // Step 3.
   1445  ISODateTime dateTime;
   1446  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1447                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1448    return false;
   1449  }
   1450 
   1451  // Step 4.
   1452  return CalendarDay(cx, zonedDateTime.calendar(), dateTime.date, args.rval());
   1453 }
   1454 
   1455 /**
   1456 * get Temporal.ZonedDateTime.prototype.day
   1457 */
   1458 static bool ZonedDateTime_day(JSContext* cx, unsigned argc, Value* vp) {
   1459  // Steps 1-2.
   1460  CallArgs args = CallArgsFromVp(argc, vp);
   1461  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_day>(cx, args);
   1462 }
   1463 
   1464 /**
   1465 * get Temporal.ZonedDateTime.prototype.hour
   1466 */
   1467 static bool ZonedDateTime_hour(JSContext* cx, const CallArgs& args) {
   1468  Rooted<ZonedDateTime> zonedDateTime(
   1469      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1470 
   1471  // Step 3.
   1472  ISODateTime dateTime;
   1473  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1474                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1475    return false;
   1476  }
   1477 
   1478  // Step 4.
   1479  args.rval().setInt32(dateTime.time.hour);
   1480  return true;
   1481 }
   1482 
   1483 /**
   1484 * get Temporal.ZonedDateTime.prototype.hour
   1485 */
   1486 static bool ZonedDateTime_hour(JSContext* cx, unsigned argc, Value* vp) {
   1487  // Steps 1-2.
   1488  CallArgs args = CallArgsFromVp(argc, vp);
   1489  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hour>(cx, args);
   1490 }
   1491 
   1492 /**
   1493 * get Temporal.ZonedDateTime.prototype.minute
   1494 */
   1495 static bool ZonedDateTime_minute(JSContext* cx, const CallArgs& args) {
   1496  Rooted<ZonedDateTime> zonedDateTime(
   1497      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1498 
   1499  // Step 3.
   1500  ISODateTime dateTime;
   1501  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1502                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1503    return false;
   1504  }
   1505 
   1506  // Step 4.
   1507  args.rval().setInt32(dateTime.time.minute);
   1508  return true;
   1509 }
   1510 
   1511 /**
   1512 * get Temporal.ZonedDateTime.prototype.minute
   1513 */
   1514 static bool ZonedDateTime_minute(JSContext* cx, unsigned argc, Value* vp) {
   1515  // Steps 1-2.
   1516  CallArgs args = CallArgsFromVp(argc, vp);
   1517  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_minute>(cx, args);
   1518 }
   1519 
   1520 /**
   1521 * get Temporal.ZonedDateTime.prototype.second
   1522 */
   1523 static bool ZonedDateTime_second(JSContext* cx, const CallArgs& args) {
   1524  Rooted<ZonedDateTime> zonedDateTime(
   1525      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1526 
   1527  // Step 3.
   1528  ISODateTime dateTime;
   1529  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1530                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1531    return false;
   1532  }
   1533 
   1534  // Step 4.
   1535  args.rval().setInt32(dateTime.time.second);
   1536  return true;
   1537 }
   1538 
   1539 /**
   1540 * get Temporal.ZonedDateTime.prototype.second
   1541 */
   1542 static bool ZonedDateTime_second(JSContext* cx, unsigned argc, Value* vp) {
   1543  // Steps 1-2.
   1544  CallArgs args = CallArgsFromVp(argc, vp);
   1545  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_second>(cx, args);
   1546 }
   1547 
   1548 /**
   1549 * get Temporal.ZonedDateTime.prototype.millisecond
   1550 */
   1551 static bool ZonedDateTime_millisecond(JSContext* cx, const CallArgs& args) {
   1552  Rooted<ZonedDateTime> zonedDateTime(
   1553      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1554 
   1555  // Step 3.
   1556  ISODateTime dateTime;
   1557  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1558                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1559    return false;
   1560  }
   1561 
   1562  // Step 4.
   1563  args.rval().setInt32(dateTime.time.millisecond);
   1564  return true;
   1565 }
   1566 
   1567 /**
   1568 * get Temporal.ZonedDateTime.prototype.millisecond
   1569 */
   1570 static bool ZonedDateTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
   1571  // Steps 1-2.
   1572  CallArgs args = CallArgsFromVp(argc, vp);
   1573  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_millisecond>(cx,
   1574                                                                          args);
   1575 }
   1576 
   1577 /**
   1578 * get Temporal.ZonedDateTime.prototype.microsecond
   1579 */
   1580 static bool ZonedDateTime_microsecond(JSContext* cx, const CallArgs& args) {
   1581  Rooted<ZonedDateTime> zonedDateTime(
   1582      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1583 
   1584  // Step 3.
   1585  ISODateTime dateTime;
   1586  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1587                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1588    return false;
   1589  }
   1590 
   1591  // Step 4.
   1592  args.rval().setInt32(dateTime.time.microsecond);
   1593  return true;
   1594 }
   1595 
   1596 /**
   1597 * get Temporal.ZonedDateTime.prototype.microsecond
   1598 */
   1599 static bool ZonedDateTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
   1600  // Steps 1-2.
   1601  CallArgs args = CallArgsFromVp(argc, vp);
   1602  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_microsecond>(cx,
   1603                                                                          args);
   1604 }
   1605 
   1606 /**
   1607 * get Temporal.ZonedDateTime.prototype.nanosecond
   1608 */
   1609 static bool ZonedDateTime_nanosecond(JSContext* cx, const CallArgs& args) {
   1610  Rooted<ZonedDateTime> zonedDateTime(
   1611      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1612 
   1613  // Step 3.
   1614  ISODateTime dateTime;
   1615  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1616                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1617    return false;
   1618  }
   1619 
   1620  // Step 4.
   1621  args.rval().setInt32(dateTime.time.nanosecond);
   1622  return true;
   1623 }
   1624 
   1625 /**
   1626 * get Temporal.ZonedDateTime.prototype.nanosecond
   1627 */
   1628 static bool ZonedDateTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
   1629  // Steps 1-2.
   1630  CallArgs args = CallArgsFromVp(argc, vp);
   1631  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_nanosecond>(cx,
   1632                                                                         args);
   1633 }
   1634 
   1635 /**
   1636 * get Temporal.ZonedDateTime.prototype.epochMilliseconds
   1637 */
   1638 static bool ZonedDateTime_epochMilliseconds(JSContext* cx,
   1639                                            const CallArgs& args) {
   1640  auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
   1641 
   1642  // Step 3.
   1643  auto epochNs = zonedDateTime->epochNanoseconds();
   1644 
   1645  // Steps 4-5.
   1646  args.rval().setNumber(epochNs.floorToMilliseconds());
   1647  return true;
   1648 }
   1649 
   1650 /**
   1651 * get Temporal.ZonedDateTime.prototype.epochMilliseconds
   1652 */
   1653 static bool ZonedDateTime_epochMilliseconds(JSContext* cx, unsigned argc,
   1654                                            Value* vp) {
   1655  // Steps 1-2.
   1656  CallArgs args = CallArgsFromVp(argc, vp);
   1657  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochMilliseconds>(
   1658      cx, args);
   1659 }
   1660 
   1661 /**
   1662 * get Temporal.ZonedDateTime.prototype.epochNanoseconds
   1663 */
   1664 static bool ZonedDateTime_epochNanoseconds(JSContext* cx,
   1665                                           const CallArgs& args) {
   1666  auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
   1667 
   1668  // Step 3.
   1669  auto* nanoseconds = ToBigInt(cx, zonedDateTime->epochNanoseconds());
   1670  if (!nanoseconds) {
   1671    return false;
   1672  }
   1673 
   1674  args.rval().setBigInt(nanoseconds);
   1675  return true;
   1676 }
   1677 
   1678 /**
   1679 * get Temporal.ZonedDateTime.prototype.epochNanoseconds
   1680 */
   1681 static bool ZonedDateTime_epochNanoseconds(JSContext* cx, unsigned argc,
   1682                                           Value* vp) {
   1683  // Steps 1-2.
   1684  CallArgs args = CallArgsFromVp(argc, vp);
   1685  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochNanoseconds>(
   1686      cx, args);
   1687 }
   1688 
   1689 /**
   1690 * get Temporal.ZonedDateTime.prototype.dayOfWeek
   1691 */
   1692 static bool ZonedDateTime_dayOfWeek(JSContext* cx, const CallArgs& args) {
   1693  Rooted<ZonedDateTime> zonedDateTime(
   1694      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1695 
   1696  // Step 3.
   1697  ISODateTime dateTime;
   1698  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1699                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1700    return false;
   1701  }
   1702 
   1703  // Step 4.
   1704  return CalendarDayOfWeek(cx, zonedDateTime.calendar(), dateTime.date,
   1705                           args.rval());
   1706 }
   1707 
   1708 /**
   1709 * get Temporal.ZonedDateTime.prototype.dayOfWeek
   1710 */
   1711 static bool ZonedDateTime_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
   1712  // Steps 1-2.
   1713  CallArgs args = CallArgsFromVp(argc, vp);
   1714  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_dayOfWeek>(cx,
   1715                                                                        args);
   1716 }
   1717 
   1718 /**
   1719 * get Temporal.ZonedDateTime.prototype.dayOfYear
   1720 */
   1721 static bool ZonedDateTime_dayOfYear(JSContext* cx, const CallArgs& args) {
   1722  Rooted<ZonedDateTime> zonedDateTime(
   1723      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1724 
   1725  // Step 3.
   1726  ISODateTime dateTime;
   1727  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1728                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1729    return false;
   1730  }
   1731 
   1732  // Step 4.
   1733  return CalendarDayOfYear(cx, zonedDateTime.calendar(), dateTime.date,
   1734                           args.rval());
   1735 }
   1736 
   1737 /**
   1738 * get Temporal.ZonedDateTime.prototype.dayOfYear
   1739 */
   1740 static bool ZonedDateTime_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
   1741  // Steps 1-2.
   1742  CallArgs args = CallArgsFromVp(argc, vp);
   1743  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_dayOfYear>(cx,
   1744                                                                        args);
   1745 }
   1746 
   1747 /**
   1748 * get Temporal.ZonedDateTime.prototype.weekOfYear
   1749 */
   1750 static bool ZonedDateTime_weekOfYear(JSContext* cx, const CallArgs& args) {
   1751  Rooted<ZonedDateTime> zonedDateTime(
   1752      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1753 
   1754  // Step 3.
   1755  ISODateTime dateTime;
   1756  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1757                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1758    return false;
   1759  }
   1760 
   1761  // Steps 4-6.
   1762  return CalendarWeekOfYear(cx, zonedDateTime.calendar(), dateTime.date,
   1763                            args.rval());
   1764 }
   1765 
   1766 /**
   1767 * get Temporal.ZonedDateTime.prototype.weekOfYear
   1768 */
   1769 static bool ZonedDateTime_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
   1770  // Steps 1-2.
   1771  CallArgs args = CallArgsFromVp(argc, vp);
   1772  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_weekOfYear>(cx,
   1773                                                                         args);
   1774 }
   1775 
   1776 /**
   1777 * get Temporal.ZonedDateTime.prototype.yearOfWeek
   1778 */
   1779 static bool ZonedDateTime_yearOfWeek(JSContext* cx, const CallArgs& args) {
   1780  Rooted<ZonedDateTime> zonedDateTime(
   1781      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1782 
   1783  // Step 3.
   1784  ISODateTime dateTime;
   1785  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1786                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1787    return false;
   1788  }
   1789 
   1790  // Steps 4-6.
   1791  return CalendarYearOfWeek(cx, zonedDateTime.calendar(), dateTime.date,
   1792                            args.rval());
   1793 }
   1794 
   1795 /**
   1796 * get Temporal.ZonedDateTime.prototype.yearOfWeek
   1797 */
   1798 static bool ZonedDateTime_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
   1799  // Steps 1-2.
   1800  CallArgs args = CallArgsFromVp(argc, vp);
   1801  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_yearOfWeek>(cx,
   1802                                                                         args);
   1803 }
   1804 
   1805 /**
   1806 * get Temporal.ZonedDateTime.prototype.hoursInDay
   1807 */
   1808 static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) {
   1809  Rooted<ZonedDateTime> zonedDateTime(
   1810      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1811 
   1812  // Step 3.
   1813  auto timeZone = zonedDateTime.timeZone();
   1814 
   1815  // Step 4.
   1816  ISODateTime dateTime;
   1817  if (!GetISODateTimeFor(cx, timeZone, zonedDateTime.epochNanoseconds(),
   1818                         &dateTime)) {
   1819    return false;
   1820  }
   1821 
   1822  // Step 5.
   1823  const auto& today = dateTime.date;
   1824 
   1825  // Step 6.
   1826  auto tomorrow = BalanceISODate(today, 1);
   1827  if (!ISODateWithinLimits(tomorrow)) {
   1828    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1829                              JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   1830    return false;
   1831  }
   1832 
   1833  // Step 7.
   1834  EpochNanoseconds todayNs;
   1835  if (!GetStartOfDay(cx, timeZone, today, &todayNs)) {
   1836    return false;
   1837  }
   1838 
   1839  // Step 8.
   1840  EpochNanoseconds tomorrowNs;
   1841  if (!GetStartOfDay(cx, timeZone, tomorrow, &tomorrowNs)) {
   1842    return false;
   1843  }
   1844 
   1845  // Step 9.
   1846  auto diff = tomorrowNs - todayNs;
   1847  MOZ_ASSERT(diff.abs() <= EpochDuration::fromDays(2),
   1848             "maximum day length for repeated days doesn't exceed two days");
   1849 
   1850  static_assert(EpochDuration::fromDays(2).toNanoseconds() < Int128{INT64_MAX},
   1851                "two days in nanoseconds fits into int64_t");
   1852 
   1853  // Step 10. (Inlined TotalTimeDuration)
   1854  constexpr auto nsPerHour = ToNanoseconds(TemporalUnit::Hour);
   1855  args.rval().setNumber(
   1856      FractionToDouble(int64_t(diff.toNanoseconds()), nsPerHour));
   1857  return true;
   1858 }
   1859 
   1860 /**
   1861 * get Temporal.ZonedDateTime.prototype.hoursInDay
   1862 */
   1863 static bool ZonedDateTime_hoursInDay(JSContext* cx, unsigned argc, Value* vp) {
   1864  // Steps 1-2.
   1865  CallArgs args = CallArgsFromVp(argc, vp);
   1866  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hoursInDay>(cx,
   1867                                                                         args);
   1868 }
   1869 
   1870 /**
   1871 * get Temporal.ZonedDateTime.prototype.daysInWeek
   1872 */
   1873 static bool ZonedDateTime_daysInWeek(JSContext* cx, const CallArgs& args) {
   1874  Rooted<ZonedDateTime> zonedDateTime(
   1875      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1876 
   1877  // Step 3.
   1878  ISODateTime dateTime;
   1879  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1880                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1881    return false;
   1882  }
   1883 
   1884  // Step 4.
   1885  return CalendarDaysInWeek(cx, zonedDateTime.calendar(), dateTime.date,
   1886                            args.rval());
   1887 }
   1888 
   1889 /**
   1890 * get Temporal.ZonedDateTime.prototype.daysInWeek
   1891 */
   1892 static bool ZonedDateTime_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
   1893  // Steps 1-2.
   1894  CallArgs args = CallArgsFromVp(argc, vp);
   1895  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInWeek>(cx,
   1896                                                                         args);
   1897 }
   1898 
   1899 /**
   1900 * get Temporal.ZonedDateTime.prototype.daysInMonth
   1901 */
   1902 static bool ZonedDateTime_daysInMonth(JSContext* cx, const CallArgs& args) {
   1903  Rooted<ZonedDateTime> zonedDateTime(
   1904      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1905 
   1906  // Step 3.
   1907  ISODateTime dateTime;
   1908  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1909                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1910    return false;
   1911  }
   1912 
   1913  // Step 4.
   1914  return CalendarDaysInMonth(cx, zonedDateTime.calendar(), dateTime.date,
   1915                             args.rval());
   1916 }
   1917 
   1918 /**
   1919 * get Temporal.ZonedDateTime.prototype.daysInMonth
   1920 */
   1921 static bool ZonedDateTime_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
   1922  // Steps 1-2.
   1923  CallArgs args = CallArgsFromVp(argc, vp);
   1924  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInMonth>(cx,
   1925                                                                          args);
   1926 }
   1927 
   1928 /**
   1929 * get Temporal.ZonedDateTime.prototype.daysInYear
   1930 */
   1931 static bool ZonedDateTime_daysInYear(JSContext* cx, const CallArgs& args) {
   1932  Rooted<ZonedDateTime> zonedDateTime(
   1933      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1934 
   1935  // Step 3.
   1936  ISODateTime dateTime;
   1937  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1938                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1939    return false;
   1940  }
   1941 
   1942  // Step 4.
   1943  return CalendarDaysInYear(cx, zonedDateTime.calendar(), dateTime.date,
   1944                            args.rval());
   1945 }
   1946 
   1947 /**
   1948 * get Temporal.ZonedDateTime.prototype.daysInYear
   1949 */
   1950 static bool ZonedDateTime_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
   1951  // Steps 1-2.
   1952  CallArgs args = CallArgsFromVp(argc, vp);
   1953  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInYear>(cx,
   1954                                                                         args);
   1955 }
   1956 
   1957 /**
   1958 * get Temporal.ZonedDateTime.prototype.monthsInYear
   1959 */
   1960 static bool ZonedDateTime_monthsInYear(JSContext* cx, const CallArgs& args) {
   1961  Rooted<ZonedDateTime> zonedDateTime(
   1962      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1963 
   1964  // Step 3.
   1965  ISODateTime dateTime;
   1966  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1967                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1968    return false;
   1969  }
   1970 
   1971  // Step 4.
   1972  return CalendarMonthsInYear(cx, zonedDateTime.calendar(), dateTime.date,
   1973                              args.rval());
   1974 }
   1975 
   1976 /**
   1977 * get Temporal.ZonedDateTime.prototype.monthsInYear
   1978 */
   1979 static bool ZonedDateTime_monthsInYear(JSContext* cx, unsigned argc,
   1980                                       Value* vp) {
   1981  // Steps 1-2.
   1982  CallArgs args = CallArgsFromVp(argc, vp);
   1983  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthsInYear>(
   1984      cx, args);
   1985 }
   1986 
   1987 /**
   1988 * get Temporal.ZonedDateTime.prototype.inLeapYear
   1989 */
   1990 static bool ZonedDateTime_inLeapYear(JSContext* cx, const CallArgs& args) {
   1991  Rooted<ZonedDateTime> zonedDateTime(
   1992      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   1993 
   1994  // Step 3.
   1995  ISODateTime dateTime;
   1996  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   1997                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   1998    return false;
   1999  }
   2000 
   2001  // Step 4.
   2002  return CalendarInLeapYear(cx, zonedDateTime.calendar(), dateTime.date,
   2003                            args.rval());
   2004 }
   2005 
   2006 /**
   2007 * get Temporal.ZonedDateTime.prototype.inLeapYear
   2008 */
   2009 static bool ZonedDateTime_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
   2010  // Steps 1-2.
   2011  CallArgs args = CallArgsFromVp(argc, vp);
   2012  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_inLeapYear>(cx,
   2013                                                                         args);
   2014 }
   2015 
   2016 /**
   2017 * get Temporal.ZonedDateTime.prototype.offsetNanoseconds
   2018 */
   2019 static bool ZonedDateTime_offsetNanoseconds(JSContext* cx,
   2020                                            const CallArgs& args) {
   2021  Rooted<ZonedDateTime> zonedDateTime(
   2022      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2023 
   2024  // Step 3.
   2025  int64_t offsetNanoseconds;
   2026  if (!GetOffsetNanosecondsFor(cx, zonedDateTime.timeZone(),
   2027                               zonedDateTime.epochNanoseconds(),
   2028                               &offsetNanoseconds)) {
   2029    return false;
   2030  }
   2031  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
   2032 
   2033  args.rval().setNumber(offsetNanoseconds);
   2034  return true;
   2035 }
   2036 
   2037 /**
   2038 * get Temporal.ZonedDateTime.prototype.offsetNanoseconds
   2039 */
   2040 static bool ZonedDateTime_offsetNanoseconds(JSContext* cx, unsigned argc,
   2041                                            Value* vp) {
   2042  // Steps 1-2.
   2043  CallArgs args = CallArgsFromVp(argc, vp);
   2044  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_offsetNanoseconds>(
   2045      cx, args);
   2046 }
   2047 
   2048 /**
   2049 * get Temporal.ZonedDateTime.prototype.offset
   2050 */
   2051 static bool ZonedDateTime_offset(JSContext* cx, const CallArgs& args) {
   2052  Rooted<ZonedDateTime> zonedDateTime(
   2053      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2054 
   2055  // Step 3.
   2056  int64_t offsetNanoseconds;
   2057  if (!GetOffsetNanosecondsFor(cx, zonedDateTime.timeZone(),
   2058                               zonedDateTime.epochNanoseconds(),
   2059                               &offsetNanoseconds)) {
   2060    return false;
   2061  }
   2062  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
   2063 
   2064  // Step 4.
   2065  JSString* str = FormatUTCOffsetNanoseconds(cx, offsetNanoseconds);
   2066  if (!str) {
   2067    return false;
   2068  }
   2069 
   2070  args.rval().setString(str);
   2071  return true;
   2072 }
   2073 
   2074 /**
   2075 * get Temporal.ZonedDateTime.prototype.offset
   2076 */
   2077 static bool ZonedDateTime_offset(JSContext* cx, unsigned argc, Value* vp) {
   2078  // Steps 1-2.
   2079  CallArgs args = CallArgsFromVp(argc, vp);
   2080  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_offset>(cx, args);
   2081 }
   2082 
   2083 /**
   2084 * Temporal.ZonedDateTime.prototype.with ( temporalZonedDateTimeLike [ , options
   2085 * ] )
   2086 */
   2087 static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
   2088  Rooted<ZonedDateTime> zonedDateTime(
   2089      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2090 
   2091  // Step 3.
   2092  Rooted<JSObject*> temporalZonedDateTimeLike(
   2093      cx,
   2094      RequireObjectArg(cx, "temporalZonedDateTimeLike", "with", args.get(0)));
   2095  if (!temporalZonedDateTimeLike) {
   2096    return false;
   2097  }
   2098  if (!ThrowIfTemporalLikeObject(cx, temporalZonedDateTimeLike)) {
   2099    return false;
   2100  }
   2101 
   2102  // Step 4.
   2103  const auto& epochNs = zonedDateTime.epochNanoseconds();
   2104 
   2105  // Step 5.
   2106  auto timeZone = zonedDateTime.timeZone();
   2107 
   2108  // Step 6.
   2109  auto calendar = zonedDateTime.calendar();
   2110 
   2111  // Step 7.
   2112  int64_t offsetNanoseconds;
   2113  if (!GetOffsetNanosecondsFor(cx, timeZone, epochNs, &offsetNanoseconds)) {
   2114    return false;
   2115  }
   2116 
   2117  // Step 8.
   2118  auto dateTime = GetISODateTimeFor(epochNs, offsetNanoseconds);
   2119  MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
   2120 
   2121  // Step 9.
   2122  Rooted<PlainDate> date(cx, PlainDate{dateTime.date, calendar});
   2123  Rooted<CalendarFields> fields(cx);
   2124  if (!ISODateToFields(cx, date, &fields)) {
   2125    return false;
   2126  }
   2127 
   2128  // Steps 10-16.
   2129  fields.setHour(dateTime.time.hour);
   2130  fields.setMinute(dateTime.time.minute);
   2131  fields.setSecond(dateTime.time.second);
   2132  fields.setMillisecond(dateTime.time.millisecond);
   2133  fields.setMicrosecond(dateTime.time.microsecond);
   2134  fields.setNanosecond(dateTime.time.nanosecond);
   2135  fields.setOffset(OffsetField{offsetNanoseconds});
   2136 
   2137  // Step 17.
   2138  Rooted<CalendarFields> partialZonedDateTime(cx);
   2139  if (!PreparePartialCalendarFields(cx, calendar, temporalZonedDateTimeLike,
   2140                                    {
   2141                                        CalendarField::Year,
   2142                                        CalendarField::Month,
   2143                                        CalendarField::MonthCode,
   2144                                        CalendarField::Day,
   2145                                        CalendarField::Hour,
   2146                                        CalendarField::Minute,
   2147                                        CalendarField::Second,
   2148                                        CalendarField::Millisecond,
   2149                                        CalendarField::Microsecond,
   2150                                        CalendarField::Nanosecond,
   2151                                        CalendarField::Offset,
   2152                                    },
   2153                                    &partialZonedDateTime)) {
   2154    return false;
   2155  }
   2156  MOZ_ASSERT(!partialZonedDateTime.keys().isEmpty());
   2157 
   2158  // Step 18.
   2159  fields = CalendarMergeFields(calendar, fields, partialZonedDateTime);
   2160 
   2161  // Steps 19-22.
   2162  auto disambiguation = TemporalDisambiguation::Compatible;
   2163  auto offset = TemporalOffset::Prefer;
   2164  auto overflow = TemporalOverflow::Constrain;
   2165  if (args.hasDefined(1)) {
   2166    // Step 19.
   2167    Rooted<JSObject*> options(cx,
   2168                              RequireObjectArg(cx, "options", "with", args[1]));
   2169    if (!options) {
   2170      return false;
   2171    }
   2172 
   2173    // Step 20.
   2174    if (!GetTemporalDisambiguationOption(cx, options, &disambiguation)) {
   2175      return false;
   2176    }
   2177 
   2178    // Step 21.
   2179    if (!GetTemporalOffsetOption(cx, options, &offset)) {
   2180      return false;
   2181    }
   2182 
   2183    // Step 22.
   2184    if (!GetTemporalOverflowOption(cx, options, &overflow)) {
   2185      return false;
   2186    }
   2187  }
   2188 
   2189  // Step 23.
   2190  ISODateTime dateTimeResult;
   2191  if (!InterpretTemporalDateTimeFields(cx, calendar, fields, overflow,
   2192                                       &dateTimeResult)) {
   2193    return false;
   2194  }
   2195 
   2196  // Step 24.
   2197  int64_t newOffsetNanoseconds = int64_t(fields.offset());
   2198 
   2199  // Step 25.
   2200  EpochNanoseconds epochNanoseconds;
   2201  if (!InterpretISODateTimeOffset(
   2202          cx, dateTimeResult, OffsetBehaviour::Option, newOffsetNanoseconds,
   2203          timeZone, disambiguation, offset, MatchBehaviour::MatchExactly,
   2204          &epochNanoseconds)) {
   2205    return false;
   2206  }
   2207 
   2208  // Step 26.
   2209  auto* result =
   2210      CreateTemporalZonedDateTime(cx, epochNanoseconds, timeZone, calendar);
   2211  if (!result) {
   2212    return false;
   2213  }
   2214 
   2215  args.rval().setObject(*result);
   2216  return true;
   2217 }
   2218 
   2219 /**
   2220 * Temporal.ZonedDateTime.prototype.with ( temporalZonedDateTimeLike [ , options
   2221 * ] )
   2222 */
   2223 static bool ZonedDateTime_with(JSContext* cx, unsigned argc, Value* vp) {
   2224  // Steps 1-2.
   2225  CallArgs args = CallArgsFromVp(argc, vp);
   2226  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_with>(cx, args);
   2227 }
   2228 
   2229 /**
   2230 * Temporal.ZonedDateTime.prototype.withPlainTime ( [ plainTimeLike ] )
   2231 */
   2232 static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
   2233  Rooted<ZonedDateTime> zonedDateTime(
   2234      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2235 
   2236  // Step 3.
   2237  auto timeZone = zonedDateTime.timeZone();
   2238 
   2239  // Step 4.
   2240  auto calendar = zonedDateTime.calendar();
   2241 
   2242  // Step 5.
   2243  ISODateTime isoDateTime;
   2244  if (!GetISODateTimeFor(cx, timeZone, zonedDateTime.epochNanoseconds(),
   2245                         &isoDateTime)) {
   2246    return false;
   2247  }
   2248 
   2249  // Steps 6-7.
   2250  EpochNanoseconds epochNs;
   2251  if (!args.hasDefined(0)) {
   2252    // Step 6.a.
   2253    if (!GetStartOfDay(cx, timeZone, isoDateTime.date, &epochNs)) {
   2254      return false;
   2255    }
   2256  } else {
   2257    // Step 7.a.
   2258    Time time;
   2259    if (!ToTemporalTime(cx, args[0], &time)) {
   2260      return false;
   2261    }
   2262 
   2263    // Step 7.b.
   2264    auto resultISODateTime = ISODateTime{isoDateTime.date, time};
   2265    if (!ISODateTimeWithinLimits(resultISODateTime)) {
   2266      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2267                                JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
   2268      return false;
   2269    }
   2270 
   2271    // Step 7.c.
   2272    if (!GetEpochNanosecondsFor(cx, timeZone, resultISODateTime,
   2273                                TemporalDisambiguation::Compatible, &epochNs)) {
   2274      return false;
   2275    }
   2276  }
   2277 
   2278  // Step 8.
   2279  auto* result = CreateTemporalZonedDateTime(cx, epochNs, timeZone, calendar);
   2280  if (!result) {
   2281    return false;
   2282  }
   2283 
   2284  args.rval().setObject(*result);
   2285  return true;
   2286 }
   2287 
   2288 /**
   2289 * Temporal.ZonedDateTime.prototype.withPlainTime ( [ plainTimeLike ] )
   2290 */
   2291 static bool ZonedDateTime_withPlainTime(JSContext* cx, unsigned argc,
   2292                                        Value* vp) {
   2293  // Steps 1-2.
   2294  CallArgs args = CallArgsFromVp(argc, vp);
   2295  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withPlainTime>(
   2296      cx, args);
   2297 }
   2298 
   2299 /**
   2300 * Temporal.ZonedDateTime.prototype.withTimeZone ( timeZoneLike )
   2301 */
   2302 static bool ZonedDateTime_withTimeZone(JSContext* cx, const CallArgs& args) {
   2303  Rooted<ZonedDateTime> zonedDateTime(
   2304      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2305 
   2306  // Step 3.
   2307  Rooted<TimeZoneValue> timeZone(cx);
   2308  if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
   2309    return false;
   2310  }
   2311 
   2312  // Step 4.
   2313  auto* result = CreateTemporalZonedDateTime(
   2314      cx, zonedDateTime.epochNanoseconds(), timeZone, zonedDateTime.calendar());
   2315  if (!result) {
   2316    return false;
   2317  }
   2318 
   2319  args.rval().setObject(*result);
   2320  return true;
   2321 }
   2322 
   2323 /**
   2324 * Temporal.ZonedDateTime.prototype.withTimeZone ( timeZoneLike )
   2325 */
   2326 static bool ZonedDateTime_withTimeZone(JSContext* cx, unsigned argc,
   2327                                       Value* vp) {
   2328  // Steps 1-2.
   2329  CallArgs args = CallArgsFromVp(argc, vp);
   2330  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withTimeZone>(
   2331      cx, args);
   2332 }
   2333 
   2334 /**
   2335 * Temporal.ZonedDateTime.prototype.withCalendar ( calendarLike )
   2336 */
   2337 static bool ZonedDateTime_withCalendar(JSContext* cx, const CallArgs& args) {
   2338  Rooted<ZonedDateTime> zonedDateTime(
   2339      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2340 
   2341  // Step 3.
   2342  Rooted<CalendarValue> calendar(cx);
   2343  if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
   2344    return false;
   2345  }
   2346 
   2347  // Step 4.
   2348  auto* result = CreateTemporalZonedDateTime(
   2349      cx, zonedDateTime.epochNanoseconds(), zonedDateTime.timeZone(), calendar);
   2350  if (!result) {
   2351    return false;
   2352  }
   2353 
   2354  args.rval().setObject(*result);
   2355  return true;
   2356 }
   2357 
   2358 /**
   2359 * Temporal.ZonedDateTime.prototype.withCalendar ( calendarLike )
   2360 */
   2361 static bool ZonedDateTime_withCalendar(JSContext* cx, unsigned argc,
   2362                                       Value* vp) {
   2363  // Steps 1-2.
   2364  CallArgs args = CallArgsFromVp(argc, vp);
   2365  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withCalendar>(
   2366      cx, args);
   2367 }
   2368 
   2369 /**
   2370 * Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] )
   2371 */
   2372 static bool ZonedDateTime_add(JSContext* cx, const CallArgs& args) {
   2373  // Step 3.
   2374  return AddDurationToZonedDateTime(cx, TemporalAddDuration::Add, args);
   2375 }
   2376 
   2377 /**
   2378 * Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] )
   2379 */
   2380 static bool ZonedDateTime_add(JSContext* cx, unsigned argc, Value* vp) {
   2381  // Steps 1-2.
   2382  CallArgs args = CallArgsFromVp(argc, vp);
   2383  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_add>(cx, args);
   2384 }
   2385 
   2386 /**
   2387 * Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options
   2388 * ] )
   2389 */
   2390 static bool ZonedDateTime_subtract(JSContext* cx, const CallArgs& args) {
   2391  // Step 3.
   2392  return AddDurationToZonedDateTime(cx, TemporalAddDuration::Subtract, args);
   2393 }
   2394 
   2395 /**
   2396 * Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options
   2397 * ] )
   2398 */
   2399 static bool ZonedDateTime_subtract(JSContext* cx, unsigned argc, Value* vp) {
   2400  // Steps 1-2.
   2401  CallArgs args = CallArgsFromVp(argc, vp);
   2402  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_subtract>(cx,
   2403                                                                       args);
   2404 }
   2405 
   2406 /**
   2407 * Temporal.ZonedDateTime.prototype.until ( other [ , options ] )
   2408 */
   2409 static bool ZonedDateTime_until(JSContext* cx, const CallArgs& args) {
   2410  // Step 3.
   2411  return DifferenceTemporalZonedDateTime(cx, TemporalDifference::Until, args);
   2412 }
   2413 
   2414 /**
   2415 * Temporal.ZonedDateTime.prototype.until ( other [ , options ] )
   2416 */
   2417 static bool ZonedDateTime_until(JSContext* cx, unsigned argc, Value* vp) {
   2418  // Steps 1-2.
   2419  CallArgs args = CallArgsFromVp(argc, vp);
   2420  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_until>(cx, args);
   2421 }
   2422 
   2423 /**
   2424 * Temporal.ZonedDateTime.prototype.since ( other [ , options ] )
   2425 */
   2426 static bool ZonedDateTime_since(JSContext* cx, const CallArgs& args) {
   2427  // Step 3.
   2428  return DifferenceTemporalZonedDateTime(cx, TemporalDifference::Since, args);
   2429 }
   2430 
   2431 /**
   2432 * Temporal.ZonedDateTime.prototype.since ( other [ , options ] )
   2433 */
   2434 static bool ZonedDateTime_since(JSContext* cx, unsigned argc, Value* vp) {
   2435  // Steps 1-2.
   2436  CallArgs args = CallArgsFromVp(argc, vp);
   2437  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_since>(cx, args);
   2438 }
   2439 
   2440 /**
   2441 * Temporal.ZonedDateTime.prototype.round ( roundTo )
   2442 */
   2443 static bool ZonedDateTime_round(JSContext* cx, const CallArgs& args) {
   2444  Rooted<ZonedDateTime> zonedDateTime(
   2445      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2446 
   2447  // Steps 3-13.
   2448  auto smallestUnit = TemporalUnit::Unset;
   2449  auto roundingMode = TemporalRoundingMode::HalfExpand;
   2450  auto roundingIncrement = Increment{1};
   2451  if (args.get(0).isString()) {
   2452    // Step 4. (Not applicable in our implementation.)
   2453 
   2454    // Step 9.
   2455    Rooted<JSString*> paramString(cx, args[0].toString());
   2456    if (!GetTemporalUnitValuedOption(
   2457            cx, paramString, TemporalUnitKey::SmallestUnit, &smallestUnit)) {
   2458      return false;
   2459    }
   2460 
   2461    // Step 10.
   2462    if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   2463                                   smallestUnit, TemporalUnitGroup::DayTime)) {
   2464      return false;
   2465    }
   2466 
   2467    // Steps 6-8 and 11-13. (Implicit)
   2468  } else {
   2469    // Steps 3 and 5.a
   2470    Rooted<JSObject*> roundTo(
   2471        cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
   2472    if (!roundTo) {
   2473      return false;
   2474    }
   2475 
   2476    // Steps 6-7.
   2477    if (!GetRoundingIncrementOption(cx, roundTo, &roundingIncrement)) {
   2478      return false;
   2479    }
   2480 
   2481    // Step 8.
   2482    if (!GetRoundingModeOption(cx, roundTo, &roundingMode)) {
   2483      return false;
   2484    }
   2485 
   2486    // Step 9.
   2487    if (!GetTemporalUnitValuedOption(cx, roundTo, TemporalUnitKey::SmallestUnit,
   2488                                     &smallestUnit)) {
   2489      return false;
   2490    }
   2491 
   2492    if (smallestUnit == TemporalUnit::Unset) {
   2493      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2494                                JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
   2495      return false;
   2496    }
   2497 
   2498    // Step 10.
   2499    if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   2500                                   smallestUnit, TemporalUnitGroup::DayTime)) {
   2501      return false;
   2502    }
   2503    MOZ_ASSERT(TemporalUnit::Day <= smallestUnit &&
   2504               smallestUnit <= TemporalUnit::Nanosecond);
   2505 
   2506    // Steps 11-12.
   2507    auto maximum = Increment{1};
   2508    bool inclusive = true;
   2509    if (smallestUnit > TemporalUnit::Day) {
   2510      maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
   2511      inclusive = false;
   2512    }
   2513 
   2514    // Step 13.
   2515    if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
   2516                                           inclusive)) {
   2517      return false;
   2518    }
   2519  }
   2520 
   2521  // Step 14.
   2522  if (smallestUnit == TemporalUnit::Nanosecond &&
   2523      roundingIncrement == Increment{1}) {
   2524    // Step 14.a.
   2525    auto* result = CreateTemporalZonedDateTime(
   2526        cx, zonedDateTime.epochNanoseconds(), zonedDateTime.timeZone(),
   2527        zonedDateTime.calendar());
   2528    if (!result) {
   2529      return false;
   2530    }
   2531 
   2532    args.rval().setObject(*result);
   2533    return true;
   2534  }
   2535 
   2536  // Step 15.
   2537  auto thisNs = zonedDateTime.epochNanoseconds();
   2538 
   2539  // Step 16.
   2540  auto timeZone = zonedDateTime.timeZone();
   2541 
   2542  // Step 17.
   2543  auto calendar = zonedDateTime.calendar();
   2544 
   2545  // Step 18.
   2546  ISODateTime isoDateTime;
   2547  if (!GetISODateTimeFor(cx, timeZone, thisNs, &isoDateTime)) {
   2548    return false;
   2549  }
   2550 
   2551  // Steps 19-20.
   2552  EpochNanoseconds epochNanoseconds;
   2553  if (smallestUnit == TemporalUnit::Day) {
   2554    // Step 19.a.
   2555    const auto& dateStart = isoDateTime.date;
   2556 
   2557    // Step 19.b.
   2558    auto dateEnd = BalanceISODate(dateStart, 1);
   2559    if (!ISODateWithinLimits(dateEnd)) {
   2560      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2561                                JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
   2562      return false;
   2563    }
   2564 
   2565    // Step 19.c.
   2566    EpochNanoseconds startNs;
   2567    if (!GetStartOfDay(cx, timeZone, dateStart, &startNs)) {
   2568      return false;
   2569    }
   2570 
   2571    // Step 19.d.
   2572    MOZ_ASSERT(thisNs >= startNs);
   2573 
   2574    // Step 19.e.
   2575    EpochNanoseconds endNs;
   2576    if (!GetStartOfDay(cx, timeZone, dateEnd, &endNs)) {
   2577      return false;
   2578    }
   2579 
   2580    // Step 19.f.
   2581    MOZ_ASSERT(thisNs < endNs);
   2582 
   2583    // Step 19.g.
   2584    auto dayLengthNs = endNs - startNs;
   2585    MOZ_ASSERT(IsValidEpochDuration(dayLengthNs));
   2586    MOZ_ASSERT(dayLengthNs > EpochDuration{}, "dayLengthNs is positive");
   2587 
   2588    // Step 19.h. (Inlined TimeDurationFromEpochNanosecondsDifference)
   2589    auto dayProgressNs = thisNs - startNs;
   2590    MOZ_ASSERT(IsValidEpochDuration(dayProgressNs));
   2591    MOZ_ASSERT(dayProgressNs >= EpochDuration{},
   2592               "dayProgressNs is non-negative");
   2593 
   2594    MOZ_ASSERT(startNs <= thisNs && thisNs < endNs);
   2595    MOZ_ASSERT(dayProgressNs < dayLengthNs);
   2596    MOZ_ASSERT(dayLengthNs <= EpochDuration::fromDays(2),
   2597               "maximum day length for repeated days");
   2598 
   2599    // Step 19.i. (Inlined RoundTimeDurationToIncrement)
   2600    auto rounded = RoundNumberToIncrement(
   2601        static_cast<int64_t>(dayProgressNs.toNanoseconds()),
   2602        static_cast<int64_t>(dayLengthNs.toNanoseconds()), roundingMode);
   2603    auto roundedDaysNs = EpochDuration::fromNanoseconds(rounded);
   2604    MOZ_ASSERT(roundedDaysNs == EpochDuration{} ||
   2605               roundedDaysNs == dayLengthNs);
   2606    MOZ_ASSERT(IsValidEpochDuration(roundedDaysNs));
   2607 
   2608    // Step 19.j. (Inlined AddTimeDurationToEpochNanoseconds)
   2609    epochNanoseconds = startNs + roundedDaysNs;
   2610    MOZ_ASSERT(epochNanoseconds == startNs || epochNanoseconds == endNs);
   2611  } else {
   2612    // Step 20.a.
   2613    auto roundResult = RoundISODateTime(isoDateTime, roundingIncrement,
   2614                                        smallestUnit, roundingMode);
   2615 
   2616    // Step 20.b.
   2617    int64_t offsetNanoseconds;
   2618    if (!GetOffsetNanosecondsFor(cx, timeZone, thisNs, &offsetNanoseconds)) {
   2619      return false;
   2620    }
   2621    MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
   2622 
   2623    // Step 20.c.
   2624    if (!InterpretISODateTimeOffset(
   2625            cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds,
   2626            timeZone, TemporalDisambiguation::Compatible,
   2627            TemporalOffset::Prefer, MatchBehaviour::MatchExactly,
   2628            &epochNanoseconds)) {
   2629      return false;
   2630    }
   2631  }
   2632  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
   2633 
   2634  // Step 21.
   2635  auto* result =
   2636      CreateTemporalZonedDateTime(cx, epochNanoseconds, timeZone, calendar);
   2637  if (!result) {
   2638    return false;
   2639  }
   2640 
   2641  args.rval().setObject(*result);
   2642  return true;
   2643 }
   2644 
   2645 /**
   2646 * Temporal.ZonedDateTime.prototype.round ( roundTo )
   2647 */
   2648 static bool ZonedDateTime_round(JSContext* cx, unsigned argc, Value* vp) {
   2649  // Steps 1-2.
   2650  CallArgs args = CallArgsFromVp(argc, vp);
   2651  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_round>(cx, args);
   2652 }
   2653 
   2654 /**
   2655 * Temporal.ZonedDateTime.prototype.equals ( other )
   2656 */
   2657 static bool ZonedDateTime_equals(JSContext* cx, const CallArgs& args) {
   2658  Rooted<ZonedDateTime> zonedDateTime(
   2659      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2660 
   2661  // Step 3.
   2662  Rooted<ZonedDateTime> other(cx);
   2663  if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) {
   2664    return false;
   2665  }
   2666 
   2667  // Steps 4-6.
   2668  bool equals = zonedDateTime.epochNanoseconds() == other.epochNanoseconds() &&
   2669                TimeZoneEquals(zonedDateTime.timeZone(), other.timeZone()) &&
   2670                CalendarEquals(zonedDateTime.calendar(), other.calendar());
   2671 
   2672  args.rval().setBoolean(equals);
   2673  return true;
   2674 }
   2675 
   2676 /**
   2677 * Temporal.ZonedDateTime.prototype.equals ( other )
   2678 */
   2679 static bool ZonedDateTime_equals(JSContext* cx, unsigned argc, Value* vp) {
   2680  // Steps 1-2.
   2681  CallArgs args = CallArgsFromVp(argc, vp);
   2682  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_equals>(cx, args);
   2683 }
   2684 
   2685 /**
   2686 * Temporal.ZonedDateTime.prototype.toString ( [ options ] )
   2687 */
   2688 static bool ZonedDateTime_toString(JSContext* cx, const CallArgs& args) {
   2689  Rooted<ZonedDateTime> zonedDateTime(
   2690      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2691 
   2692  SecondsStringPrecision precision = {Precision::Auto(),
   2693                                      TemporalUnit::Nanosecond, Increment{1}};
   2694  auto roundingMode = TemporalRoundingMode::Trunc;
   2695  auto showCalendar = ShowCalendar::Auto;
   2696  auto showTimeZone = ShowTimeZoneName::Auto;
   2697  auto showOffset = ShowOffset::Auto;
   2698  if (args.hasDefined(0)) {
   2699    // Step 3.
   2700    Rooted<JSObject*> options(
   2701        cx, RequireObjectArg(cx, "options", "toString", args[0]));
   2702    if (!options) {
   2703      return false;
   2704    }
   2705 
   2706    // Steps 4-5.
   2707    if (!GetTemporalShowCalendarNameOption(cx, options, &showCalendar)) {
   2708      return false;
   2709    }
   2710 
   2711    // Step 6.
   2712    auto digits = Precision::Auto();
   2713    if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) {
   2714      return false;
   2715    }
   2716 
   2717    // Step 7.
   2718    if (!GetTemporalShowOffsetOption(cx, options, &showOffset)) {
   2719      return false;
   2720    }
   2721 
   2722    // Step 8.
   2723    if (!GetRoundingModeOption(cx, options, &roundingMode)) {
   2724      return false;
   2725    }
   2726 
   2727    // Step 9.
   2728    auto smallestUnit = TemporalUnit::Unset;
   2729    if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
   2730                                     &smallestUnit)) {
   2731      return false;
   2732    }
   2733 
   2734    // Step 10.
   2735    if (!GetTemporalShowTimeZoneNameOption(cx, options, &showTimeZone)) {
   2736      return false;
   2737    }
   2738 
   2739    // Step 11.
   2740    if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   2741                                   smallestUnit, TemporalUnitGroup::Time)) {
   2742      return false;
   2743    }
   2744 
   2745    // Step 12.
   2746    if (smallestUnit == TemporalUnit::Hour) {
   2747      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2748                                JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
   2749                                "smallestUnit");
   2750      return false;
   2751    }
   2752 
   2753    // Step 13.
   2754    precision = ToSecondsStringPrecision(smallestUnit, digits);
   2755  }
   2756 
   2757  // Step 14.
   2758  JSString* str = TemporalZonedDateTimeToString(
   2759      cx, zonedDateTime, precision.precision, showCalendar, showTimeZone,
   2760      showOffset, precision.increment, precision.unit, roundingMode);
   2761  if (!str) {
   2762    return false;
   2763  }
   2764 
   2765  args.rval().setString(str);
   2766  return true;
   2767 }
   2768 
   2769 /**
   2770 * Temporal.ZonedDateTime.prototype.toString ( [ options ] )
   2771 */
   2772 static bool ZonedDateTime_toString(JSContext* cx, unsigned argc, Value* vp) {
   2773  // Steps 1-2.
   2774  CallArgs args = CallArgsFromVp(argc, vp);
   2775  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toString>(cx,
   2776                                                                       args);
   2777 }
   2778 
   2779 /**
   2780 * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
   2781 */
   2782 static bool ZonedDateTime_toLocaleString(JSContext* cx, const CallArgs& args) {
   2783  Rooted<ZonedDateTime> zonedDateTime(
   2784      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2785 
   2786  // Steps 3-6.
   2787  Rooted<Value> timeZone(cx,
   2788                         StringValue(zonedDateTime.timeZone().identifier()));
   2789  return intl::TemporalObjectToLocaleString(
   2790      cx, args, intl::DateTimeFormatKind::All, timeZone);
   2791 }
   2792 
   2793 /**
   2794 * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
   2795 */
   2796 static bool ZonedDateTime_toLocaleString(JSContext* cx, unsigned argc,
   2797                                         Value* vp) {
   2798  // Steps 1-2.
   2799  CallArgs args = CallArgsFromVp(argc, vp);
   2800  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toLocaleString>(
   2801      cx, args);
   2802 }
   2803 
   2804 /**
   2805 * Temporal.ZonedDateTime.prototype.toJSON ( )
   2806 */
   2807 static bool ZonedDateTime_toJSON(JSContext* cx, const CallArgs& args) {
   2808  Rooted<ZonedDateTime> zonedDateTime(
   2809      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2810 
   2811  // Step 3.
   2812  JSString* str = TemporalZonedDateTimeToString(
   2813      cx, zonedDateTime, Precision::Auto(), ShowCalendar::Auto,
   2814      ShowTimeZoneName::Auto, ShowOffset::Auto);
   2815  if (!str) {
   2816    return false;
   2817  }
   2818 
   2819  args.rval().setString(str);
   2820  return true;
   2821 }
   2822 
   2823 /**
   2824 * Temporal.ZonedDateTime.prototype.toJSON ( )
   2825 */
   2826 static bool ZonedDateTime_toJSON(JSContext* cx, unsigned argc, Value* vp) {
   2827  // Steps 1-2.
   2828  CallArgs args = CallArgsFromVp(argc, vp);
   2829  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toJSON>(cx, args);
   2830 }
   2831 
   2832 /**
   2833 * Temporal.ZonedDateTime.prototype.valueOf ( )
   2834 */
   2835 static bool ZonedDateTime_valueOf(JSContext* cx, unsigned argc, Value* vp) {
   2836  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
   2837                            "ZonedDateTime", "primitive type");
   2838  return false;
   2839 }
   2840 
   2841 /**
   2842 * Temporal.ZonedDateTime.prototype.startOfDay ( )
   2843 */
   2844 static bool ZonedDateTime_startOfDay(JSContext* cx, const CallArgs& args) {
   2845  Rooted<ZonedDateTime> zonedDateTime(
   2846      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2847 
   2848  // Step 3.
   2849  auto timeZone = zonedDateTime.timeZone();
   2850 
   2851  // Step 4.
   2852  auto calendar = zonedDateTime.calendar();
   2853 
   2854  // Step 5.
   2855  ISODateTime dateTime;
   2856  if (!GetISODateTimeFor(cx, timeZone, zonedDateTime.epochNanoseconds(),
   2857                         &dateTime)) {
   2858    return false;
   2859  }
   2860 
   2861  // Step 6.
   2862  EpochNanoseconds epochNs;
   2863  if (!GetStartOfDay(cx, timeZone, dateTime.date, &epochNs)) {
   2864    return false;
   2865  }
   2866 
   2867  // Step 7.
   2868  auto* result = CreateTemporalZonedDateTime(cx, epochNs, timeZone, calendar);
   2869  if (!result) {
   2870    return false;
   2871  }
   2872 
   2873  args.rval().setObject(*result);
   2874  return true;
   2875 }
   2876 
   2877 /**
   2878 * Temporal.ZonedDateTime.prototype.startOfDay ( )
   2879 */
   2880 static bool ZonedDateTime_startOfDay(JSContext* cx, unsigned argc, Value* vp) {
   2881  // Steps 1-2.
   2882  CallArgs args = CallArgsFromVp(argc, vp);
   2883  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_startOfDay>(cx,
   2884                                                                         args);
   2885 }
   2886 
   2887 /**
   2888 * Temporal.ZonedDateTime.prototype.getTimeZoneTransition ( directionParam )
   2889 */
   2890 static bool ZonedDateTime_getTimeZoneTransition(JSContext* cx,
   2891                                                const CallArgs& args) {
   2892  Rooted<ZonedDateTime> zonedDateTime(
   2893      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   2894 
   2895  // Step 3.
   2896  auto timeZone = zonedDateTime.timeZone();
   2897 
   2898  // Steps 4-7.
   2899  auto direction = Direction::Next;
   2900  if (args.get(0).isString()) {
   2901    // Steps 5 and 7.
   2902    Rooted<JSString*> directionString(cx, args[0].toString());
   2903    if (!GetDirectionOption(cx, directionString, &direction)) {
   2904      return false;
   2905    }
   2906  } else {
   2907    // Steps 4 and 6.
   2908    Rooted<JSObject*> options(cx, RequireObjectArg(cx, "getTimeZoneTransition",
   2909                                                   "direction", args.get(0)));
   2910    if (!options) {
   2911      return false;
   2912    }
   2913 
   2914    // Step 7.
   2915    if (!GetDirectionOption(cx, options, &direction)) {
   2916      return false;
   2917    }
   2918  }
   2919 
   2920  // Step 8.
   2921  if (timeZone.isOffset()) {
   2922    args.rval().setNull();
   2923    return true;
   2924  }
   2925 
   2926  // Steps 9-10.
   2927  mozilla::Maybe<EpochNanoseconds> transition;
   2928  if (direction == Direction::Next) {
   2929    if (!GetNamedTimeZoneNextTransition(
   2930            cx, timeZone, zonedDateTime.epochNanoseconds(), &transition)) {
   2931      return false;
   2932    }
   2933  } else {
   2934    if (!GetNamedTimeZonePreviousTransition(
   2935            cx, timeZone, zonedDateTime.epochNanoseconds(), &transition)) {
   2936      return false;
   2937    }
   2938  }
   2939 
   2940  // Step 11.
   2941  if (!transition) {
   2942    args.rval().setNull();
   2943    return true;
   2944  }
   2945 
   2946  // Step 12.
   2947  auto* result = CreateTemporalZonedDateTime(cx, *transition, timeZone,
   2948                                             zonedDateTime.calendar());
   2949  if (!result) {
   2950    return false;
   2951  }
   2952 
   2953  args.rval().setObject(*result);
   2954  return true;
   2955 }
   2956 
   2957 /**
   2958 * Temporal.ZonedDateTime.prototype.getTimeZoneTransition ( directionParam )
   2959 */
   2960 static bool ZonedDateTime_getTimeZoneTransition(JSContext* cx, unsigned argc,
   2961                                                Value* vp) {
   2962  // Steps 1-2.
   2963  CallArgs args = CallArgsFromVp(argc, vp);
   2964  return CallNonGenericMethod<IsZonedDateTime,
   2965                              ZonedDateTime_getTimeZoneTransition>(cx, args);
   2966 }
   2967 
   2968 /**
   2969 * Temporal.ZonedDateTime.prototype.toInstant ( )
   2970 */
   2971 static bool ZonedDateTime_toInstant(JSContext* cx, const CallArgs& args) {
   2972  auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
   2973  auto epochNs = zonedDateTime->epochNanoseconds();
   2974 
   2975  // Step 3.
   2976  auto* result = CreateTemporalInstant(cx, epochNs);
   2977  if (!result) {
   2978    return false;
   2979  }
   2980 
   2981  args.rval().setObject(*result);
   2982  return true;
   2983 }
   2984 
   2985 /**
   2986 * Temporal.ZonedDateTime.prototype.toInstant ( )
   2987 */
   2988 static bool ZonedDateTime_toInstant(JSContext* cx, unsigned argc, Value* vp) {
   2989  // Steps 1-2.
   2990  CallArgs args = CallArgsFromVp(argc, vp);
   2991  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toInstant>(cx,
   2992                                                                        args);
   2993 }
   2994 
   2995 /**
   2996 * Temporal.ZonedDateTime.prototype.toPlainDate ( )
   2997 */
   2998 static bool ZonedDateTime_toPlainDate(JSContext* cx, const CallArgs& args) {
   2999  Rooted<ZonedDateTime> zonedDateTime(
   3000      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   3001 
   3002  // Step 3.
   3003  ISODateTime temporalDateTime;
   3004  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   3005                         zonedDateTime.epochNanoseconds(), &temporalDateTime)) {
   3006    return false;
   3007  }
   3008 
   3009  // Step 4.
   3010  auto* result =
   3011      CreateTemporalDate(cx, temporalDateTime.date, zonedDateTime.calendar());
   3012  if (!result) {
   3013    return false;
   3014  }
   3015 
   3016  args.rval().setObject(*result);
   3017  return true;
   3018 }
   3019 
   3020 /**
   3021 * Temporal.ZonedDateTime.prototype.toPlainDate ( )
   3022 */
   3023 static bool ZonedDateTime_toPlainDate(JSContext* cx, unsigned argc, Value* vp) {
   3024  // Steps 1-2.
   3025  CallArgs args = CallArgsFromVp(argc, vp);
   3026  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainDate>(cx,
   3027                                                                          args);
   3028 }
   3029 
   3030 /**
   3031 * Temporal.ZonedDateTime.prototype.toPlainTime ( )
   3032 */
   3033 static bool ZonedDateTime_toPlainTime(JSContext* cx, const CallArgs& args) {
   3034  Rooted<ZonedDateTime> zonedDateTime(
   3035      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   3036 
   3037  // Step 3.
   3038  ISODateTime temporalDateTime;
   3039  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   3040                         zonedDateTime.epochNanoseconds(), &temporalDateTime)) {
   3041    return false;
   3042  }
   3043 
   3044  // Step 4.
   3045  auto* result = CreateTemporalTime(cx, temporalDateTime.time);
   3046  if (!result) {
   3047    return false;
   3048  }
   3049 
   3050  args.rval().setObject(*result);
   3051  return true;
   3052 }
   3053 
   3054 /**
   3055 * Temporal.ZonedDateTime.prototype.toPlainTime ( )
   3056 */
   3057 static bool ZonedDateTime_toPlainTime(JSContext* cx, unsigned argc, Value* vp) {
   3058  // Steps 1-2.
   3059  CallArgs args = CallArgsFromVp(argc, vp);
   3060  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainTime>(cx,
   3061                                                                          args);
   3062 }
   3063 
   3064 /**
   3065 * Temporal.ZonedDateTime.prototype.toPlainDateTime ( )
   3066 */
   3067 static bool ZonedDateTime_toPlainDateTime(JSContext* cx, const CallArgs& args) {
   3068  Rooted<ZonedDateTime> zonedDateTime(
   3069      cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
   3070 
   3071  // Step 3.
   3072  ISODateTime dateTime;
   3073  if (!GetISODateTimeFor(cx, zonedDateTime.timeZone(),
   3074                         zonedDateTime.epochNanoseconds(), &dateTime)) {
   3075    return false;
   3076  }
   3077  MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
   3078 
   3079  // Step 4.
   3080  auto* result = CreateTemporalDateTime(cx, dateTime, zonedDateTime.calendar());
   3081  if (!result) {
   3082    return false;
   3083  }
   3084 
   3085  args.rval().setObject(*result);
   3086  return true;
   3087 }
   3088 
   3089 /**
   3090 * Temporal.ZonedDateTime.prototype.toPlainDateTime ( )
   3091 */
   3092 static bool ZonedDateTime_toPlainDateTime(JSContext* cx, unsigned argc,
   3093                                          Value* vp) {
   3094  // Steps 1-2.
   3095  CallArgs args = CallArgsFromVp(argc, vp);
   3096  return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainDateTime>(
   3097      cx, args);
   3098 }
   3099 
   3100 const JSClass ZonedDateTimeObject::class_ = {
   3101    "Temporal.ZonedDateTime",
   3102    JSCLASS_HAS_RESERVED_SLOTS(ZonedDateTimeObject::SLOT_COUNT) |
   3103        JSCLASS_HAS_CACHED_PROTO(JSProto_ZonedDateTime),
   3104    JS_NULL_CLASS_OPS,
   3105    &ZonedDateTimeObject::classSpec_,
   3106 };
   3107 
   3108 const JSClass& ZonedDateTimeObject::protoClass_ = PlainObject::class_;
   3109 
   3110 static const JSFunctionSpec ZonedDateTime_methods[] = {
   3111    JS_FN("from", ZonedDateTime_from, 1, 0),
   3112    JS_FN("compare", ZonedDateTime_compare, 2, 0),
   3113    JS_FS_END,
   3114 };
   3115 
   3116 static const JSFunctionSpec ZonedDateTime_prototype_methods[] = {
   3117    JS_FN("with", ZonedDateTime_with, 1, 0),
   3118    JS_FN("withPlainTime", ZonedDateTime_withPlainTime, 0, 0),
   3119    JS_FN("withTimeZone", ZonedDateTime_withTimeZone, 1, 0),
   3120    JS_FN("withCalendar", ZonedDateTime_withCalendar, 1, 0),
   3121    JS_FN("add", ZonedDateTime_add, 1, 0),
   3122    JS_FN("subtract", ZonedDateTime_subtract, 1, 0),
   3123    JS_FN("until", ZonedDateTime_until, 1, 0),
   3124    JS_FN("since", ZonedDateTime_since, 1, 0),
   3125    JS_FN("round", ZonedDateTime_round, 1, 0),
   3126    JS_FN("equals", ZonedDateTime_equals, 1, 0),
   3127    JS_FN("toString", ZonedDateTime_toString, 0, 0),
   3128    JS_FN("toLocaleString", ZonedDateTime_toLocaleString, 0, 0),
   3129    JS_FN("toJSON", ZonedDateTime_toJSON, 0, 0),
   3130    JS_FN("valueOf", ZonedDateTime_valueOf, 0, 0),
   3131    JS_FN("startOfDay", ZonedDateTime_startOfDay, 0, 0),
   3132    JS_FN("getTimeZoneTransition", ZonedDateTime_getTimeZoneTransition, 1, 0),
   3133    JS_FN("toInstant", ZonedDateTime_toInstant, 0, 0),
   3134    JS_FN("toPlainDate", ZonedDateTime_toPlainDate, 0, 0),
   3135    JS_FN("toPlainTime", ZonedDateTime_toPlainTime, 0, 0),
   3136    JS_FN("toPlainDateTime", ZonedDateTime_toPlainDateTime, 0, 0),
   3137    JS_FS_END,
   3138 };
   3139 
   3140 static const JSPropertySpec ZonedDateTime_prototype_properties[] = {
   3141    JS_PSG("calendarId", ZonedDateTime_calendarId, 0),
   3142    JS_PSG("timeZoneId", ZonedDateTime_timeZoneId, 0),
   3143    JS_PSG("era", ZonedDateTime_era, 0),
   3144    JS_PSG("eraYear", ZonedDateTime_eraYear, 0),
   3145    JS_PSG("year", ZonedDateTime_year, 0),
   3146    JS_PSG("month", ZonedDateTime_month, 0),
   3147    JS_PSG("monthCode", ZonedDateTime_monthCode, 0),
   3148    JS_PSG("day", ZonedDateTime_day, 0),
   3149    JS_PSG("hour", ZonedDateTime_hour, 0),
   3150    JS_PSG("minute", ZonedDateTime_minute, 0),
   3151    JS_PSG("second", ZonedDateTime_second, 0),
   3152    JS_PSG("millisecond", ZonedDateTime_millisecond, 0),
   3153    JS_PSG("microsecond", ZonedDateTime_microsecond, 0),
   3154    JS_PSG("nanosecond", ZonedDateTime_nanosecond, 0),
   3155    JS_PSG("epochMilliseconds", ZonedDateTime_epochMilliseconds, 0),
   3156    JS_PSG("epochNanoseconds", ZonedDateTime_epochNanoseconds, 0),
   3157    JS_PSG("dayOfWeek", ZonedDateTime_dayOfWeek, 0),
   3158    JS_PSG("dayOfYear", ZonedDateTime_dayOfYear, 0),
   3159    JS_PSG("weekOfYear", ZonedDateTime_weekOfYear, 0),
   3160    JS_PSG("yearOfWeek", ZonedDateTime_yearOfWeek, 0),
   3161    JS_PSG("hoursInDay", ZonedDateTime_hoursInDay, 0),
   3162    JS_PSG("daysInWeek", ZonedDateTime_daysInWeek, 0),
   3163    JS_PSG("daysInMonth", ZonedDateTime_daysInMonth, 0),
   3164    JS_PSG("daysInYear", ZonedDateTime_daysInYear, 0),
   3165    JS_PSG("monthsInYear", ZonedDateTime_monthsInYear, 0),
   3166    JS_PSG("inLeapYear", ZonedDateTime_inLeapYear, 0),
   3167    JS_PSG("offsetNanoseconds", ZonedDateTime_offsetNanoseconds, 0),
   3168    JS_PSG("offset", ZonedDateTime_offset, 0),
   3169    JS_STRING_SYM_PS(toStringTag, "Temporal.ZonedDateTime", JSPROP_READONLY),
   3170    JS_PS_END,
   3171 };
   3172 
   3173 const ClassSpec ZonedDateTimeObject::classSpec_ = {
   3174    GenericCreateConstructor<ZonedDateTimeConstructor, 2,
   3175                             gc::AllocKind::FUNCTION>,
   3176    GenericCreatePrototype<ZonedDateTimeObject>,
   3177    ZonedDateTime_methods,
   3178    nullptr,
   3179    ZonedDateTime_prototype_methods,
   3180    ZonedDateTime_prototype_properties,
   3181    nullptr,
   3182    ClassSpec::DontDefineConstructor,
   3183 };