tor-browser

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

Duration.cpp (120867B)


      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/Duration.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/Casting.h"
     11 #include "mozilla/CheckedInt.h"
     12 #include "mozilla/FloatingPoint.h"
     13 #include "mozilla/Maybe.h"
     14 
     15 #include <algorithm>
     16 #include <cmath>
     17 #include <cstdlib>
     18 #include <stdint.h>
     19 #include <type_traits>
     20 
     21 #include "jsnum.h"
     22 #include "jspubtd.h"
     23 #include "NamespaceImports.h"
     24 
     25 #include "builtin/intl/DurationFormat.h"
     26 #include "builtin/temporal/Calendar.h"
     27 #include "builtin/temporal/CalendarFields.h"
     28 #include "builtin/temporal/Instant.h"
     29 #include "builtin/temporal/Int96.h"
     30 #include "builtin/temporal/PlainDate.h"
     31 #include "builtin/temporal/PlainDateTime.h"
     32 #include "builtin/temporal/PlainTime.h"
     33 #include "builtin/temporal/Temporal.h"
     34 #include "builtin/temporal/TemporalParser.h"
     35 #include "builtin/temporal/TemporalRoundingMode.h"
     36 #include "builtin/temporal/TemporalTypes.h"
     37 #include "builtin/temporal/TemporalUnit.h"
     38 #include "builtin/temporal/TimeZone.h"
     39 #include "builtin/temporal/ZonedDateTime.h"
     40 #include "gc/AllocKind.h"
     41 #include "gc/Barrier.h"
     42 #include "gc/GCEnum.h"
     43 #include "js/CallArgs.h"
     44 #include "js/CallNonGenericMethod.h"
     45 #include "js/Class.h"
     46 #include "js/Conversions.h"
     47 #include "js/ErrorReport.h"
     48 #include "js/friend/ErrorMessages.h"
     49 #include "js/Printer.h"
     50 #include "js/PropertyDescriptor.h"
     51 #include "js/PropertySpec.h"
     52 #include "js/RootingAPI.h"
     53 #include "js/Value.h"
     54 #include "util/StringBuilder.h"
     55 #include "vm/BytecodeUtil.h"
     56 #include "vm/GlobalObject.h"
     57 #include "vm/Int128.h"
     58 #include "vm/JSAtomState.h"
     59 #include "vm/JSContext.h"
     60 #include "vm/JSObject.h"
     61 #include "vm/PlainObject.h"
     62 #include "vm/StringType.h"
     63 
     64 #include "vm/JSObject-inl.h"
     65 #include "vm/NativeObject-inl.h"
     66 #include "vm/ObjectOperations-inl.h"
     67 
     68 using namespace js;
     69 using namespace js::temporal;
     70 
     71 static inline bool IsDuration(Handle<Value> v) {
     72  return v.isObject() && v.toObject().is<DurationObject>();
     73 }
     74 
     75 #ifdef DEBUG
     76 static bool IsIntegerOrInfinity(double d) {
     77  return IsInteger(d) || std::isinf(d);
     78 }
     79 
     80 static bool IsIntegerOrInfinityDuration(const Duration& duration) {
     81  const auto& [years, months, weeks, days, hours, minutes, seconds,
     82               milliseconds, microseconds, nanoseconds] = duration;
     83 
     84  // Integers exceeding the Number range are represented as infinity.
     85 
     86  return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
     87         IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
     88         IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
     89         IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
     90         IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
     91 }
     92 
     93 static bool IsIntegerDuration(const Duration& duration) {
     94  const auto& [years, months, weeks, days, hours, minutes, seconds,
     95               milliseconds, microseconds, nanoseconds] = duration;
     96 
     97  return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
     98         IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
     99         IsInteger(seconds) && IsInteger(milliseconds) &&
    100         IsInteger(microseconds) && IsInteger(nanoseconds);
    101 }
    102 #endif
    103 
    104 /**
    105 * DurationSign ( duration )
    106 */
    107 int32_t js::temporal::DurationSign(const Duration& duration) {
    108  MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
    109 
    110  const auto& [years, months, weeks, days, hours, minutes, seconds,
    111               milliseconds, microseconds, nanoseconds] = duration;
    112 
    113  // Step 1.
    114  for (auto v : {years, months, weeks, days, hours, minutes, seconds,
    115                 milliseconds, microseconds, nanoseconds}) {
    116    // Step 1.a.
    117    if (v < 0) {
    118      return -1;
    119    }
    120 
    121    // Step 1.b.
    122    if (v > 0) {
    123      return 1;
    124    }
    125  }
    126 
    127  // Step 2.
    128  return 0;
    129 }
    130 
    131 /**
    132 * DateDurationSign ( dateDuration )
    133 */
    134 int32_t js::temporal::DateDurationSign(const DateDuration& duration) {
    135  const auto& [years, months, weeks, days] = duration;
    136 
    137  // Step 1.
    138  for (auto v : {years, months, weeks, days}) {
    139    // Step 1.a.
    140    if (v < 0) {
    141      return -1;
    142    }
    143 
    144    // Step 1.b.
    145    if (v > 0) {
    146      return 1;
    147    }
    148  }
    149 
    150  // Step 2.
    151  return 0;
    152 }
    153 
    154 /**
    155 * InternalDurationSign ( internalDuration )
    156 */
    157 static int32_t InternalDurationSign(const InternalDuration& duration) {
    158  MOZ_ASSERT(IsValidDuration(duration));
    159 
    160  if (int32_t sign = DateDurationSign(duration.date)) {
    161    return sign;
    162  }
    163  return TimeDurationSign(duration.time);
    164 }
    165 
    166 /**
    167 * Create a time duration from a nanoseconds amount.
    168 */
    169 static TimeDuration TimeDurationFromNanoseconds(const Int96& nanoseconds) {
    170  // Split into seconds and nanoseconds.
    171  auto [seconds, nanos] = nanoseconds / ToNanoseconds(TemporalUnit::Second);
    172 
    173  return {{seconds, nanos}};
    174 }
    175 
    176 /**
    177 * Create a time duration from a nanoseconds amount. Return Nothing if the value
    178 * is too large.
    179 */
    180 static mozilla::Maybe<TimeDuration> TimeDurationFromNanoseconds(
    181    double nanoseconds) {
    182  MOZ_ASSERT(IsInteger(nanoseconds));
    183 
    184  if (auto int96 = Int96::fromInteger(nanoseconds)) {
    185    // The number of time duration seconds must not exceed `2**53 - 1`.
    186    constexpr auto limit =
    187        Int96{uint64_t(1) << 53} * ToNanoseconds(TemporalUnit::Second);
    188 
    189    if (int96->abs() < limit) {
    190      return mozilla::Some(TimeDurationFromNanoseconds(*int96));
    191    }
    192  }
    193  return mozilla::Nothing();
    194 }
    195 
    196 /**
    197 * Create a time duration from a microseconds amount.
    198 */
    199 static TimeDuration TimeDurationFromMicroseconds(const Int96& microseconds) {
    200  // Split into seconds and microseconds.
    201  auto [seconds, micros] = microseconds / ToMicroseconds(TemporalUnit::Second);
    202 
    203  // Scale microseconds to nanoseconds.
    204  int32_t nanos = micros * int32_t(ToNanoseconds(TemporalUnit::Microsecond));
    205 
    206  return {{seconds, nanos}};
    207 }
    208 
    209 /**
    210 * Create a time duration from a microseconds amount. Return Nothing if the
    211 * value is too large.
    212 */
    213 static mozilla::Maybe<TimeDuration> TimeDurationFromMicroseconds(
    214    double microseconds) {
    215  MOZ_ASSERT(IsInteger(microseconds));
    216 
    217  if (auto int96 = Int96::fromInteger(microseconds)) {
    218    // The number of time duration seconds must not exceed `2**53 - 1`.
    219    constexpr auto limit =
    220        Int96{uint64_t(1) << 53} * ToMicroseconds(TemporalUnit::Second);
    221 
    222    if (int96->abs() < limit) {
    223      return mozilla::Some(TimeDurationFromMicroseconds(*int96));
    224    }
    225  }
    226  return mozilla::Nothing();
    227 }
    228 
    229 /**
    230 * Create a time duration from a duration. Return Nothing if any duration
    231 * value is too large.
    232 */
    233 static mozilla::Maybe<TimeDuration> TimeDurationFromDuration(
    234    const Duration& duration) {
    235  do {
    236    auto nanoseconds = TimeDurationFromNanoseconds(duration.nanoseconds);
    237    if (!nanoseconds) {
    238      break;
    239    }
    240    MOZ_ASSERT(IsValidTimeDuration(*nanoseconds));
    241 
    242    auto microseconds = TimeDurationFromMicroseconds(duration.microseconds);
    243    if (!microseconds) {
    244      break;
    245    }
    246    MOZ_ASSERT(IsValidTimeDuration(*microseconds));
    247 
    248    // Overflows for millis/seconds/minutes/hours/days always result in an
    249    // invalid time duration.
    250 
    251    int64_t milliseconds;
    252    if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
    253      break;
    254    }
    255 
    256    int64_t seconds;
    257    if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
    258      break;
    259    }
    260 
    261    int64_t minutes;
    262    if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
    263      break;
    264    }
    265 
    266    int64_t hours;
    267    if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
    268      break;
    269    }
    270 
    271    int64_t days;
    272    if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
    273      break;
    274    }
    275 
    276    // Compute the overall amount of milliseconds.
    277    mozilla::CheckedInt64 millis = days;
    278    millis *= 24;
    279    millis += hours;
    280    millis *= 60;
    281    millis += minutes;
    282    millis *= 60;
    283    millis += seconds;
    284    millis *= 1000;
    285    millis += milliseconds;
    286    if (!millis.isValid()) {
    287      break;
    288    }
    289 
    290    auto milli = TimeDuration::fromMilliseconds(millis.value());
    291    if (!IsValidTimeDuration(milli)) {
    292      break;
    293    }
    294 
    295    // Compute the overall time duration.
    296    auto result = milli + *microseconds + *nanoseconds;
    297    if (!IsValidTimeDuration(result)) {
    298      break;
    299    }
    300 
    301    return mozilla::Some(result);
    302  } while (false);
    303 
    304  return mozilla::Nothing();
    305 }
    306 
    307 /**
    308 * TimeDurationFromComponents ( hours, minutes, seconds, milliseconds,
    309 * microseconds, nanoseconds )
    310 */
    311 static TimeDuration TimeDurationFromComponents(double hours, double minutes,
    312                                               double seconds,
    313                                               double milliseconds,
    314                                               double microseconds,
    315                                               double nanoseconds) {
    316  MOZ_ASSERT(IsInteger(hours));
    317  MOZ_ASSERT(IsInteger(minutes));
    318  MOZ_ASSERT(IsInteger(seconds));
    319  MOZ_ASSERT(IsInteger(milliseconds));
    320  MOZ_ASSERT(IsInteger(microseconds));
    321  MOZ_ASSERT(IsInteger(nanoseconds));
    322 
    323  // Steps 1-3.
    324  mozilla::CheckedInt64 millis = int64_t(hours);
    325  millis *= 60;
    326  millis += int64_t(minutes);
    327  millis *= 60;
    328  millis += int64_t(seconds);
    329  millis *= 1000;
    330  millis += int64_t(milliseconds);
    331  MOZ_ASSERT(millis.isValid());
    332 
    333  auto timeDuration = TimeDuration::fromMilliseconds(millis.value());
    334 
    335  // Step 4.
    336  auto micros = Int96::fromInteger(microseconds);
    337  MOZ_ASSERT(micros);
    338 
    339  timeDuration += TimeDurationFromMicroseconds(*micros);
    340 
    341  // Step 5.
    342  auto nanos = Int96::fromInteger(nanoseconds);
    343  MOZ_ASSERT(nanos);
    344 
    345  timeDuration += TimeDurationFromNanoseconds(*nanos);
    346 
    347  // Step 6.
    348  MOZ_ASSERT(IsValidTimeDuration(timeDuration));
    349 
    350  // Step 7.
    351  return timeDuration;
    352 }
    353 
    354 /**
    355 * TimeDurationFromComponents ( hours, minutes, seconds, milliseconds,
    356 * microseconds, nanoseconds )
    357 */
    358 TimeDuration js::temporal::TimeDurationFromComponents(
    359    const Duration& duration) {
    360  MOZ_ASSERT(IsValidDuration(duration));
    361 
    362  return ::TimeDurationFromComponents(
    363      duration.hours, duration.minutes, duration.seconds, duration.milliseconds,
    364      duration.microseconds, duration.nanoseconds);
    365 }
    366 
    367 /**
    368 * Add24HourDaysToTimeDuration ( d, days )
    369 */
    370 static bool Add24HourDaysToTimeDuration(JSContext* cx, const TimeDuration& d,
    371                                        int64_t days, TimeDuration* result) {
    372  MOZ_ASSERT(IsValidTimeDuration(d));
    373 
    374  // Step 1.
    375  if (days > TimeDuration::max().toDays()) {
    376    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    377                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    378    return false;
    379  }
    380 
    381  auto timeDurationDays = TimeDuration::fromDays(days);
    382  if (!IsValidTimeDuration(timeDurationDays)) {
    383    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    384                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    385    return false;
    386  }
    387 
    388  // Step 2.
    389  auto sum = d + timeDurationDays;
    390  if (!IsValidTimeDuration(sum)) {
    391    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    392                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    393    return false;
    394  }
    395 
    396  // Step 3.
    397  *result = sum;
    398  return true;
    399 }
    400 
    401 /**
    402 * ToInternalDurationRecordWith24HourDays ( duration )
    403 */
    404 InternalDuration js::temporal::ToInternalDurationRecordWith24HourDays(
    405    const Duration& duration) {
    406  MOZ_ASSERT(IsValidDuration(duration));
    407 
    408  // Step 1.
    409  auto timeDuration = TimeDurationFromComponents(duration);
    410 
    411  // Step 2. (Inlined Add24HourDaysToTimeDuration)
    412  timeDuration += TimeDuration::fromDays(int64_t(duration.days));
    413 
    414  // Step 3.
    415  auto dateDuration = DateDuration{
    416      int64_t(duration.years),
    417      int64_t(duration.months),
    418      int64_t(duration.weeks),
    419      0,
    420  };
    421 
    422  // Step 4. (Inlined CombineDateAndTimeDuration)
    423  return InternalDuration{dateDuration, timeDuration};
    424 }
    425 
    426 /**
    427 * ToDateDurationRecordWithoutTime ( duration )
    428 */
    429 DateDuration js::temporal::ToDateDurationRecordWithoutTime(
    430    const Duration& duration) {
    431  // Step 1.
    432  auto internalDuration = ToInternalDurationRecordWith24HourDays(duration);
    433 
    434  // Step 2.
    435  int64_t days = internalDuration.time.toDays();
    436 
    437  // Step 3.
    438  auto result = DateDuration{
    439      internalDuration.date.years,
    440      internalDuration.date.months,
    441      internalDuration.date.weeks,
    442      days,
    443  };
    444  MOZ_ASSERT(IsValidDuration(result));
    445 
    446  return result;
    447 }
    448 
    449 /**
    450 * TemporalDurationFromInternal ( internalDuration, largestUnit )
    451 */
    452 static Duration TemporalDurationFromInternal(const TimeDuration& timeDuration,
    453                                             TemporalUnit largestUnit) {
    454  MOZ_ASSERT(IsValidTimeDuration(timeDuration));
    455  MOZ_ASSERT(largestUnit <= TemporalUnit::Second,
    456             "fallible fractional seconds units");
    457 
    458  auto [seconds, nanoseconds] = timeDuration.denormalize();
    459 
    460  // Step 1.
    461  int64_t days = 0;
    462  int64_t hours = 0;
    463  int64_t minutes = 0;
    464  int64_t milliseconds = 0;
    465  int64_t microseconds = 0;
    466 
    467  // Steps 2-3. (Not applicable in our implementation.)
    468  //
    469  // We don't need to convert to positive numbers, because integer division
    470  // truncates and the %-operator has modulo semantics.
    471 
    472  // Steps 4-11.
    473  switch (largestUnit) {
    474    // Step 4.
    475    case TemporalUnit::Year:
    476    case TemporalUnit::Month:
    477    case TemporalUnit::Week:
    478    case TemporalUnit::Day: {
    479      // Step 4.a.
    480      microseconds = nanoseconds / 1000;
    481 
    482      // Step 4.b.
    483      nanoseconds = nanoseconds % 1000;
    484 
    485      // Step 4.c.
    486      milliseconds = microseconds / 1000;
    487 
    488      // Step 4.d.
    489      microseconds = microseconds % 1000;
    490 
    491      // Steps 4.e-f. (Not applicable)
    492      MOZ_ASSERT(std::abs(milliseconds) <= 999);
    493 
    494      // Step 4.g.
    495      minutes = seconds / 60;
    496 
    497      // Step 4.h.
    498      seconds = seconds % 60;
    499 
    500      // Step 4.i.
    501      hours = minutes / 60;
    502 
    503      // Step 4.j.
    504      minutes = minutes % 60;
    505 
    506      // Step 4.k.
    507      days = hours / 24;
    508 
    509      // Step 4.l.
    510      hours = hours % 24;
    511 
    512      break;
    513    }
    514 
    515      // Step 5.
    516    case TemporalUnit::Hour: {
    517      // Step 5.a.
    518      microseconds = nanoseconds / 1000;
    519 
    520      // Step 5.b.
    521      nanoseconds = nanoseconds % 1000;
    522 
    523      // Step 5.c.
    524      milliseconds = microseconds / 1000;
    525 
    526      // Step 5.d.
    527      microseconds = microseconds % 1000;
    528 
    529      // Steps 5.e-f. (Not applicable)
    530      MOZ_ASSERT(std::abs(milliseconds) <= 999);
    531 
    532      // Step 5.g.
    533      minutes = seconds / 60;
    534 
    535      // Step 5.h.
    536      seconds = seconds % 60;
    537 
    538      // Step 5.i.
    539      hours = minutes / 60;
    540 
    541      // Step 5.j.
    542      minutes = minutes % 60;
    543 
    544      break;
    545    }
    546 
    547    case TemporalUnit::Minute: {
    548      // Step 6.a.
    549      microseconds = nanoseconds / 1000;
    550 
    551      // Step 6.b.
    552      nanoseconds = nanoseconds % 1000;
    553 
    554      // Step 6.c.
    555      milliseconds = microseconds / 1000;
    556 
    557      // Step 6.d.
    558      microseconds = microseconds % 1000;
    559 
    560      // Steps 6.e-f. (Not applicable)
    561      MOZ_ASSERT(std::abs(milliseconds) <= 999);
    562 
    563      // Step 6.g.
    564      minutes = seconds / 60;
    565 
    566      // Step 6.h.
    567      seconds = seconds % 60;
    568 
    569      break;
    570    }
    571 
    572    // Step 7.
    573    case TemporalUnit::Second: {
    574      // Step 7.a.
    575      microseconds = nanoseconds / 1000;
    576 
    577      // Step 7.b.
    578      nanoseconds = nanoseconds % 1000;
    579 
    580      // Step 7.c.
    581      milliseconds = microseconds / 1000;
    582 
    583      // Step 7.d.
    584      microseconds = microseconds % 1000;
    585 
    586      // Steps 7.e-f. (Not applicable)
    587      MOZ_ASSERT(std::abs(milliseconds) <= 999);
    588 
    589      break;
    590    }
    591 
    592    // Steps 8-11. (Not applicable in our implementation)
    593    case TemporalUnit::Millisecond:
    594    case TemporalUnit::Microsecond:
    595    case TemporalUnit::Nanosecond:
    596    case TemporalUnit::Unset:
    597    case TemporalUnit::Auto:
    598      MOZ_CRASH("Unexpected temporal unit");
    599  }
    600 
    601  // Step 12.
    602  auto result = Duration{
    603      0,
    604      0,
    605      0,
    606      double(days),
    607      double(hours),
    608      double(minutes),
    609      double(seconds),
    610      double(milliseconds),
    611      double(microseconds),
    612      double(nanoseconds),
    613  };
    614  MOZ_ASSERT(IsValidDuration(result));
    615  return result;
    616 }
    617 
    618 /**
    619 * TemporalDurationFromInternal ( internalDuration, largestUnit )
    620 */
    621 bool js::temporal::TemporalDurationFromInternal(
    622    JSContext* cx, const TimeDuration& timeDuration, TemporalUnit largestUnit,
    623    Duration* result) {
    624  MOZ_ASSERT(IsValidTimeDuration(timeDuration));
    625 
    626  auto [seconds, nanoseconds] = timeDuration.denormalize();
    627 
    628  // Steps 1-3. (Not applicable in our implementation.)
    629  //
    630  // We don't need to convert to positive numbers, because integer division
    631  // truncates and the %-operator has modulo semantics.
    632 
    633  // Steps 4-10.
    634  switch (largestUnit) {
    635    // Steps 4-7.
    636    case TemporalUnit::Year:
    637    case TemporalUnit::Month:
    638    case TemporalUnit::Week:
    639    case TemporalUnit::Day:
    640    case TemporalUnit::Hour:
    641    case TemporalUnit::Minute:
    642    case TemporalUnit::Second:
    643      *result = ::TemporalDurationFromInternal(timeDuration, largestUnit);
    644      return true;
    645 
    646    // Step 8.
    647    case TemporalUnit::Millisecond: {
    648      // Valid time durations must be below |limit|.
    649      constexpr auto limit = TimeDuration::max().toMilliseconds() + 1;
    650 
    651      // The largest possible milliseconds value whose double representation
    652      // doesn't exceed the time duration limit.
    653      constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
    654 
    655      // Assert |max| is the maximum allowed milliseconds value.
    656      static_assert(double(max) < double(limit));
    657      static_assert(double(max + 1) >= double(limit));
    658 
    659      static_assert((TimeDuration::max().seconds + 1) *
    660                            ToMilliseconds(TemporalUnit::Second) <=
    661                        INT64_MAX,
    662                    "total number duration milliseconds fits into int64");
    663 
    664      // Step 8.a.
    665      int64_t microseconds = nanoseconds / 1000;
    666 
    667      // Step 8.b.
    668      nanoseconds = nanoseconds % 1000;
    669 
    670      // Step 8.c.
    671      int64_t milliseconds = microseconds / 1000;
    672      MOZ_ASSERT(std::abs(milliseconds) <= 999);
    673 
    674      // Step 8.d.
    675      microseconds = microseconds % 1000;
    676 
    677      auto millis =
    678          (seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
    679      if (std::abs(millis) > max) {
    680        JS_ReportErrorNumberASCII(
    681            cx, GetErrorMessage, nullptr,
    682            JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    683        return false;
    684      }
    685 
    686      // Step 11.
    687      *result = {0,
    688                 0,
    689                 0,
    690                 0,
    691                 0,
    692                 0,
    693                 0,
    694                 double(millis),
    695                 double(microseconds),
    696                 double(nanoseconds)};
    697      MOZ_ASSERT(IsValidDuration(*result));
    698      return true;
    699    }
    700 
    701    // Step 9.
    702    case TemporalUnit::Microsecond: {
    703      // Valid time durations must be below |limit|.
    704      constexpr auto limit =
    705          Uint128{TimeDuration::max().toMicroseconds()} + Uint128{1};
    706 
    707      // The largest possible microseconds value whose double representation
    708      // doesn't exceed the time duration limit.
    709      constexpr auto max =
    710          (Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff};
    711      static_assert(max < limit);
    712 
    713      // Assert |max| is the maximum allowed microseconds value.
    714      MOZ_ASSERT(double(max) < double(limit));
    715      MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
    716 
    717      // Step 9.a.
    718      int64_t microseconds = nanoseconds / 1000;
    719      MOZ_ASSERT(std::abs(microseconds) <= 999'999);
    720 
    721      // Step 9.b.
    722      nanoseconds = nanoseconds % 1000;
    723 
    724      auto micros =
    725          (Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
    726          Int128{microseconds};
    727      if (micros.abs() > max) {
    728        JS_ReportErrorNumberASCII(
    729            cx, GetErrorMessage, nullptr,
    730            JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    731        return false;
    732      }
    733 
    734      // Step 11.
    735      *result = {0, 0, 0, 0, 0, 0, 0, 0, double(micros), double(nanoseconds)};
    736      MOZ_ASSERT(IsValidDuration(*result));
    737      return true;
    738    }
    739 
    740    // Step 10.
    741    case TemporalUnit::Nanosecond: {
    742      // Valid time durations must be below |limit|.
    743      constexpr auto limit =
    744          Uint128{TimeDuration::max().toNanoseconds()} + Uint128{1};
    745 
    746      // The largest possible nanoseconds value whose double representation
    747      // doesn't exceed the time duration limit.
    748      constexpr auto max =
    749          (Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff};
    750      static_assert(max < limit);
    751 
    752      // Assert |max| is the maximum allowed nanoseconds value.
    753      MOZ_ASSERT(double(max) < double(limit));
    754      MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
    755 
    756      MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
    757 
    758      auto nanos =
    759          (Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
    760          Int128{nanoseconds};
    761      if (nanos.abs() > max) {
    762        JS_ReportErrorNumberASCII(
    763            cx, GetErrorMessage, nullptr,
    764            JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
    765        return false;
    766      }
    767 
    768      // Step 11.
    769      *result = {0, 0, 0, 0, 0, 0, 0, 0, 0, double(nanos)};
    770      MOZ_ASSERT(IsValidDuration(*result));
    771      return true;
    772    }
    773 
    774    case TemporalUnit::Unset:
    775    case TemporalUnit::Auto:
    776      break;
    777  }
    778  MOZ_CRASH("Unexpected temporal unit");
    779 }
    780 
    781 /**
    782 * TemporalDurationFromInternal ( internalDuration, largestUnit )
    783 */
    784 bool js::temporal::TemporalDurationFromInternal(
    785    JSContext* cx, const InternalDuration& internalDuration,
    786    TemporalUnit largestUnit, Duration* result) {
    787  MOZ_ASSERT(IsValidDuration(internalDuration.date));
    788  MOZ_ASSERT(IsValidTimeDuration(internalDuration.time));
    789 
    790  // Steps 1-11.
    791  Duration duration;
    792  if (!TemporalDurationFromInternal(cx, internalDuration.time, largestUnit,
    793                                    &duration)) {
    794    return false;
    795  }
    796  MOZ_ASSERT(IsValidDuration(duration));
    797 
    798  // Step 12.
    799  auto days = mozilla::CheckedInt64(internalDuration.date.days) +
    800              mozilla::AssertedCast<int64_t>(duration.days);
    801  MOZ_ASSERT(days.isValid(), "valid duration days can't overflow");
    802 
    803  *result = {
    804      double(internalDuration.date.years),
    805      double(internalDuration.date.months),
    806      double(internalDuration.date.weeks),
    807      double(days.value()),
    808      duration.hours,
    809      duration.minutes,
    810      duration.seconds,
    811      duration.milliseconds,
    812      duration.microseconds,
    813      duration.nanoseconds,
    814  };
    815  return ThrowIfInvalidDuration(cx, *result);
    816 }
    817 
    818 /**
    819 * TimeDurationFromEpochNanosecondsDifference ( one, two )
    820 */
    821 TimeDuration js::temporal::TimeDurationFromEpochNanosecondsDifference(
    822    const EpochNanoseconds& one, const EpochNanoseconds& two) {
    823  MOZ_ASSERT(IsValidEpochNanoseconds(one));
    824  MOZ_ASSERT(IsValidEpochNanoseconds(two));
    825 
    826  // Step 1.
    827  auto result = one - two;
    828 
    829  // Step 2.
    830  MOZ_ASSERT(IsValidEpochDuration(result));
    831 
    832  // Step 3.
    833  return result.to<TimeDuration>();
    834 }
    835 
    836 #ifdef DEBUG
    837 /**
    838 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
    839 * milliseconds, microseconds, nanoseconds )
    840 */
    841 bool js::temporal::IsValidDuration(const Duration& duration) {
    842  MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
    843 
    844  const auto& [years, months, weeks, days, hours, minutes, seconds,
    845               milliseconds, microseconds, nanoseconds] = duration;
    846 
    847  // Step 1.
    848  int32_t sign = 0;
    849 
    850  // Step 2.
    851  for (auto v : {years, months, weeks, days, hours, minutes, seconds,
    852                 milliseconds, microseconds, nanoseconds}) {
    853    // Step 2.a.
    854    if (!std::isfinite(v)) {
    855      return false;
    856    }
    857 
    858    // Step 2.b.
    859    if (v < 0) {
    860      // Step 2.b.i.
    861      if (sign > 0) {
    862        return false;
    863      }
    864 
    865      // Step 2.b.ii.
    866      sign = -1;
    867    }
    868 
    869    // Step 2.c.
    870    else if (v > 0) {
    871      // Step 2.c.i.
    872      if (sign < 0) {
    873        return false;
    874      }
    875 
    876      // Step 2.c.ii.
    877      sign = 1;
    878    }
    879  }
    880 
    881  // Step 3.
    882  if (std::abs(years) >= double(int64_t(1) << 32)) {
    883    return false;
    884  }
    885 
    886  // Step 4.
    887  if (std::abs(months) >= double(int64_t(1) << 32)) {
    888    return false;
    889  }
    890 
    891  // Step 5.
    892  if (std::abs(weeks) >= double(int64_t(1) << 32)) {
    893    return false;
    894  }
    895 
    896  // Steps 6-8.
    897  if (!TimeDurationFromDuration(duration)) {
    898    return false;
    899  }
    900 
    901  // Step 9.
    902  return true;
    903 }
    904 
    905 /**
    906 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
    907 * milliseconds, microseconds, nanoseconds )
    908 */
    909 bool js::temporal::IsValidDuration(const DateDuration& duration) {
    910  return IsValidDuration(duration.toDuration());
    911 }
    912 
    913 /**
    914 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
    915 * milliseconds, microseconds, nanoseconds )
    916 */
    917 bool js::temporal::IsValidDuration(const InternalDuration& duration) {
    918  if (!IsValidTimeDuration(duration.time)) {
    919    return false;
    920  }
    921 
    922  auto d = duration.date.toDuration();
    923  auto [seconds, nanoseconds] = duration.time.denormalize();
    924  d.seconds = double(seconds);
    925  d.nanoseconds = double(nanoseconds);
    926 
    927  return IsValidDuration(d);
    928 }
    929 #endif
    930 
    931 static bool ThrowInvalidDurationPart(JSContext* cx, double value,
    932                                     const char* name, unsigned errorNumber) {
    933  ToCStringBuf cbuf;
    934  const char* numStr = NumberToCString(&cbuf, value);
    935 
    936  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
    937                            numStr);
    938  return false;
    939 }
    940 
    941 /**
    942 * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
    943 * milliseconds, microseconds, nanoseconds )
    944 */
    945 bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
    946                                          const Duration& duration) {
    947  MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
    948 
    949  const auto& [years, months, weeks, days, hours, minutes, seconds,
    950               milliseconds, microseconds, nanoseconds] = duration;
    951 
    952  // Step 1.
    953  int32_t sign = DurationSign(duration);
    954 
    955  auto throwIfInvalid = [&](double v, const char* name) {
    956    // Step 2.a.
    957    if (!std::isfinite(v)) {
    958      return ThrowInvalidDurationPart(
    959          cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
    960    }
    961 
    962    // Steps 2.b-c.
    963    if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
    964      return ThrowInvalidDurationPart(cx, v, name,
    965                                      JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
    966    }
    967 
    968    return true;
    969  };
    970 
    971  auto throwIfTooLarge = [&](double v, const char* name) {
    972    if (std::abs(v) >= double(int64_t(1) << 32)) {
    973      return ThrowInvalidDurationPart(
    974          cx, v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
    975    }
    976    return true;
    977  };
    978 
    979  // Step 2.
    980  if (!throwIfInvalid(years, "years")) {
    981    return false;
    982  }
    983  if (!throwIfInvalid(months, "months")) {
    984    return false;
    985  }
    986  if (!throwIfInvalid(weeks, "weeks")) {
    987    return false;
    988  }
    989  if (!throwIfInvalid(days, "days")) {
    990    return false;
    991  }
    992  if (!throwIfInvalid(hours, "hours")) {
    993    return false;
    994  }
    995  if (!throwIfInvalid(minutes, "minutes")) {
    996    return false;
    997  }
    998  if (!throwIfInvalid(seconds, "seconds")) {
    999    return false;
   1000  }
   1001  if (!throwIfInvalid(milliseconds, "milliseconds")) {
   1002    return false;
   1003  }
   1004  if (!throwIfInvalid(microseconds, "microseconds")) {
   1005    return false;
   1006  }
   1007  if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
   1008    return false;
   1009  }
   1010 
   1011  // Step 3.
   1012  if (!throwIfTooLarge(years, "years")) {
   1013    return false;
   1014  }
   1015 
   1016  // Step 4.
   1017  if (!throwIfTooLarge(months, "months")) {
   1018    return false;
   1019  }
   1020 
   1021  // Step 5.
   1022  if (!throwIfTooLarge(weeks, "weeks")) {
   1023    return false;
   1024  }
   1025 
   1026  // Steps 6-8.
   1027  if (!TimeDurationFromDuration(duration)) {
   1028    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1029                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
   1030    return false;
   1031  }
   1032 
   1033  MOZ_ASSERT(IsValidDuration(duration));
   1034 
   1035  // Step 9.
   1036  return true;
   1037 }
   1038 
   1039 /**
   1040 * DefaultTemporalLargestUnit ( duration )
   1041 */
   1042 static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
   1043  MOZ_ASSERT(IsIntegerDuration(duration));
   1044 
   1045  // Step 1.
   1046  if (duration.years != 0) {
   1047    return TemporalUnit::Year;
   1048  }
   1049 
   1050  // Step 2.
   1051  if (duration.months != 0) {
   1052    return TemporalUnit::Month;
   1053  }
   1054 
   1055  // Step 3.
   1056  if (duration.weeks != 0) {
   1057    return TemporalUnit::Week;
   1058  }
   1059 
   1060  // Step 4.
   1061  if (duration.days != 0) {
   1062    return TemporalUnit::Day;
   1063  }
   1064 
   1065  // Step 5.
   1066  if (duration.hours != 0) {
   1067    return TemporalUnit::Hour;
   1068  }
   1069 
   1070  // Step 6.
   1071  if (duration.minutes != 0) {
   1072    return TemporalUnit::Minute;
   1073  }
   1074 
   1075  // Step 7.
   1076  if (duration.seconds != 0) {
   1077    return TemporalUnit::Second;
   1078  }
   1079 
   1080  // Step 8.
   1081  if (duration.milliseconds != 0) {
   1082    return TemporalUnit::Millisecond;
   1083  }
   1084 
   1085  // Step 9.
   1086  if (duration.microseconds != 0) {
   1087    return TemporalUnit::Microsecond;
   1088  }
   1089 
   1090  // Step 10.
   1091  return TemporalUnit::Nanosecond;
   1092 }
   1093 
   1094 /**
   1095 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
   1096 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
   1097 */
   1098 static DurationObject* CreateTemporalDuration(JSContext* cx,
   1099                                              const CallArgs& args,
   1100                                              const Duration& duration) {
   1101  const auto& [years, months, weeks, days, hours, minutes, seconds,
   1102               milliseconds, microseconds, nanoseconds] = duration;
   1103 
   1104  // Step 1.
   1105  if (!ThrowIfInvalidDuration(cx, duration)) {
   1106    return nullptr;
   1107  }
   1108 
   1109  // Steps 2-3.
   1110  Rooted<JSObject*> proto(cx);
   1111  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
   1112    return nullptr;
   1113  }
   1114 
   1115  auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
   1116  if (!object) {
   1117    return nullptr;
   1118  }
   1119 
   1120  // Steps 4-13.
   1121  // Add zero to convert -0 to +0.
   1122  object->initFixedSlot(DurationObject::YEARS_SLOT,
   1123                        NumberValue(years + (+0.0)));
   1124  object->initFixedSlot(DurationObject::MONTHS_SLOT,
   1125                        NumberValue(months + (+0.0)));
   1126  object->initFixedSlot(DurationObject::WEEKS_SLOT,
   1127                        NumberValue(weeks + (+0.0)));
   1128  object->initFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
   1129  object->initFixedSlot(DurationObject::HOURS_SLOT,
   1130                        NumberValue(hours + (+0.0)));
   1131  object->initFixedSlot(DurationObject::MINUTES_SLOT,
   1132                        NumberValue(minutes + (+0.0)));
   1133  object->initFixedSlot(DurationObject::SECONDS_SLOT,
   1134                        NumberValue(seconds + (+0.0)));
   1135  object->initFixedSlot(DurationObject::MILLISECONDS_SLOT,
   1136                        NumberValue(milliseconds + (+0.0)));
   1137  object->initFixedSlot(DurationObject::MICROSECONDS_SLOT,
   1138                        NumberValue(microseconds + (+0.0)));
   1139  object->initFixedSlot(DurationObject::NANOSECONDS_SLOT,
   1140                        NumberValue(nanoseconds + (+0.0)));
   1141 
   1142  // Step 14.
   1143  return object;
   1144 }
   1145 
   1146 /**
   1147 * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
   1148 * milliseconds, microseconds, nanoseconds [ , newTarget ] )
   1149 */
   1150 DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
   1151                                                     const Duration& duration) {
   1152  const auto& [years, months, weeks, days, hours, minutes, seconds,
   1153               milliseconds, microseconds, nanoseconds] = duration;
   1154 
   1155  MOZ_ASSERT(IsInteger(years));
   1156  MOZ_ASSERT(IsInteger(months));
   1157  MOZ_ASSERT(IsInteger(weeks));
   1158  MOZ_ASSERT(IsInteger(days));
   1159  MOZ_ASSERT(IsInteger(hours));
   1160  MOZ_ASSERT(IsInteger(minutes));
   1161  MOZ_ASSERT(IsInteger(seconds));
   1162  MOZ_ASSERT(IsInteger(milliseconds));
   1163  MOZ_ASSERT(IsInteger(microseconds));
   1164  MOZ_ASSERT(IsInteger(nanoseconds));
   1165 
   1166  // Step 1.
   1167  if (!ThrowIfInvalidDuration(cx, duration)) {
   1168    return nullptr;
   1169  }
   1170 
   1171  // Steps 2-3.
   1172  auto* object = NewBuiltinClassInstance<DurationObject>(cx);
   1173  if (!object) {
   1174    return nullptr;
   1175  }
   1176 
   1177  // Steps 4-13.
   1178  // Add zero to convert -0 to +0.
   1179  object->initFixedSlot(DurationObject::YEARS_SLOT,
   1180                        NumberValue(years + (+0.0)));
   1181  object->initFixedSlot(DurationObject::MONTHS_SLOT,
   1182                        NumberValue(months + (+0.0)));
   1183  object->initFixedSlot(DurationObject::WEEKS_SLOT,
   1184                        NumberValue(weeks + (+0.0)));
   1185  object->initFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
   1186  object->initFixedSlot(DurationObject::HOURS_SLOT,
   1187                        NumberValue(hours + (+0.0)));
   1188  object->initFixedSlot(DurationObject::MINUTES_SLOT,
   1189                        NumberValue(minutes + (+0.0)));
   1190  object->initFixedSlot(DurationObject::SECONDS_SLOT,
   1191                        NumberValue(seconds + (+0.0)));
   1192  object->initFixedSlot(DurationObject::MILLISECONDS_SLOT,
   1193                        NumberValue(milliseconds + (+0.0)));
   1194  object->initFixedSlot(DurationObject::MICROSECONDS_SLOT,
   1195                        NumberValue(microseconds + (+0.0)));
   1196  object->initFixedSlot(DurationObject::NANOSECONDS_SLOT,
   1197                        NumberValue(nanoseconds + (+0.0)));
   1198 
   1199  // Step 14.
   1200  return object;
   1201 }
   1202 
   1203 /**
   1204 * ToIntegerIfIntegral ( argument )
   1205 */
   1206 static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
   1207                                Handle<Value> argument, double* num) {
   1208  // Step 1.
   1209  double d;
   1210  if (!JS::ToNumber(cx, argument, &d)) {
   1211    return false;
   1212  }
   1213 
   1214  // Step 2.
   1215  if (!js::IsInteger(d)) {
   1216    ToCStringBuf cbuf;
   1217    const char* numStr = NumberToCString(&cbuf, d);
   1218 
   1219    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1220                              JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
   1221                              name);
   1222    return false;
   1223  }
   1224 
   1225  // Step 3.
   1226  *num = d;
   1227  return true;
   1228 }
   1229 
   1230 /**
   1231 * ToIntegerIfIntegral ( argument )
   1232 */
   1233 static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
   1234                                Handle<Value> argument, double* result) {
   1235  // Step 1.
   1236  double d;
   1237  if (!JS::ToNumber(cx, argument, &d)) {
   1238    return false;
   1239  }
   1240 
   1241  // Step 2.
   1242  if (!js::IsInteger(d)) {
   1243    if (auto nameStr = js::QuoteString(cx, name)) {
   1244      ToCStringBuf cbuf;
   1245      const char* numStr = NumberToCString(&cbuf, d);
   1246 
   1247      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1248                                JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
   1249                                nameStr.get());
   1250    }
   1251    return false;
   1252  }
   1253 
   1254  // Step 3.
   1255  *result = d;
   1256  return true;
   1257 }
   1258 
   1259 /**
   1260 * ToTemporalPartialDurationRecord ( temporalDurationLike )
   1261 */
   1262 static bool ToTemporalPartialDurationRecord(
   1263    JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
   1264  // Steps 1-3. (Not applicable in our implementation.)
   1265 
   1266  Rooted<Value> value(cx);
   1267  bool any = false;
   1268 
   1269  auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
   1270    if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
   1271                     &value)) {
   1272      return false;
   1273    }
   1274 
   1275    if (!value.isUndefined()) {
   1276      any = true;
   1277 
   1278      if (!ToIntegerIfIntegral(cx, name, value, num)) {
   1279        return false;
   1280      }
   1281    }
   1282    return true;
   1283  };
   1284 
   1285  // Steps 4-23.
   1286  if (!getDurationProperty(cx->names().days, &result->days)) {
   1287    return false;
   1288  }
   1289  if (!getDurationProperty(cx->names().hours, &result->hours)) {
   1290    return false;
   1291  }
   1292  if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
   1293    return false;
   1294  }
   1295  if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
   1296    return false;
   1297  }
   1298  if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
   1299    return false;
   1300  }
   1301  if (!getDurationProperty(cx->names().months, &result->months)) {
   1302    return false;
   1303  }
   1304  if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
   1305    return false;
   1306  }
   1307  if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
   1308    return false;
   1309  }
   1310  if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
   1311    return false;
   1312  }
   1313  if (!getDurationProperty(cx->names().years, &result->years)) {
   1314    return false;
   1315  }
   1316 
   1317  // Step 24.
   1318  if (!any) {
   1319    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1320                              JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
   1321    return false;
   1322  }
   1323 
   1324  // Step 25.
   1325  return true;
   1326 }
   1327 
   1328 /**
   1329 * ToTemporalDuration ( item )
   1330 */
   1331 bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
   1332                                      Duration* result) {
   1333  // Steps 1 and 3-15.
   1334  if (item.isObject()) {
   1335    Rooted<JSObject*> itemObj(cx, &item.toObject());
   1336 
   1337    // Step 1.
   1338    if (auto* duration = itemObj->maybeUnwrapIf<DurationObject>()) {
   1339      *result = ToDuration(duration);
   1340      return true;
   1341    }
   1342 
   1343    // Step 3. (Reordered)
   1344    Duration duration = {};
   1345 
   1346    // Steps 4-14.
   1347    if (!ToTemporalPartialDurationRecord(cx, itemObj, &duration)) {
   1348      return false;
   1349    }
   1350 
   1351    // Step 15.
   1352    if (!ThrowIfInvalidDuration(cx, duration)) {
   1353      return false;
   1354    }
   1355 
   1356    *result = duration;
   1357    return true;
   1358  }
   1359 
   1360  // Step 2.a.
   1361  if (!item.isString()) {
   1362    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
   1363                     nullptr, "not a string");
   1364    return false;
   1365  }
   1366  Rooted<JSString*> string(cx, item.toString());
   1367 
   1368  // Step 2.b.
   1369  return ParseTemporalDurationString(cx, string, result);
   1370 }
   1371 
   1372 /**
   1373 * DateDurationDays ( dateDuration, plainRelativeTo )
   1374 */
   1375 static bool DateDurationDays(JSContext* cx, const DateDuration& duration,
   1376                             Handle<PlainDate> plainRelativeTo,
   1377                             int64_t* result) {
   1378  MOZ_ASSERT(IsValidDuration(duration));
   1379 
   1380  auto [years, months, weeks, days] = duration;
   1381 
   1382  // Step 1.
   1383  auto yearsMonthsWeeksDuration = DateDuration{years, months, weeks};
   1384 
   1385  // Step 2.
   1386  if (yearsMonthsWeeksDuration == DateDuration{}) {
   1387    *result = days;
   1388    return true;
   1389  }
   1390 
   1391  // Moved from caller.
   1392  if (!plainRelativeTo) {
   1393    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1394                              JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
   1395                              "relativeTo");
   1396    return false;
   1397  }
   1398 
   1399  // Step 3.
   1400  ISODate later;
   1401  if (!CalendarDateAdd(cx, plainRelativeTo.calendar(), plainRelativeTo,
   1402                       yearsMonthsWeeksDuration, TemporalOverflow::Constrain,
   1403                       &later)) {
   1404    return false;
   1405  }
   1406 
   1407  // Step 4.
   1408  int32_t epochDays1 = MakeDay(plainRelativeTo);
   1409  MOZ_ASSERT(MinEpochDay <= epochDays1 && epochDays1 <= MaxEpochDay);
   1410 
   1411  // Step 5.
   1412  int32_t epochDays2 = MakeDay(later);
   1413  MOZ_ASSERT(MinEpochDay <= epochDays2 && epochDays2 <= MaxEpochDay);
   1414 
   1415  // Step 4.
   1416  int32_t yearsMonthsWeeksInDay = epochDays2 - epochDays1;
   1417 
   1418  // Step 5.
   1419  *result = days + yearsMonthsWeeksInDay;
   1420  return true;
   1421 }
   1422 
   1423 static bool NumberToStringBuilder(JSContext* cx, double num,
   1424                                  JSStringBuilder& sb) {
   1425  MOZ_ASSERT(IsInteger(num));
   1426  MOZ_ASSERT(num >= 0);
   1427  MOZ_ASSERT(num < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1428 
   1429  ToCStringBuf cbuf;
   1430  size_t length;
   1431  const char* numStr = NumberToCString(&cbuf, num, &length);
   1432 
   1433  return sb.append(numStr, length);
   1434 }
   1435 
   1436 static Duration AbsoluteDuration(const Duration& duration) {
   1437  return {
   1438      std::abs(duration.years),        std::abs(duration.months),
   1439      std::abs(duration.weeks),        std::abs(duration.days),
   1440      std::abs(duration.hours),        std::abs(duration.minutes),
   1441      std::abs(duration.seconds),      std::abs(duration.milliseconds),
   1442      std::abs(duration.microseconds), std::abs(duration.nanoseconds),
   1443  };
   1444 }
   1445 
   1446 /**
   1447 * FormatFractionalSeconds ( subSecondNanoseconds, precision )
   1448 */
   1449 [[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
   1450                                                  int32_t subSecondNanoseconds,
   1451                                                  Precision precision) {
   1452  MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
   1453  MOZ_ASSERT(precision != Precision::Minute());
   1454 
   1455  // Steps 1-2.
   1456  if (precision == Precision::Auto()) {
   1457    // Step 1.a.
   1458    if (subSecondNanoseconds == 0) {
   1459      return true;
   1460    }
   1461 
   1462    // Step 3. (Reordered)
   1463    if (!result.append('.')) {
   1464      return false;
   1465    }
   1466 
   1467    // Steps 1.b-c.
   1468    int32_t k = 100'000'000;
   1469    do {
   1470      if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
   1471        return false;
   1472      }
   1473      subSecondNanoseconds %= k;
   1474      k /= 10;
   1475    } while (subSecondNanoseconds);
   1476  } else {
   1477    // Step 2.a.
   1478    uint8_t p = precision.value();
   1479    if (p == 0) {
   1480      return true;
   1481    }
   1482 
   1483    // Step 3. (Reordered)
   1484    if (!result.append('.')) {
   1485      return false;
   1486    }
   1487 
   1488    // Steps 2.b-c.
   1489    int32_t k = 100'000'000;
   1490    for (uint8_t i = 0; i < precision.value(); i++) {
   1491      if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
   1492        return false;
   1493      }
   1494      subSecondNanoseconds %= k;
   1495      k /= 10;
   1496    }
   1497  }
   1498 
   1499  return true;
   1500 }
   1501 
   1502 /**
   1503 * TemporalDurationToString ( duration, precision )
   1504 */
   1505 static JSString* TemporalDurationToString(JSContext* cx,
   1506                                          const Duration& duration,
   1507                                          Precision precision) {
   1508  MOZ_ASSERT(IsValidDuration(duration));
   1509  MOZ_ASSERT(precision != Precision::Minute());
   1510 
   1511  // Fast path for zero durations.
   1512  if (duration == Duration{} &&
   1513      (precision == Precision::Auto() || precision.value() == 0)) {
   1514    return NewStringCopyZ<CanGC>(cx, "PT0S");
   1515  }
   1516 
   1517  // Convert to absolute values up front. This is okay to do, because when the
   1518  // duration is valid, all components have the same sign.
   1519  const auto& [years, months, weeks, days, hours, minutes, seconds,
   1520               milliseconds, microseconds, nanoseconds] =
   1521      AbsoluteDuration(duration);
   1522 
   1523  // Years to seconds parts are all safe integers for valid durations.
   1524  MOZ_ASSERT(years < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1525  MOZ_ASSERT(months < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1526  MOZ_ASSERT(weeks < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1527  MOZ_ASSERT(days < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1528  MOZ_ASSERT(hours < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1529  MOZ_ASSERT(minutes < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1530  MOZ_ASSERT(seconds < DOUBLE_INTEGRAL_PRECISION_LIMIT);
   1531 
   1532  // Step 1.
   1533  int32_t sign = DurationSign(duration);
   1534 
   1535  // Steps 2 and 7.
   1536  JSStringBuilder result(cx);
   1537 
   1538  // Step 14. (Reordered)
   1539  if (sign < 0) {
   1540    if (!result.append('-')) {
   1541      return nullptr;
   1542    }
   1543  }
   1544 
   1545  // Step 15. (Reordered)
   1546  if (!result.append('P')) {
   1547    return nullptr;
   1548  }
   1549 
   1550  // Step 3.
   1551  if (years != 0) {
   1552    if (!NumberToStringBuilder(cx, years, result)) {
   1553      return nullptr;
   1554    }
   1555    if (!result.append('Y')) {
   1556      return nullptr;
   1557    }
   1558  }
   1559 
   1560  // Step 4.
   1561  if (months != 0) {
   1562    if (!NumberToStringBuilder(cx, months, result)) {
   1563      return nullptr;
   1564    }
   1565    if (!result.append('M')) {
   1566      return nullptr;
   1567    }
   1568  }
   1569 
   1570  // Step 5.
   1571  if (weeks != 0) {
   1572    if (!NumberToStringBuilder(cx, weeks, result)) {
   1573      return nullptr;
   1574    }
   1575    if (!result.append('W')) {
   1576      return nullptr;
   1577    }
   1578  }
   1579 
   1580  // Step 6.
   1581  if (days != 0) {
   1582    if (!NumberToStringBuilder(cx, days, result)) {
   1583      return nullptr;
   1584    }
   1585    if (!result.append('D')) {
   1586      return nullptr;
   1587    }
   1588  }
   1589 
   1590  // Step 7. (Moved above)
   1591 
   1592  // Steps 10-11. (Reordered)
   1593  bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
   1594                              days == 0 && hours == 0 && minutes == 0;
   1595 
   1596  // Step 12.
   1597  auto secondsDuration = TimeDurationFromComponents(
   1598      0.0, 0.0, seconds, milliseconds, microseconds, nanoseconds);
   1599 
   1600  // Steps 8-9, 13, and 16.
   1601  bool hasSecondsPart = (secondsDuration != TimeDuration{}) ||
   1602                        zeroMinutesAndHigher || precision != Precision::Auto();
   1603  if (hours != 0 || minutes != 0 || hasSecondsPart) {
   1604    // Step 16. (Reordered)
   1605    if (!result.append('T')) {
   1606      return nullptr;
   1607    }
   1608 
   1609    // Step 8.
   1610    if (hours != 0) {
   1611      if (!NumberToStringBuilder(cx, hours, result)) {
   1612        return nullptr;
   1613      }
   1614      if (!result.append('H')) {
   1615        return nullptr;
   1616      }
   1617    }
   1618 
   1619    // Step 9.
   1620    if (minutes != 0) {
   1621      if (!NumberToStringBuilder(cx, minutes, result)) {
   1622        return nullptr;
   1623      }
   1624      if (!result.append('M')) {
   1625        return nullptr;
   1626      }
   1627    }
   1628 
   1629    // Step 13.
   1630    if (hasSecondsPart) {
   1631      // Step 13.a.
   1632      if (!NumberToStringBuilder(cx, double(secondsDuration.seconds), result)) {
   1633        return nullptr;
   1634      }
   1635 
   1636      // Step 13.b.
   1637      if (!FormatFractionalSeconds(result, secondsDuration.nanoseconds,
   1638                                   precision)) {
   1639        return nullptr;
   1640      }
   1641 
   1642      // Step 13.c.
   1643      if (!result.append('S')) {
   1644        return nullptr;
   1645      }
   1646    }
   1647  }
   1648 
   1649  // Steps 14-16. (Moved above)
   1650 
   1651  // Step 17.
   1652  return result.finishString();
   1653 }
   1654 
   1655 /**
   1656 * GetTemporalRelativeToOption ( options )
   1657 */
   1658 static bool GetTemporalRelativeToOption(
   1659    JSContext* cx, Handle<JSObject*> options,
   1660    MutableHandle<PlainDate> plainRelativeTo,
   1661    MutableHandle<ZonedDateTime> zonedRelativeTo) {
   1662  // Default initialize both return values.
   1663  plainRelativeTo.set(PlainDate{});
   1664  zonedRelativeTo.set(ZonedDateTime{});
   1665 
   1666  // Step 1.
   1667  Rooted<Value> value(cx);
   1668  if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
   1669    return false;
   1670  }
   1671 
   1672  // Step 2.
   1673  if (value.isUndefined()) {
   1674    return true;
   1675  }
   1676 
   1677  // Step 3.
   1678  auto offsetBehaviour = OffsetBehaviour::Option;
   1679 
   1680  // Step 4.
   1681  auto matchBehaviour = MatchBehaviour::MatchExactly;
   1682 
   1683  // Steps 5-6.
   1684  EpochNanoseconds epochNanoseconds;
   1685  Rooted<TimeZoneValue> timeZone(cx);
   1686  Rooted<CalendarValue> calendar(cx);
   1687  if (value.isObject()) {
   1688    Rooted<JSObject*> obj(cx, &value.toObject());
   1689 
   1690    // Step 5.a.
   1691    if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
   1692      auto epochNs = zonedDateTime->epochNanoseconds();
   1693      Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
   1694      Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
   1695 
   1696      if (!timeZone.wrap(cx)) {
   1697        return false;
   1698      }
   1699      if (!calendar.wrap(cx)) {
   1700        return false;
   1701      }
   1702 
   1703      // Step 5.a.i.
   1704      zonedRelativeTo.set(ZonedDateTime{epochNs, timeZone, calendar});
   1705      return true;
   1706    }
   1707 
   1708    // Step 5.b.
   1709    if (auto* plainDate = obj->maybeUnwrapIf<PlainDateObject>()) {
   1710      auto date = plainDate->date();
   1711 
   1712      Rooted<CalendarValue> calendar(cx, plainDate->calendar());
   1713      if (!calendar.wrap(cx)) {
   1714        return false;
   1715      }
   1716 
   1717      // Step 5.b.i.
   1718      plainRelativeTo.set(PlainDate{date, calendar});
   1719      return true;
   1720    }
   1721 
   1722    // Step 5.c.
   1723    if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
   1724      auto date = dateTime->date();
   1725 
   1726      Rooted<CalendarValue> calendar(cx, dateTime->calendar());
   1727      if (!calendar.wrap(cx)) {
   1728        return false;
   1729      }
   1730 
   1731      // Steps 5.c.i-ii.
   1732      plainRelativeTo.set(PlainDate{date, calendar});
   1733      return true;
   1734    }
   1735 
   1736    // Step 5.d.
   1737    if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
   1738      return false;
   1739    }
   1740 
   1741    // Step 5.e.
   1742    Rooted<CalendarFields> fields(cx);
   1743    if (!PrepareCalendarFields(cx, calendar, obj,
   1744                               {
   1745                                   CalendarField::Year,
   1746                                   CalendarField::Month,
   1747                                   CalendarField::MonthCode,
   1748                                   CalendarField::Day,
   1749                                   CalendarField::Hour,
   1750                                   CalendarField::Minute,
   1751                                   CalendarField::Second,
   1752                                   CalendarField::Millisecond,
   1753                                   CalendarField::Microsecond,
   1754                                   CalendarField::Nanosecond,
   1755                                   CalendarField::Offset,
   1756                                   CalendarField::TimeZone,
   1757                               },
   1758                               &fields)) {
   1759      return false;
   1760    }
   1761 
   1762    // Step 5.f.
   1763    ISODateTime dateTime;
   1764    if (!InterpretTemporalDateTimeFields(
   1765            cx, calendar, fields, TemporalOverflow::Constrain, &dateTime)) {
   1766      return false;
   1767    }
   1768 
   1769    // Step 5.g.
   1770    timeZone = fields.timeZone();
   1771 
   1772    // Step 5.h.
   1773    auto offset = fields.offset();
   1774 
   1775    // Step 5.j.
   1776    if (!fields.has(CalendarField::Offset)) {
   1777      offsetBehaviour = OffsetBehaviour::Wall;
   1778    }
   1779 
   1780    // Step 7.
   1781    if (!timeZone) {
   1782      // Steps 7.a-b.
   1783      return CreateTemporalDate(cx, dateTime.date, calendar, plainRelativeTo);
   1784    }
   1785 
   1786    // Steps 8-9.
   1787    int64_t offsetNs = 0;
   1788    if (offsetBehaviour == OffsetBehaviour::Option) {
   1789      // Step 8.a.
   1790      offsetNs = int64_t(offset);
   1791    }
   1792 
   1793    // Step 10.
   1794    if (!InterpretISODateTimeOffset(
   1795            cx, dateTime, offsetBehaviour, offsetNs, timeZone,
   1796            TemporalDisambiguation::Compatible, TemporalOffset::Reject,
   1797            matchBehaviour, &epochNanoseconds)) {
   1798      return false;
   1799    }
   1800  } else {
   1801    // Step 6.a.
   1802    if (!value.isString()) {
   1803      ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
   1804                       nullptr, "not a string");
   1805      return false;
   1806    }
   1807    Rooted<JSString*> string(cx, value.toString());
   1808 
   1809    // Step 6.b.
   1810    Rooted<ParsedZonedDateTime> parsed(cx);
   1811    if (!ParseTemporalRelativeToString(cx, string, &parsed)) {
   1812      return false;
   1813    }
   1814 
   1815    // Steps 6.c-e. (Not applicable in our implementation.)
   1816 
   1817    // Step 6.f.
   1818    if (parsed.timeZoneAnnotation()) {
   1819      // Step 6.f.i.
   1820      if (!ToTemporalTimeZone(cx, parsed.timeZoneAnnotation(), &timeZone)) {
   1821        return false;
   1822      }
   1823 
   1824      // Steps 6.f.ii-iii.
   1825      if (parsed.timeZone().constructed<UTCTimeZone>()) {
   1826        offsetBehaviour = OffsetBehaviour::Exact;
   1827      } else if (parsed.timeZone().empty()) {
   1828        offsetBehaviour = OffsetBehaviour::Wall;
   1829      }
   1830 
   1831      // Step 6.f.iv.
   1832      matchBehaviour = MatchBehaviour::MatchMinutes;
   1833 
   1834      // Step 6.f.v.
   1835      if (parsed.timeZone().constructed<OffsetTimeZone>()) {
   1836        // Steps 6.f.v.1-3.
   1837        if (parsed.timeZone().ref<OffsetTimeZone>().hasSubMinutePrecision) {
   1838          matchBehaviour = MatchBehaviour::MatchExactly;
   1839        }
   1840      }
   1841    } else {
   1842      MOZ_ASSERT(!timeZone);
   1843    }
   1844 
   1845    // Steps 6.g-i.
   1846    if (parsed.calendar()) {
   1847      if (!CanonicalizeCalendar(cx, parsed.calendar(), &calendar)) {
   1848        return false;
   1849      }
   1850    } else {
   1851      calendar.set(CalendarValue(CalendarId::ISO8601));
   1852    }
   1853 
   1854    // Step 7.
   1855    if (!timeZone) {
   1856      // Steps 7.a-b.
   1857      return CreateTemporalDate(cx, parsed.dateTime().date, calendar,
   1858                                plainRelativeTo);
   1859    }
   1860 
   1861    // Steps 8-9.
   1862    int64_t offsetNs;
   1863    if (offsetBehaviour == OffsetBehaviour::Option) {
   1864      MOZ_ASSERT(parsed.timeZone().constructed<OffsetTimeZone>());
   1865 
   1866      // Step 8.a.
   1867      offsetNs = parsed.timeZone().ref<OffsetTimeZone>().offset;
   1868    } else {
   1869      // Step 9.
   1870      offsetNs = 0;
   1871    }
   1872 
   1873    // Step 10.
   1874    if (parsed.isStartOfDay()) {
   1875      if (!InterpretISODateTimeOffset(
   1876              cx, parsed.dateTime().date, offsetBehaviour, offsetNs, timeZone,
   1877              TemporalDisambiguation::Compatible, TemporalOffset::Reject,
   1878              matchBehaviour, &epochNanoseconds)) {
   1879        return false;
   1880      }
   1881    } else {
   1882      if (!InterpretISODateTimeOffset(
   1883              cx, parsed.dateTime(), offsetBehaviour, offsetNs, timeZone,
   1884              TemporalDisambiguation::Compatible, TemporalOffset::Reject,
   1885              matchBehaviour, &epochNanoseconds)) {
   1886        return false;
   1887      }
   1888    }
   1889  }
   1890  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
   1891 
   1892  // Steps 11-12.
   1893  zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
   1894  return true;
   1895 }
   1896 
   1897 /**
   1898 * RoundTimeDurationToIncrement ( d, increment, roundingMode )
   1899 */
   1900 static TimeDuration RoundTimeDurationToIncrement(
   1901    const TimeDuration& duration, const TemporalUnit unit, Increment increment,
   1902    TemporalRoundingMode roundingMode) {
   1903  MOZ_ASSERT(IsValidTimeDuration(duration));
   1904  MOZ_ASSERT(unit >= TemporalUnit::Day);
   1905  MOZ_ASSERT_IF(unit >= TemporalUnit::Hour,
   1906                increment <= MaximumTemporalDurationRoundingIncrement(unit));
   1907 
   1908  auto divisor = Int128{ToNanoseconds(unit)} * Int128{increment.value()};
   1909  MOZ_ASSERT(divisor > Int128{0});
   1910  MOZ_ASSERT_IF(unit >= TemporalUnit::Hour,
   1911                divisor <= Int128{ToNanoseconds(TemporalUnit::Day)});
   1912 
   1913  auto totalNanoseconds = duration.toNanoseconds();
   1914  auto rounded =
   1915      RoundNumberToIncrement(totalNanoseconds, divisor, roundingMode);
   1916  return TimeDuration::fromNanoseconds(rounded);
   1917 }
   1918 
   1919 /**
   1920 * TotalTimeDuration ( timeDuration, unit )
   1921 */
   1922 double js::temporal::TotalTimeDuration(const TimeDuration& duration,
   1923                                       TemporalUnit unit) {
   1924  MOZ_ASSERT(IsValidTimeDuration(duration));
   1925  MOZ_ASSERT(unit >= TemporalUnit::Day);
   1926 
   1927  auto numerator = duration.toNanoseconds();
   1928  auto denominator = Int128{ToNanoseconds(unit)};
   1929  return FractionToDouble(numerator, denominator);
   1930 }
   1931 
   1932 /**
   1933 * RoundTimeDuration ( duration, increment, unit, roundingMode )
   1934 */
   1935 static bool RoundTimeDuration(JSContext* cx, const TimeDuration& duration,
   1936                              Increment increment, TemporalUnit unit,
   1937                              TemporalRoundingMode roundingMode,
   1938                              TimeDuration* result) {
   1939  MOZ_ASSERT(IsValidTimeDuration(duration));
   1940  MOZ_ASSERT(increment <= Increment::max());
   1941  MOZ_ASSERT(unit > TemporalUnit::Day);
   1942 
   1943  // Step 1-2.
   1944  auto rounded =
   1945      RoundTimeDurationToIncrement(duration, unit, increment, roundingMode);
   1946  if (!IsValidTimeDuration(rounded)) {
   1947    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1948                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
   1949    return false;
   1950  }
   1951  *result = rounded;
   1952  return true;
   1953 }
   1954 
   1955 /**
   1956 * RoundTimeDuration ( duration, increment, unit, roundingMode )
   1957 */
   1958 TimeDuration js::temporal::RoundTimeDuration(
   1959    const TimeDuration& duration, Increment increment, TemporalUnit unit,
   1960    TemporalRoundingMode roundingMode) {
   1961  MOZ_ASSERT(IsValidTimeDuration(duration));
   1962  MOZ_ASSERT(increment <= Increment::max());
   1963  MOZ_ASSERT(unit > TemporalUnit::Day);
   1964 
   1965  auto result =
   1966      RoundTimeDurationToIncrement(duration, unit, increment, roundingMode);
   1967  MOZ_ASSERT(IsValidTimeDuration(result));
   1968 
   1969  return result;
   1970 }
   1971 
   1972 #ifdef DEBUG
   1973 /**
   1974 * Return true if the input is within the valid epoch nanoseconds limits with a
   1975 * time zone offset applied, i.e. it's smaller than ±(8.64 × 10^21 + nsPerDay).
   1976 */
   1977 static bool IsValidLocalNanoseconds(const EpochNanoseconds& epochNanoseconds) {
   1978  MOZ_ASSERT(0 <= epochNanoseconds.nanoseconds &&
   1979             epochNanoseconds.nanoseconds <= 999'999'999);
   1980 
   1981  // Time zone offsets can't exceed 24 hours.
   1982  constexpr auto oneDay = EpochDuration::fromDays(1);
   1983 
   1984  // Exclusive limits.
   1985  constexpr auto min = EpochNanoseconds::min() - oneDay;
   1986  constexpr auto max = EpochNanoseconds::max() + oneDay;
   1987 
   1988  return min < epochNanoseconds && epochNanoseconds < max;
   1989 }
   1990 #endif
   1991 
   1992 enum class UnsignedRoundingMode {
   1993  Zero,
   1994  Infinity,
   1995  HalfZero,
   1996  HalfInfinity,
   1997  HalfEven
   1998 };
   1999 
   2000 /**
   2001 * GetUnsignedRoundingMode ( roundingMode, sign )
   2002 */
   2003 static UnsignedRoundingMode GetUnsignedRoundingMode(
   2004    TemporalRoundingMode roundingMode, bool isNegative) {
   2005  switch (roundingMode) {
   2006    case TemporalRoundingMode::Ceil:
   2007      return isNegative ? UnsignedRoundingMode::Zero
   2008                        : UnsignedRoundingMode::Infinity;
   2009    case TemporalRoundingMode::Floor:
   2010      return isNegative ? UnsignedRoundingMode::Infinity
   2011                        : UnsignedRoundingMode::Zero;
   2012    case TemporalRoundingMode::Expand:
   2013      return UnsignedRoundingMode::Infinity;
   2014    case TemporalRoundingMode::Trunc:
   2015      return UnsignedRoundingMode::Zero;
   2016    case TemporalRoundingMode::HalfCeil:
   2017      return isNegative ? UnsignedRoundingMode::HalfZero
   2018                        : UnsignedRoundingMode::HalfInfinity;
   2019    case TemporalRoundingMode::HalfFloor:
   2020      return isNegative ? UnsignedRoundingMode::HalfInfinity
   2021                        : UnsignedRoundingMode::HalfZero;
   2022    case TemporalRoundingMode::HalfExpand:
   2023      return UnsignedRoundingMode::HalfInfinity;
   2024    case TemporalRoundingMode::HalfTrunc:
   2025      return UnsignedRoundingMode::HalfZero;
   2026    case TemporalRoundingMode::HalfEven:
   2027      return UnsignedRoundingMode::HalfEven;
   2028  }
   2029  MOZ_CRASH("invalid rounding mode");
   2030 }
   2031 
   2032 struct NudgeWindow {
   2033  int64_t r1;
   2034  int64_t r2;
   2035  EpochNanoseconds startEpochNs;
   2036  EpochNanoseconds endEpochNs;
   2037  DateDuration startDuration;
   2038  DateDuration endDuration;
   2039 };
   2040 
   2041 /**
   2042 * ComputeNudgeWindow ( sign, duration, originEpochNs, isoDateTime, timeZone,
   2043 * calendar, increment, unit, additionalShift )
   2044 */
   2045 static bool ComputeNudgeWindow(JSContext* cx, const InternalDuration& duration,
   2046                               const EpochNanoseconds& originEpochNs,
   2047                               const ISODateTime& isoDateTime,
   2048                               Handle<TimeZoneValue> timeZone,
   2049                               Handle<CalendarValue> calendar,
   2050                               Increment increment, TemporalUnit unit,
   2051                               bool additionalShift, NudgeWindow* result) {
   2052  MOZ_ASSERT(IsValidDuration(duration));
   2053  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs));
   2054  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs));
   2055  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
   2056  MOZ_ASSERT(unit <= TemporalUnit::Day);
   2057 
   2058  int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1;
   2059 
   2060  // Steps 1-4.
   2061  int64_t r1;
   2062  int64_t r2;
   2063  DateDuration startDuration;
   2064  DateDuration endDuration;
   2065  if (unit == TemporalUnit::Year) {
   2066    // Step 1.a.
   2067    int64_t years = RoundNumberToIncrement(duration.date.years, increment,
   2068                                           TemporalRoundingMode::Trunc);
   2069 
   2070    // Steps 1.b-c.
   2071    if (!additionalShift) {
   2072      r1 = years;
   2073    } else {
   2074      r1 = years + int64_t(increment.value()) * sign;
   2075    }
   2076 
   2077    // Step 1.d.
   2078    r2 = r1 + int64_t(increment.value()) * sign;
   2079 
   2080    // Step 1.e.
   2081    startDuration = {r1};
   2082 
   2083    // Step 1.f.
   2084    endDuration = {r2};
   2085  } else if (unit == TemporalUnit::Month) {
   2086    // Step 2.a.
   2087    int64_t months = RoundNumberToIncrement(duration.date.months, increment,
   2088                                            TemporalRoundingMode::Trunc);
   2089 
   2090    // Steps 2.b-c.
   2091    if (!additionalShift) {
   2092      r1 = months;
   2093    } else {
   2094      r1 = months + int64_t(increment.value()) * sign;
   2095    }
   2096 
   2097    // Step 2.d.
   2098    r2 = r1 + int64_t(increment.value()) * sign;
   2099 
   2100    // Step 2.e.
   2101    startDuration = {duration.date.years, r1};
   2102 
   2103    // Step 2.f.
   2104    endDuration = {duration.date.years, r2};
   2105  } else if (unit == TemporalUnit::Week) {
   2106    // Step 3.a.
   2107    auto yearsMonths = DateDuration{duration.date.years, duration.date.months};
   2108 
   2109    // Step 3.b.
   2110    ISODate weeksStart;
   2111    if (!CalendarDateAdd(cx, calendar, isoDateTime.date, yearsMonths,
   2112                         TemporalOverflow::Constrain, &weeksStart)) {
   2113      return false;
   2114    }
   2115    MOZ_ASSERT(ISODateWithinLimits(weeksStart));
   2116 
   2117    // Step 3.c.
   2118    ISODate weeksEnd;
   2119    if (!BalanceISODate(cx, weeksStart, duration.date.days, &weeksEnd)) {
   2120      return false;
   2121    }
   2122    MOZ_ASSERT(ISODateWithinLimits(weeksEnd));
   2123 
   2124    // Step 3.d.
   2125    DateDuration untilResult;
   2126    if (!CalendarDateUntil(cx, calendar, weeksStart, weeksEnd,
   2127                           TemporalUnit::Week, &untilResult)) {
   2128      return false;
   2129    }
   2130 
   2131    // Step 3.e.
   2132    int64_t weeks =
   2133        RoundNumberToIncrement(duration.date.weeks + untilResult.weeks,
   2134                               increment, TemporalRoundingMode::Trunc);
   2135 
   2136    // Steps 3.f-g.
   2137    if (!additionalShift) {
   2138      r1 = weeks;
   2139    } else {
   2140      r1 = weeks + int64_t(increment.value()) * sign;
   2141    }
   2142 
   2143    // Step 3.h.
   2144    r2 = r1 + int64_t(increment.value()) * sign;
   2145 
   2146    // Step 3.i.
   2147    startDuration = {duration.date.years, duration.date.months, r1};
   2148 
   2149    // Step 3.j.
   2150    endDuration = {duration.date.years, duration.date.months, r2};
   2151  } else {
   2152    // Step 4.a.
   2153    MOZ_ASSERT(unit == TemporalUnit::Day);
   2154 
   2155    // Step 4.b.
   2156    int64_t days = RoundNumberToIncrement(duration.date.days, increment,
   2157                                          TemporalRoundingMode::Trunc);
   2158 
   2159    // Steps 4.c-d.
   2160    if (!additionalShift) {
   2161      r1 = days;
   2162    } else {
   2163      r1 = days + int64_t(increment.value()) * sign;
   2164    }
   2165 
   2166    // Step 4.e.
   2167    r2 = r1 + int64_t(increment.value()) * sign;
   2168 
   2169    // Step 4.f.
   2170    startDuration = {duration.date.years, duration.date.months,
   2171                     duration.date.weeks, r1};
   2172 
   2173    // Step 4.g.
   2174    endDuration = {duration.date.years, duration.date.months,
   2175                   duration.date.weeks, r2};
   2176  }
   2177  MOZ_ASSERT(IsValidDuration(startDuration));
   2178  MOZ_ASSERT(IsValidDuration(endDuration));
   2179 
   2180  // Step 5.
   2181  MOZ_ASSERT_IF(sign > 0, r1 >= 0 && r1 < r2);
   2182 
   2183  // Step 6.
   2184  MOZ_ASSERT_IF(sign < 0, r1 <= 0 && r1 > r2);
   2185 
   2186  // Steps 7-8.
   2187  EpochNanoseconds startEpochNs;
   2188  if (r1 == 0) {
   2189    // Step 7.a.
   2190    startEpochNs = originEpochNs;
   2191  } else {
   2192    // Step 8.a.
   2193    ISODate start;
   2194    if (!CalendarDateAdd(cx, calendar, isoDateTime.date, startDuration,
   2195                         TemporalOverflow::Constrain, &start)) {
   2196      return false;
   2197    }
   2198 
   2199    // Step 8.b.
   2200    auto startDateTime = ISODateTime{start, isoDateTime.time};
   2201    MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime));
   2202 
   2203    // Steps 8.c-d.
   2204    if (!timeZone) {
   2205      // Step 8.c.
   2206      startEpochNs = GetUTCEpochNanoseconds(startDateTime);
   2207    } else {
   2208      // Step 8.d.
   2209      if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime,
   2210                                  TemporalDisambiguation::Compatible,
   2211                                  &startEpochNs)) {
   2212        return false;
   2213      }
   2214    }
   2215  }
   2216 
   2217  // Step 9.
   2218  ISODate end;
   2219  if (!CalendarDateAdd(cx, calendar, isoDateTime.date, endDuration,
   2220                       TemporalOverflow::Constrain, &end)) {
   2221    return false;
   2222  }
   2223 
   2224  // Step 10.
   2225  auto endDateTime = ISODateTime{end, isoDateTime.time};
   2226  MOZ_ASSERT(ISODateTimeWithinLimits(endDateTime));
   2227 
   2228  // Steps 11-12.
   2229  EpochNanoseconds endEpochNs;
   2230  if (!timeZone) {
   2231    // Step 11.a.
   2232    endEpochNs = GetUTCEpochNanoseconds(endDateTime);
   2233  } else {
   2234    // Step 12.a.
   2235    if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime,
   2236                                TemporalDisambiguation::Compatible,
   2237                                &endEpochNs)) {
   2238      return false;
   2239    }
   2240  }
   2241 
   2242  // Step 13.
   2243  *result = {r1, r2, startEpochNs, endEpochNs, startDuration, endDuration};
   2244  return true;
   2245 }
   2246 
   2247 struct DurationNudge {
   2248  InternalDuration duration;
   2249  EpochNanoseconds epochNs;
   2250  double total = 0;
   2251  bool didExpandCalendarUnit = false;
   2252 };
   2253 
   2254 /**
   2255 * NudgeToCalendarUnit ( sign, duration, originEpochNs, destEpochNs,
   2256 * isoDateTime, timeZone, calendar, increment, unit, roundingMode )
   2257 */
   2258 static bool NudgeToCalendarUnit(
   2259    JSContext* cx, const InternalDuration& duration,
   2260    const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs,
   2261    const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone,
   2262    Handle<CalendarValue> calendar, Increment increment, TemporalUnit unit,
   2263    TemporalRoundingMode roundingMode, DurationNudge* result) {
   2264  MOZ_ASSERT(IsValidDuration(duration));
   2265  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs));
   2266  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs));
   2267  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs));
   2268  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs));
   2269  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
   2270  MOZ_ASSERT(unit <= TemporalUnit::Day);
   2271 
   2272  int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1;
   2273 
   2274  // Step 1.
   2275  bool didExpandCalendarUnit = false;
   2276 
   2277  // Step 2.
   2278  NudgeWindow nudgeWindow;
   2279  if (!ComputeNudgeWindow(cx, duration, originEpochNs, isoDateTime, timeZone,
   2280                          calendar, increment, unit, false, &nudgeWindow)) {
   2281    return false;
   2282  }
   2283 
   2284  // Steps 3-6.
   2285  const auto& startPoint =
   2286      sign > 0 ? nudgeWindow.startEpochNs : nudgeWindow.endEpochNs;
   2287  const auto& endPoint =
   2288      sign > 0 ? nudgeWindow.endEpochNs : nudgeWindow.startEpochNs;
   2289  if (!(startPoint <= destEpochNs && destEpochNs <= endPoint)) {
   2290    // Steps 5.a.i and 6.a.i.
   2291    if (!ComputeNudgeWindow(cx, duration, originEpochNs, isoDateTime, timeZone,
   2292                            calendar, increment, unit, true, &nudgeWindow)) {
   2293      return false;
   2294    }
   2295 
   2296    // Steps 5.a.ii and 6.a.ii. (Moved below)
   2297 
   2298    // Steps 5.a.iii and 6.a.iii.
   2299    didExpandCalendarUnit = true;
   2300  }
   2301 
   2302  // Steps 7-12.
   2303  const auto& [r1, r2, startEpochNs, endEpochNs, startDuration, endDuration] =
   2304      nudgeWindow;
   2305 
   2306  // Step 13.
   2307  MOZ_ASSERT(startEpochNs != endEpochNs);
   2308  MOZ_ASSERT_IF(sign > 0,
   2309                startEpochNs <= destEpochNs && destEpochNs <= endEpochNs);
   2310  MOZ_ASSERT_IF(sign < 0,
   2311                endEpochNs <= destEpochNs && destEpochNs <= startEpochNs);
   2312 
   2313  // Step 14.
   2314  auto numerator = (destEpochNs - startEpochNs).toNanoseconds();
   2315  auto denominator = (endEpochNs - startEpochNs).toNanoseconds();
   2316  MOZ_ASSERT(denominator != Int128{0});
   2317  MOZ_ASSERT(numerator.abs() <= denominator.abs());
   2318  MOZ_ASSERT_IF(denominator > Int128{0}, numerator >= Int128{0});
   2319  MOZ_ASSERT_IF(denominator < Int128{0}, numerator <= Int128{0});
   2320 
   2321  // Ensure |numerator| and |denominator| are both non-negative to simplify the
   2322  // following computations.
   2323  if (denominator < Int128{0}) {
   2324    numerator = -numerator;
   2325    denominator = -denominator;
   2326  }
   2327 
   2328  // Steps 15-17.
   2329  //
   2330  // |total| must only be computed when called from Duration.prototype.total,
   2331  // which always passes "trunc" rounding mode with an increment of one.
   2332  double total = mozilla::UnspecifiedNaN<double>();
   2333  if (roundingMode == TemporalRoundingMode::Trunc &&
   2334      increment == Increment{1}) {
   2335    // total = r1 + progress × increment × sign
   2336    //       = r1 + (numerator / denominator) × increment × sign
   2337    //       = r1 + (numerator × increment × sign) / denominator
   2338    //       = (r1 × denominator + numerator × increment × sign) / denominator
   2339    //
   2340    // Computing `n` can't overflow, because:
   2341    // - For years, months, and weeks, `abs(r1) ≤ 2^32`.
   2342    // - For days, `abs(r1) < ⌈(2^53) / (24 * 60 * 60)⌉`.
   2343    // - `denominator` and `numerator` are below-or-equal `2 × 8.64 × 10^21`.
   2344    // - And finally `increment ≤ 10^9`.
   2345    auto n = Int128{r1} * denominator + numerator * Int128{sign};
   2346    total = FractionToDouble(n, denominator);
   2347  }
   2348 
   2349  // Steps 18-19.
   2350  auto unsignedRoundingMode = GetUnsignedRoundingMode(roundingMode, sign < 0);
   2351 
   2352  // Steps 20-21. (Inlined ApplyUnsignedRoundingMode)
   2353  //
   2354  // clang-format off
   2355  //
   2356  // ApplyUnsignedRoundingMode, steps 1-16.
   2357  //
   2358  // `total = r1` iff `progress = 0`. And `progress = 0` iff `numerator = 0`.
   2359  //
   2360  // d1 = total - r1
   2361  //    = (r1 × denominator + numerator × increment × sign) / denominator - r1
   2362  //    = (numerator × increment × sign) / denominator
   2363  //
   2364  // d2 = r2 - total
   2365  //    = r1 + increment - (r1 × denominator + numerator × increment × sign) / denominator
   2366  //    = (increment × denominator - numerator × increment × sign) / denominator
   2367  //
   2368  // d1 < d2
   2369  // ⇔ (numerator × increment × sign) / denominator < (increment × denominator - numerator × increment × sign) / denominator
   2370  // ⇔ (numerator × increment × sign) < (increment × denominator - numerator × increment × sign)
   2371  // ⇔ (numerator × sign) < (denominator - numerator × sign)
   2372  // ⇔ (2 × numerator × sign) < denominator
   2373  //
   2374  // cardinality = (r1 / (r2 – r1)) modulo 2
   2375  //             = (r1 / (r1 + increment - r1)) modulo 2
   2376  //             = (r1 / increment) modulo 2
   2377  //
   2378  // clang-format on
   2379  bool roundedUp;
   2380  if (numerator == denominator) {
   2381    roundedUp = true;
   2382  } else if (numerator == Int128{0}) {
   2383    roundedUp = false;
   2384  } else if (unsignedRoundingMode == UnsignedRoundingMode::Zero) {
   2385    roundedUp = false;
   2386  } else if (unsignedRoundingMode == UnsignedRoundingMode::Infinity) {
   2387    roundedUp = true;
   2388  } else if (numerator + numerator < denominator) {
   2389    roundedUp = false;
   2390  } else if (numerator + numerator > denominator) {
   2391    roundedUp = true;
   2392  } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfZero) {
   2393    roundedUp = false;
   2394  } else if (unsignedRoundingMode == UnsignedRoundingMode::HalfInfinity) {
   2395    roundedUp = true;
   2396  } else if ((r1 / increment.value()) % 2 == 0) {
   2397    roundedUp = false;
   2398  } else {
   2399    roundedUp = true;
   2400  }
   2401 
   2402  // Steps 22-26.
   2403  auto resultDuration = roundedUp ? endDuration : startDuration;
   2404  auto resultEpochNs = roundedUp ? endEpochNs : startEpochNs;
   2405  *result = {
   2406      {resultDuration, {}},
   2407      resultEpochNs,
   2408      total,
   2409      didExpandCalendarUnit || roundedUp,
   2410  };
   2411  return true;
   2412 }
   2413 
   2414 #ifdef DEBUG
   2415 static bool IsValidTimeFromDateTimeDuration(const TimeDuration& timeDuration) {
   2416  // Time zone adjustment can't exceed 24 hours.
   2417  constexpr auto oneDay = EpochDuration::fromDays(1);
   2418 
   2419  // Time zone adjusted nsMinInstant and nsMaxInstant.
   2420  constexpr auto min = EpochNanoseconds::min() - oneDay;
   2421  constexpr auto max = EpochNanoseconds::max() + oneDay;
   2422 
   2423  // Maximum duration between two date-time points.
   2424  constexpr auto maxDuration = (max - min).to<TimeDuration>();
   2425  static_assert(maxDuration == TimeDuration::fromDays(200'000'002));
   2426 
   2427  // If |timeDuration| is a duration between two date-times within the valid
   2428  // limits, the duration can't exceed the duration between time zone adjusted
   2429  // nsMinInstant and nsMaxInstant.
   2430  return timeDuration.abs() < maxDuration;
   2431 }
   2432 #endif
   2433 
   2434 /**
   2435 * NudgeToZonedTime ( sign, duration, isoDateTime, timeZone, calendar,
   2436 * increment, unit, roundingMode )
   2437 */
   2438 static bool NudgeToZonedTime(JSContext* cx, const InternalDuration& duration,
   2439                             const ISODateTime& isoDateTime,
   2440                             Handle<TimeZoneValue> timeZone,
   2441                             Handle<CalendarValue> calendar,
   2442                             Increment increment, TemporalUnit unit,
   2443                             TemporalRoundingMode roundingMode,
   2444                             DurationNudge* result) {
   2445  MOZ_ASSERT(IsValidDuration(duration));
   2446  MOZ_ASSERT(IsValidTimeFromDateTimeDuration(duration.time));
   2447  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
   2448  MOZ_ASSERT(unit >= TemporalUnit::Hour);
   2449 
   2450  int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1;
   2451 
   2452  // Step 1.
   2453  ISODate start;
   2454  if (!CalendarDateAdd(cx, calendar, isoDateTime.date, duration.date,
   2455                       TemporalOverflow::Constrain, &start)) {
   2456    return false;
   2457  }
   2458 
   2459  // Step 2.
   2460  auto startDateTime = ISODateTime{start, isoDateTime.time};
   2461  MOZ_ASSERT(ISODateTimeWithinLimits(startDateTime));
   2462 
   2463  // Step 3.
   2464  auto end = BalanceISODate(start, sign);
   2465 
   2466  // Step 4.
   2467  auto endDateTime = ISODateTime{end, isoDateTime.time};
   2468  if (!ISODateTimeWithinLimits(endDateTime)) {
   2469    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2470                              JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
   2471    return false;
   2472  }
   2473 
   2474  // Step 5.
   2475  EpochNanoseconds startEpochNs;
   2476  if (!GetEpochNanosecondsFor(cx, timeZone, startDateTime,
   2477                              TemporalDisambiguation::Compatible,
   2478                              &startEpochNs)) {
   2479    return false;
   2480  }
   2481 
   2482  // Step 6.
   2483  EpochNanoseconds endEpochNs;
   2484  if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime,
   2485                              TemporalDisambiguation::Compatible,
   2486                              &endEpochNs)) {
   2487    return false;
   2488  }
   2489 
   2490  // Step 7.
   2491  auto daySpan =
   2492      TimeDurationFromEpochNanosecondsDifference(endEpochNs, startEpochNs);
   2493  MOZ_ASSERT(daySpan.abs() <= TimeDuration::fromDays(2),
   2494             "maximum day length for repeated days");
   2495 
   2496  // Step 8.
   2497  MOZ_ASSERT(TimeDurationSign(daySpan) == sign);
   2498 
   2499  // Steps 9-10.
   2500  //
   2501  // RoundTimeDurationToIncrement is infallible |duration.time| is a valid
   2502  // date-time duration.
   2503  auto roundedTime = RoundTimeDurationToIncrement(duration.time, unit,
   2504                                                  increment, roundingMode);
   2505  MOZ_ASSERT(IsValidTimeDuration(roundedTime));
   2506 
   2507  // Step 11. (Inlined AddTimeDuration)
   2508  auto beyondDaySpan = roundedTime - daySpan;
   2509  MOZ_ASSERT(IsValidTimeDuration(beyondDaySpan));
   2510 
   2511  // Steps 12-13.
   2512  bool didRoundBeyondDay;
   2513  int32_t dayDelta;
   2514  EpochNanoseconds nudgedEpochNs;
   2515  if (TimeDurationSign(beyondDaySpan) != -sign) {
   2516    // Step 12.a.
   2517    didRoundBeyondDay = true;
   2518 
   2519    // Step 12.b.
   2520    dayDelta = sign;
   2521 
   2522    // Step 12.c.
   2523    //
   2524    // This call to RoundTimeDurationToIncrement is also infallible.
   2525    roundedTime = RoundTimeDurationToIncrement(beyondDaySpan, unit, increment,
   2526                                               roundingMode);
   2527    MOZ_ASSERT(IsValidTimeDuration(roundedTime));
   2528 
   2529    // Step 12.d. (Inlined AddTimeDurationToEpochNanoseconds)
   2530    nudgedEpochNs = endEpochNs + roundedTime.to<EpochDuration>();
   2531  } else {
   2532    // Step 13.a.
   2533    didRoundBeyondDay = false;
   2534 
   2535    // Step 13.b.
   2536    dayDelta = 0;
   2537 
   2538    // Step 13.c. (Inlined AddTimeDurationToEpochNanoseconds)
   2539    nudgedEpochNs = startEpochNs + roundedTime.to<EpochDuration>();
   2540  }
   2541 
   2542  // Step 14.
   2543  auto dateDuration = DateDuration{
   2544      duration.date.years,
   2545      duration.date.months,
   2546      duration.date.weeks,
   2547      duration.date.days + dayDelta,
   2548  };
   2549  MOZ_ASSERT(IsValidDuration(dateDuration));
   2550 
   2551  // Step 15.
   2552  MOZ_ASSERT(DateDurationSign(dateDuration) * TimeDurationSign(roundedTime) >=
   2553             0);
   2554  auto resultDuration = InternalDuration{dateDuration, roundedTime};
   2555 
   2556  // Step 16.
   2557  *result = {
   2558      resultDuration,
   2559      nudgedEpochNs,
   2560      mozilla::UnspecifiedNaN<double>(),
   2561      didRoundBeyondDay,
   2562  };
   2563  return true;
   2564 }
   2565 
   2566 /**
   2567 * NudgeToDayOrTime ( duration, destEpochNs, largestUnit, increment,
   2568 * smallestUnit, roundingMode )
   2569 */
   2570 static DurationNudge NudgeToDayOrTime(const InternalDuration& duration,
   2571                                      const EpochNanoseconds& destEpochNs,
   2572                                      TemporalUnit largestUnit,
   2573                                      Increment increment,
   2574                                      TemporalUnit smallestUnit,
   2575                                      TemporalRoundingMode roundingMode) {
   2576  MOZ_ASSERT(IsValidDuration(duration));
   2577  MOZ_ASSERT(IsValidLocalNanoseconds(destEpochNs));
   2578  MOZ_ASSERT(smallestUnit >= TemporalUnit::Day);
   2579 
   2580  // Step 1. (Inlined Add24HourDaysToTimeDuration)
   2581  auto timeDuration =
   2582      duration.time + TimeDuration::fromDays(duration.date.days);
   2583  MOZ_ASSERT(IsValidTimeDuration(timeDuration));
   2584  MOZ_ASSERT(IsValidTimeFromDateTimeDuration(timeDuration));
   2585 
   2586  // Steps 2-3.
   2587  //
   2588  // RoundTimeDurationToIncrement is infallible |timeDuration| is a valid
   2589  // date-time duration.
   2590  auto roundedTime = RoundTimeDurationToIncrement(timeDuration, smallestUnit,
   2591                                                  increment, roundingMode);
   2592  MOZ_ASSERT(IsValidTimeDuration(roundedTime));
   2593 
   2594  // Step 4. (Inlined AddTimeDuration)
   2595  auto diffTime = roundedTime - timeDuration;
   2596  MOZ_ASSERT(IsValidTimeDuration(diffTime));
   2597 
   2598  // Step 5.
   2599  int64_t wholeDays = timeDuration.toDays();
   2600 
   2601  // Step 6.
   2602  int64_t roundedWholeDays = roundedTime.toDays();
   2603 
   2604  // Step 7.
   2605  int64_t dayDelta = roundedWholeDays - wholeDays;
   2606 
   2607  // Step 8.
   2608  int32_t dayDeltaSign = dayDelta < 0 ? -1 : dayDelta > 0 ? 1 : 0;
   2609 
   2610  // Step 9.
   2611  bool didExpandDays = dayDeltaSign == TimeDurationSign(timeDuration);
   2612 
   2613  // Step 10. (Inlined AddTimeDurationToEpochNanoseconds)
   2614  auto nudgedEpochNs = destEpochNs + diffTime.to<EpochDuration>();
   2615 
   2616  // Step 11.
   2617  int64_t days = 0;
   2618 
   2619  // Step 12.
   2620  auto remainder = roundedTime;
   2621 
   2622  // Step 13.
   2623  if (largestUnit <= TemporalUnit::Day) {
   2624    // Step 13.a.
   2625    days = roundedWholeDays;
   2626 
   2627    // Step 13.b.
   2628    remainder = roundedTime - TimeDuration::fromDays(roundedWholeDays);
   2629    MOZ_ASSERT(IsValidTimeDuration(remainder));
   2630  }
   2631 
   2632  // Step 14.
   2633  auto dateDuration = DateDuration{
   2634      duration.date.years,
   2635      duration.date.months,
   2636      duration.date.weeks,
   2637      days,
   2638  };
   2639  MOZ_ASSERT(IsValidDuration(dateDuration));
   2640 
   2641  // Step 15.
   2642  MOZ_ASSERT(DateDurationSign(dateDuration) * TimeDurationSign(remainder) >= 0);
   2643  auto resultDuration = InternalDuration{dateDuration, remainder};
   2644 
   2645  // Step 16.
   2646  return {resultDuration, nudgedEpochNs, mozilla::UnspecifiedNaN<double>(),
   2647          didExpandDays};
   2648 }
   2649 
   2650 /**
   2651 * BubbleRelativeDuration ( sign, duration, nudgedEpochNs, isoDateTime,
   2652 * timeZone, calendar, largestUnit, smallestUnit )
   2653 */
   2654 static bool BubbleRelativeDuration(
   2655    JSContext* cx, const InternalDuration& duration, const DurationNudge& nudge,
   2656    const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone,
   2657    Handle<CalendarValue> calendar, TemporalUnit largestUnit,
   2658    TemporalUnit smallestUnit, InternalDuration* result) {
   2659  MOZ_ASSERT(IsValidDuration(duration));
   2660  MOZ_ASSERT(IsValidDuration(nudge.duration));
   2661  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
   2662  MOZ_ASSERT(smallestUnit <= TemporalUnit::Day);
   2663 
   2664  // Step 1. (Modified to use `<=` to return early.)
   2665  if (smallestUnit <= largestUnit) {
   2666    *result = nudge.duration;
   2667    return true;
   2668  }
   2669  MOZ_ASSERT(smallestUnit != TemporalUnit::Year);
   2670 
   2671  int32_t sign = InternalDurationSign(duration) < 0 ? -1 : 1;
   2672 
   2673  // Steps 2-6.
   2674  auto dateDuration = nudge.duration.date;
   2675  auto timeDuration = nudge.duration.time;
   2676  auto unit = smallestUnit;
   2677  while (unit > largestUnit) {
   2678    using TemporalUnitType = std::underlying_type_t<TemporalUnit>;
   2679 
   2680    static_assert(static_cast<TemporalUnitType>(TemporalUnit::Auto) == 1,
   2681                  "TemporalUnit::Auto has value one");
   2682    MOZ_ASSERT(unit > TemporalUnit::Auto, "can subtract unit by one");
   2683 
   2684    // Steps 4, 6.a, and 6.c.
   2685    unit = static_cast<TemporalUnit>(static_cast<TemporalUnitType>(unit) - 1);
   2686    MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Week);
   2687 
   2688    // Step 6.b.
   2689    if (unit != TemporalUnit::Week || largestUnit == TemporalUnit::Week) {
   2690      // Steps 6.b.i-iii.
   2691      DateDuration endDuration;
   2692      if (unit == TemporalUnit::Year) {
   2693        // Step 6.b.i.1.
   2694        int64_t years = dateDuration.years + sign;
   2695 
   2696        // Step 6.b.i.2.
   2697        endDuration = {years};
   2698      } else if (unit == TemporalUnit::Month) {
   2699        // Step 6.b.ii.1.
   2700        int64_t months = dateDuration.months + sign;
   2701 
   2702        // Step 6.b.ii.2.
   2703        endDuration = {dateDuration.years, months};
   2704      } else {
   2705        // Step 6.b.iii.1.
   2706        MOZ_ASSERT(unit == TemporalUnit::Week);
   2707 
   2708        // Step 6.b.iii.2.
   2709        int64_t weeks = dateDuration.weeks + sign;
   2710 
   2711        // Step 6.b.iii.3.
   2712        endDuration = {dateDuration.years, dateDuration.months, weeks};
   2713      }
   2714      MOZ_ASSERT(IsValidDuration(endDuration));
   2715 
   2716      // Steps 6.b.iv.
   2717      ISODate end;
   2718      if (!CalendarDateAdd(cx, calendar, isoDateTime.date, endDuration,
   2719                           TemporalOverflow::Constrain, &end)) {
   2720        return false;
   2721      }
   2722 
   2723      // Steps 6.b.v.
   2724      auto endDateTime = ISODateTime{end, isoDateTime.time};
   2725      MOZ_ASSERT(ISODateTimeWithinLimits(endDateTime));
   2726 
   2727      // Steps 6.b.vi-vii.
   2728      EpochNanoseconds endEpochNs;
   2729      if (!timeZone) {
   2730        endEpochNs = GetUTCEpochNanoseconds(endDateTime);
   2731      } else {
   2732        if (!GetEpochNanosecondsFor(cx, timeZone, endDateTime,
   2733                                    TemporalDisambiguation::Compatible,
   2734                                    &endEpochNs)) {
   2735          return false;
   2736        }
   2737      }
   2738 
   2739      // Step 6.b.viii.
   2740      //
   2741      // NB: |nudge.epochNs| can be outside the valid epoch nanoseconds limits.
   2742      auto beyondEnd = nudge.epochNs - endEpochNs;
   2743 
   2744      // Step 6.b.ix.
   2745      int32_t beyondEndSign = beyondEnd < EpochDuration{}   ? -1
   2746                              : beyondEnd > EpochDuration{} ? 1
   2747                                                            : 0;
   2748 
   2749      // Steps 6.b.x-xi.
   2750      if (beyondEndSign != -sign) {
   2751        dateDuration = endDuration;
   2752        timeDuration = {};
   2753      } else {
   2754        break;
   2755      }
   2756    }
   2757 
   2758    // Step 6.c. (Moved above)
   2759  }
   2760 
   2761  // Step 7.
   2762  *result = {dateDuration, timeDuration};
   2763  return true;
   2764 }
   2765 
   2766 /**
   2767 * RoundRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime,
   2768 * timeZone, calendar, largestUnit, increment, smallestUnit, roundingMode )
   2769 */
   2770 bool js::temporal::RoundRelativeDuration(
   2771    JSContext* cx, const InternalDuration& duration,
   2772    const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs,
   2773    const ISODateTime& isoDateTime, Handle<TimeZoneValue> timeZone,
   2774    Handle<CalendarValue> calendar, TemporalUnit largestUnit,
   2775    Increment increment, TemporalUnit smallestUnit,
   2776    TemporalRoundingMode roundingMode, InternalDuration* result) {
   2777  MOZ_ASSERT(IsValidDuration(duration));
   2778  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs));
   2779  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs));
   2780  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs));
   2781  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs));
   2782  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
   2783  MOZ_ASSERT(largestUnit <= smallestUnit);
   2784 
   2785  // Steps 1-3.
   2786  bool irregularLengthUnit = (smallestUnit < TemporalUnit::Day) ||
   2787                             (timeZone && smallestUnit == TemporalUnit::Day);
   2788 
   2789  // Step 4. (Not applicable in our implementation.)
   2790 
   2791  // Steps 5-7.
   2792  DurationNudge nudge;
   2793  if (irregularLengthUnit) {
   2794    // Step 5.a.
   2795    if (!NudgeToCalendarUnit(cx, duration, originEpochNs, destEpochNs,
   2796                             isoDateTime, timeZone, calendar, increment,
   2797                             smallestUnit, roundingMode, &nudge)) {
   2798      return false;
   2799    }
   2800  } else if (timeZone) {
   2801    // Step 6.a.
   2802    if (!NudgeToZonedTime(cx, duration, isoDateTime, timeZone, calendar,
   2803                          increment, smallestUnit, roundingMode, &nudge)) {
   2804      return false;
   2805    }
   2806  } else {
   2807    // Step 7.a.
   2808    nudge = NudgeToDayOrTime(duration, destEpochNs, largestUnit, increment,
   2809                             smallestUnit, roundingMode);
   2810  }
   2811 
   2812  // Step 8.
   2813  auto nudgedDuration = nudge.duration;
   2814 
   2815  // Step 9.
   2816  if (nudge.didExpandCalendarUnit && smallestUnit != TemporalUnit::Week) {
   2817    // Step 9.a. (Inlined LargerOfTwoTemporalUnits)
   2818    auto startUnit = std::min(smallestUnit, TemporalUnit::Day);
   2819 
   2820    // Step 9.b.
   2821    if (!BubbleRelativeDuration(cx, duration, nudge, isoDateTime, timeZone,
   2822                                calendar, largestUnit, startUnit,
   2823                                &nudgedDuration)) {
   2824      return false;
   2825    }
   2826  }
   2827 
   2828  // Step 10.
   2829  *result = nudgedDuration;
   2830  return true;
   2831 }
   2832 
   2833 /**
   2834 * TotalRelativeDuration ( duration, originEpochNs, destEpochNs, isoDateTime,
   2835 * timeZone, calendar, unit )
   2836 */
   2837 bool js::temporal::TotalRelativeDuration(
   2838    JSContext* cx, const InternalDuration& duration,
   2839    const EpochNanoseconds& originEpochNs, const EpochNanoseconds& destEpochNs,
   2840    const ISODateTime& isoDateTime, JS::Handle<TimeZoneValue> timeZone,
   2841    JS::Handle<CalendarValue> calendar, TemporalUnit unit, double* result) {
   2842  MOZ_ASSERT(IsValidDuration(duration));
   2843  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(originEpochNs));
   2844  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(originEpochNs));
   2845  MOZ_ASSERT_IF(timeZone, IsValidEpochNanoseconds(destEpochNs));
   2846  MOZ_ASSERT_IF(!timeZone, IsValidLocalNanoseconds(destEpochNs));
   2847  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
   2848  MOZ_ASSERT(unit <= TemporalUnit::Day);
   2849  MOZ_ASSERT_IF(unit == TemporalUnit::Day, timeZone);
   2850 
   2851  // Steps 1.a-b.
   2852  DurationNudge nudge;
   2853  if (!NudgeToCalendarUnit(cx, duration, originEpochNs, destEpochNs,
   2854                           isoDateTime, timeZone, calendar, Increment{1}, unit,
   2855                           TemporalRoundingMode::Trunc, &nudge)) {
   2856    return false;
   2857  }
   2858 
   2859  // Step 1.c.
   2860  *result = nudge.total;
   2861  return true;
   2862 }
   2863 
   2864 /**
   2865 * AddDurations ( operation, duration, other )
   2866 */
   2867 static bool AddDurations(JSContext* cx, TemporalAddDuration operation,
   2868                         const CallArgs& args) {
   2869  auto* durationObj = &args.thisv().toObject().as<DurationObject>();
   2870  auto duration = ToDuration(durationObj);
   2871 
   2872  // Step 1.
   2873  Duration other;
   2874  if (!ToTemporalDuration(cx, args.get(0), &other)) {
   2875    return false;
   2876  }
   2877 
   2878  // Step 2.
   2879  if (operation == TemporalAddDuration::Subtract) {
   2880    other = other.negate();
   2881  }
   2882 
   2883  // Step 3.
   2884  auto largestUnit1 = DefaultTemporalLargestUnit(duration);
   2885 
   2886  // Step 4.
   2887  auto largestUnit2 = DefaultTemporalLargestUnit(other);
   2888 
   2889  // Step 5.
   2890  auto largestUnit = std::min(largestUnit1, largestUnit2);
   2891 
   2892  // Step 6.
   2893  if (largestUnit <= TemporalUnit::Week) {
   2894    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2895                              JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
   2896                              "relativeTo");
   2897    return false;
   2898  }
   2899 
   2900  // Step 7.
   2901  auto d1 = ToInternalDurationRecordWith24HourDays(duration).time;
   2902 
   2903  // Step 8.
   2904  auto d2 = ToInternalDurationRecordWith24HourDays(other).time;
   2905 
   2906  // Step 9. (Inline AddTimeDuration)
   2907  auto timeResult = d1 + d2;
   2908  if (!IsValidTimeDuration(timeResult)) {
   2909    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2910                              JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
   2911    return false;
   2912  }
   2913 
   2914  // Steps 10-11.
   2915  Duration resultDuration;
   2916  if (!TemporalDurationFromInternal(cx, timeResult, largestUnit,
   2917                                    &resultDuration)) {
   2918    return false;
   2919  }
   2920  MOZ_ASSERT(IsValidDuration(resultDuration));
   2921 
   2922  auto* obj = CreateTemporalDuration(cx, resultDuration);
   2923  if (!obj) {
   2924    return false;
   2925  }
   2926 
   2927  args.rval().setObject(*obj);
   2928  return true;
   2929 }
   2930 
   2931 /**
   2932 * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
   2933 * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
   2934 * ] ] ] ] ] ] )
   2935 */
   2936 static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
   2937  CallArgs args = CallArgsFromVp(argc, vp);
   2938 
   2939  // Step 1.
   2940  if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
   2941    return false;
   2942  }
   2943 
   2944  // Step 2.
   2945  double years = 0;
   2946  if (args.hasDefined(0) &&
   2947      !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
   2948    return false;
   2949  }
   2950 
   2951  // Step 3.
   2952  double months = 0;
   2953  if (args.hasDefined(1) &&
   2954      !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
   2955    return false;
   2956  }
   2957 
   2958  // Step 4.
   2959  double weeks = 0;
   2960  if (args.hasDefined(2) &&
   2961      !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
   2962    return false;
   2963  }
   2964 
   2965  // Step 5.
   2966  double days = 0;
   2967  if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
   2968    return false;
   2969  }
   2970 
   2971  // Step 6.
   2972  double hours = 0;
   2973  if (args.hasDefined(4) &&
   2974      !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
   2975    return false;
   2976  }
   2977 
   2978  // Step 7.
   2979  double minutes = 0;
   2980  if (args.hasDefined(5) &&
   2981      !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
   2982    return false;
   2983  }
   2984 
   2985  // Step 8.
   2986  double seconds = 0;
   2987  if (args.hasDefined(6) &&
   2988      !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
   2989    return false;
   2990  }
   2991 
   2992  // Step 9.
   2993  double milliseconds = 0;
   2994  if (args.hasDefined(7) &&
   2995      !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
   2996    return false;
   2997  }
   2998 
   2999  // Step 10.
   3000  double microseconds = 0;
   3001  if (args.hasDefined(8) &&
   3002      !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
   3003    return false;
   3004  }
   3005 
   3006  // Step 11.
   3007  double nanoseconds = 0;
   3008  if (args.hasDefined(9) &&
   3009      !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
   3010    return false;
   3011  }
   3012 
   3013  // Step 12.
   3014  auto* duration = CreateTemporalDuration(
   3015      cx, args,
   3016      {years, months, weeks, days, hours, minutes, seconds, milliseconds,
   3017       microseconds, nanoseconds});
   3018  if (!duration) {
   3019    return false;
   3020  }
   3021 
   3022  args.rval().setObject(*duration);
   3023  return true;
   3024 }
   3025 
   3026 /**
   3027 * Temporal.Duration.from ( item )
   3028 */
   3029 static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
   3030  CallArgs args = CallArgsFromVp(argc, vp);
   3031 
   3032  // Step 1.
   3033  Duration result;
   3034  if (!ToTemporalDuration(cx, args.get(0), &result)) {
   3035    return false;
   3036  }
   3037 
   3038  auto* obj = CreateTemporalDuration(cx, result);
   3039  if (!obj) {
   3040    return false;
   3041  }
   3042 
   3043  args.rval().setObject(*obj);
   3044  return true;
   3045 }
   3046 
   3047 /**
   3048 * Temporal.Duration.compare ( one, two [ , options ] )
   3049 */
   3050 static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
   3051  CallArgs args = CallArgsFromVp(argc, vp);
   3052 
   3053  // Step 1.
   3054  Duration one;
   3055  if (!ToTemporalDuration(cx, args.get(0), &one)) {
   3056    return false;
   3057  }
   3058 
   3059  // Step 2.
   3060  Duration two;
   3061  if (!ToTemporalDuration(cx, args.get(1), &two)) {
   3062    return false;
   3063  }
   3064 
   3065  // Steps 3-4.
   3066  Rooted<PlainDate> plainRelativeTo(cx);
   3067  Rooted<ZonedDateTime> zonedRelativeTo(cx);
   3068  if (args.hasDefined(2)) {
   3069    // Step 3.
   3070    Rooted<JSObject*> options(
   3071        cx, RequireObjectArg(cx, "options", "compare", args[2]));
   3072    if (!options) {
   3073      return false;
   3074    }
   3075 
   3076    // Step 4.
   3077    if (!GetTemporalRelativeToOption(cx, options, &plainRelativeTo,
   3078                                     &zonedRelativeTo)) {
   3079      return false;
   3080    }
   3081    MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
   3082  }
   3083 
   3084  // Step 5.
   3085  if (one == two) {
   3086    args.rval().setInt32(0);
   3087    return true;
   3088  }
   3089 
   3090  // Steps 6-9. (Not applicable in our implementation.)
   3091 
   3092  // Step 10.
   3093  auto duration1 = ToInternalDurationRecord(one);
   3094 
   3095  // Step 11.
   3096  auto duration2 = ToInternalDurationRecord(two);
   3097 
   3098  // Step 12.
   3099  if (zonedRelativeTo &&
   3100      (duration1.date != DateDuration{} || duration2.date != DateDuration{})) {
   3101    // Steps 12.a-b. (Not applicable in our implementation.)
   3102 
   3103    // Step 12.c.
   3104    EpochNanoseconds after1;
   3105    if (!AddZonedDateTime(cx, zonedRelativeTo, duration1, &after1)) {
   3106      return false;
   3107    }
   3108 
   3109    // Step 12.d.
   3110    EpochNanoseconds after2;
   3111    if (!AddZonedDateTime(cx, zonedRelativeTo, duration2, &after2)) {
   3112      return false;
   3113    }
   3114 
   3115    // Steps 12.e-g.
   3116    args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
   3117    return true;
   3118  }
   3119 
   3120  // Steps 13.a-b and 14.a.
   3121  int64_t days1;
   3122  if (!DateDurationDays(cx, duration1.date, plainRelativeTo, &days1)) {
   3123    return false;
   3124  }
   3125 
   3126  // Steps 13.a, 13.c, and 14.b.
   3127  int64_t days2;
   3128  if (!DateDurationDays(cx, duration2.date, plainRelativeTo, &days2)) {
   3129    return false;
   3130  }
   3131 
   3132  // Step 15.
   3133  auto timeDuration1 = duration1.time;
   3134  if (!Add24HourDaysToTimeDuration(cx, duration1.time, days1, &timeDuration1)) {
   3135    return false;
   3136  }
   3137 
   3138  // Step 16.
   3139  auto timeDuration2 = duration2.time;
   3140  if (!Add24HourDaysToTimeDuration(cx, duration2.time, days2, &timeDuration2)) {
   3141    return false;
   3142  }
   3143 
   3144  // Step 17.
   3145  args.rval().setInt32(CompareTimeDuration(timeDuration1, timeDuration2));
   3146  return true;
   3147 }
   3148 
   3149 /**
   3150 * get Temporal.Duration.prototype.years
   3151 */
   3152 static bool Duration_years(JSContext* cx, const CallArgs& args) {
   3153  // Step 3.
   3154  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3155  args.rval().setNumber(duration->years());
   3156  return true;
   3157 }
   3158 
   3159 /**
   3160 * get Temporal.Duration.prototype.years
   3161 */
   3162 static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
   3163  // Steps 1-2.
   3164  CallArgs args = CallArgsFromVp(argc, vp);
   3165  return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
   3166 }
   3167 
   3168 /**
   3169 * get Temporal.Duration.prototype.months
   3170 */
   3171 static bool Duration_months(JSContext* cx, const CallArgs& args) {
   3172  // Step 3.
   3173  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3174  args.rval().setNumber(duration->months());
   3175  return true;
   3176 }
   3177 
   3178 /**
   3179 * get Temporal.Duration.prototype.months
   3180 */
   3181 static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
   3182  // Steps 1-2.
   3183  CallArgs args = CallArgsFromVp(argc, vp);
   3184  return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
   3185 }
   3186 
   3187 /**
   3188 * get Temporal.Duration.prototype.weeks
   3189 */
   3190 static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
   3191  // Step 3.
   3192  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3193  args.rval().setNumber(duration->weeks());
   3194  return true;
   3195 }
   3196 
   3197 /**
   3198 * get Temporal.Duration.prototype.weeks
   3199 */
   3200 static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
   3201  // Steps 1-2.
   3202  CallArgs args = CallArgsFromVp(argc, vp);
   3203  return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
   3204 }
   3205 
   3206 /**
   3207 * get Temporal.Duration.prototype.days
   3208 */
   3209 static bool Duration_days(JSContext* cx, const CallArgs& args) {
   3210  // Step 3.
   3211  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3212  args.rval().setNumber(duration->days());
   3213  return true;
   3214 }
   3215 
   3216 /**
   3217 * get Temporal.Duration.prototype.days
   3218 */
   3219 static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
   3220  // Steps 1-2.
   3221  CallArgs args = CallArgsFromVp(argc, vp);
   3222  return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
   3223 }
   3224 
   3225 /**
   3226 * get Temporal.Duration.prototype.hours
   3227 */
   3228 static bool Duration_hours(JSContext* cx, const CallArgs& args) {
   3229  // Step 3.
   3230  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3231  args.rval().setNumber(duration->hours());
   3232  return true;
   3233 }
   3234 
   3235 /**
   3236 * get Temporal.Duration.prototype.hours
   3237 */
   3238 static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
   3239  // Steps 1-2.
   3240  CallArgs args = CallArgsFromVp(argc, vp);
   3241  return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
   3242 }
   3243 
   3244 /**
   3245 * get Temporal.Duration.prototype.minutes
   3246 */
   3247 static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
   3248  // Step 3.
   3249  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3250  args.rval().setNumber(duration->minutes());
   3251  return true;
   3252 }
   3253 
   3254 /**
   3255 * get Temporal.Duration.prototype.minutes
   3256 */
   3257 static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
   3258  // Steps 1-2.
   3259  CallArgs args = CallArgsFromVp(argc, vp);
   3260  return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
   3261 }
   3262 
   3263 /**
   3264 * get Temporal.Duration.prototype.seconds
   3265 */
   3266 static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
   3267  // Step 3.
   3268  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3269  args.rval().setNumber(duration->seconds());
   3270  return true;
   3271 }
   3272 
   3273 /**
   3274 * get Temporal.Duration.prototype.seconds
   3275 */
   3276 static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
   3277  // Steps 1-2.
   3278  CallArgs args = CallArgsFromVp(argc, vp);
   3279  return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
   3280 }
   3281 
   3282 /**
   3283 * get Temporal.Duration.prototype.milliseconds
   3284 */
   3285 static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
   3286  // Step 3.
   3287  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3288  args.rval().setNumber(duration->milliseconds());
   3289  return true;
   3290 }
   3291 
   3292 /**
   3293 * get Temporal.Duration.prototype.milliseconds
   3294 */
   3295 static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
   3296  // Steps 1-2.
   3297  CallArgs args = CallArgsFromVp(argc, vp);
   3298  return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
   3299 }
   3300 
   3301 /**
   3302 * get Temporal.Duration.prototype.microseconds
   3303 */
   3304 static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
   3305  // Step 3.
   3306  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3307  args.rval().setNumber(duration->microseconds());
   3308  return true;
   3309 }
   3310 
   3311 /**
   3312 * get Temporal.Duration.prototype.microseconds
   3313 */
   3314 static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
   3315  // Steps 1-2.
   3316  CallArgs args = CallArgsFromVp(argc, vp);
   3317  return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
   3318 }
   3319 
   3320 /**
   3321 * get Temporal.Duration.prototype.nanoseconds
   3322 */
   3323 static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
   3324  // Step 3.
   3325  auto* duration = &args.thisv().toObject().as<DurationObject>();
   3326  args.rval().setNumber(duration->nanoseconds());
   3327  return true;
   3328 }
   3329 
   3330 /**
   3331 * get Temporal.Duration.prototype.nanoseconds
   3332 */
   3333 static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
   3334  // Steps 1-2.
   3335  CallArgs args = CallArgsFromVp(argc, vp);
   3336  return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
   3337 }
   3338 
   3339 /**
   3340 * get Temporal.Duration.prototype.sign
   3341 */
   3342 static bool Duration_sign(JSContext* cx, const CallArgs& args) {
   3343  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3344 
   3345  // Step 3.
   3346  args.rval().setInt32(DurationSign(duration));
   3347  return true;
   3348 }
   3349 
   3350 /**
   3351 * get Temporal.Duration.prototype.sign
   3352 */
   3353 static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
   3354  // Steps 1-2.
   3355  CallArgs args = CallArgsFromVp(argc, vp);
   3356  return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
   3357 }
   3358 
   3359 /**
   3360 * get Temporal.Duration.prototype.blank
   3361 */
   3362 static bool Duration_blank(JSContext* cx, const CallArgs& args) {
   3363  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3364 
   3365  // Steps 3-4.
   3366  args.rval().setBoolean(duration == Duration{});
   3367  return true;
   3368 }
   3369 
   3370 /**
   3371 * get Temporal.Duration.prototype.blank
   3372 */
   3373 static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
   3374  // Steps 1-2.
   3375  CallArgs args = CallArgsFromVp(argc, vp);
   3376  return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
   3377 }
   3378 
   3379 /**
   3380 * Temporal.Duration.prototype.with ( temporalDurationLike )
   3381 */
   3382 static bool Duration_with(JSContext* cx, const CallArgs& args) {
   3383  // Absent values default to the corresponding values of |this| object.
   3384  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3385 
   3386  // Steps 3-23.
   3387  Rooted<JSObject*> temporalDurationLike(
   3388      cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
   3389  if (!temporalDurationLike) {
   3390    return false;
   3391  }
   3392  if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
   3393    return false;
   3394  }
   3395 
   3396  // Step 24.
   3397  auto* result = CreateTemporalDuration(cx, duration);
   3398  if (!result) {
   3399    return false;
   3400  }
   3401 
   3402  args.rval().setObject(*result);
   3403  return true;
   3404 }
   3405 
   3406 /**
   3407 * Temporal.Duration.prototype.with ( temporalDurationLike )
   3408 */
   3409 static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
   3410  // Steps 1-2.
   3411  CallArgs args = CallArgsFromVp(argc, vp);
   3412  return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
   3413 }
   3414 
   3415 /**
   3416 * Temporal.Duration.prototype.negated ( )
   3417 */
   3418 static bool Duration_negated(JSContext* cx, const CallArgs& args) {
   3419  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3420 
   3421  // Step 3.
   3422  auto* result = CreateTemporalDuration(cx, duration.negate());
   3423  if (!result) {
   3424    return false;
   3425  }
   3426 
   3427  args.rval().setObject(*result);
   3428  return true;
   3429 }
   3430 
   3431 /**
   3432 * Temporal.Duration.prototype.negated ( )
   3433 */
   3434 static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
   3435  // Steps 1-2.
   3436  CallArgs args = CallArgsFromVp(argc, vp);
   3437  return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
   3438 }
   3439 
   3440 /**
   3441 * Temporal.Duration.prototype.abs ( )
   3442 */
   3443 static bool Duration_abs(JSContext* cx, const CallArgs& args) {
   3444  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3445 
   3446  // Step 3.
   3447  auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
   3448  if (!result) {
   3449    return false;
   3450  }
   3451 
   3452  args.rval().setObject(*result);
   3453  return true;
   3454 }
   3455 
   3456 /**
   3457 * Temporal.Duration.prototype.abs ( )
   3458 */
   3459 static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
   3460  // Steps 1-2.
   3461  CallArgs args = CallArgsFromVp(argc, vp);
   3462  return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
   3463 }
   3464 
   3465 /**
   3466 * Temporal.Duration.prototype.add ( other )
   3467 */
   3468 static bool Duration_add(JSContext* cx, const CallArgs& args) {
   3469  // Step 3.
   3470  return AddDurations(cx, TemporalAddDuration::Add, args);
   3471 }
   3472 
   3473 /**
   3474 * Temporal.Duration.prototype.add ( other )
   3475 */
   3476 static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
   3477  // Steps 1-2.
   3478  CallArgs args = CallArgsFromVp(argc, vp);
   3479  return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
   3480 }
   3481 
   3482 /**
   3483 * Temporal.Duration.prototype.subtract ( other )
   3484 */
   3485 static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
   3486  // Step 3.
   3487  return AddDurations(cx, TemporalAddDuration::Subtract, args);
   3488 }
   3489 
   3490 /**
   3491 * Temporal.Duration.prototype.subtract ( other )
   3492 */
   3493 static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
   3494  // Steps 1-2.
   3495  CallArgs args = CallArgsFromVp(argc, vp);
   3496  return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
   3497 }
   3498 
   3499 /**
   3500 * Temporal.Duration.prototype.round ( roundTo )
   3501 */
   3502 static bool Duration_round(JSContext* cx, const CallArgs& args) {
   3503  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3504 
   3505  // Step 18. (Reordered)
   3506  auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
   3507 
   3508  // Steps 3-26.
   3509  auto smallestUnit = TemporalUnit::Unset;
   3510  auto largestUnit = TemporalUnit::Unset;
   3511  auto roundingMode = TemporalRoundingMode::HalfExpand;
   3512  auto roundingIncrement = Increment{1};
   3513  Rooted<PlainDate> plainRelativeTo(cx);
   3514  Rooted<ZonedDateTime> zonedRelativeTo(cx);
   3515  if (args.get(0).isString()) {
   3516    // Step 4. (Not applicable in our implementation.)
   3517 
   3518    // Steps 6-14. (Not applicable)
   3519 
   3520    // Step 15.
   3521    Rooted<JSString*> paramString(cx, args[0].toString());
   3522    if (!GetTemporalUnitValuedOption(
   3523            cx, paramString, TemporalUnitKey::SmallestUnit, &smallestUnit)) {
   3524      return false;
   3525    }
   3526 
   3527    // Step 16.
   3528    if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   3529                                   smallestUnit, TemporalUnitGroup::DateTime)) {
   3530      return false;
   3531    }
   3532 
   3533    // Step 17. (Not applicable)
   3534 
   3535    // Step 18. (Moved above)
   3536 
   3537    // Step 19.
   3538    auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
   3539 
   3540    // Step 20. (Not applicable)
   3541 
   3542    // Step 20.a. (Not applicable)
   3543 
   3544    // Step 20.b.
   3545    largestUnit = defaultLargestUnit;
   3546 
   3547    // Steps 21-26. (Not applicable)
   3548  } else {
   3549    // Steps 3 and 5.
   3550    Rooted<JSObject*> options(
   3551        cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
   3552    if (!options) {
   3553      return false;
   3554    }
   3555 
   3556    // Step 6.
   3557    bool smallestUnitPresent = true;
   3558 
   3559    // Step 7.
   3560    bool largestUnitPresent = true;
   3561 
   3562    // Steps 8-9.
   3563    if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::LargestUnit,
   3564                                     &largestUnit)) {
   3565      return false;
   3566    }
   3567 
   3568    // Steps 10-12.
   3569    if (!GetTemporalRelativeToOption(cx, options, &plainRelativeTo,
   3570                                     &zonedRelativeTo)) {
   3571      return false;
   3572    }
   3573    MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
   3574 
   3575    // Step 13.
   3576    if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) {
   3577      return false;
   3578    }
   3579 
   3580    // Step 14.
   3581    if (!GetRoundingModeOption(cx, options, &roundingMode)) {
   3582      return false;
   3583    }
   3584 
   3585    // Step 15.
   3586    if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
   3587                                     &smallestUnit)) {
   3588      return false;
   3589    }
   3590 
   3591    // Step 16.
   3592    if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   3593                                   smallestUnit, TemporalUnitGroup::DateTime)) {
   3594      return false;
   3595    }
   3596 
   3597    // Step 17.
   3598    if (smallestUnit == TemporalUnit::Unset) {
   3599      // Step 17.a.
   3600      smallestUnitPresent = false;
   3601 
   3602      // Step 17.b.
   3603      smallestUnit = TemporalUnit::Nanosecond;
   3604    }
   3605 
   3606    // Step 18. (Moved above)
   3607 
   3608    // Step 19.
   3609    auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
   3610 
   3611    // Steps 20-21.
   3612    if (largestUnit == TemporalUnit::Unset) {
   3613      // Step 20.a.
   3614      largestUnitPresent = false;
   3615 
   3616      // Step 20.b.
   3617      largestUnit = defaultLargestUnit;
   3618    } else if (largestUnit == TemporalUnit::Auto) {
   3619      // Step 21.a
   3620      largestUnit = defaultLargestUnit;
   3621    }
   3622 
   3623    // Step 22.
   3624    if (!smallestUnitPresent && !largestUnitPresent) {
   3625      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3626                                JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
   3627      return false;
   3628    }
   3629 
   3630    // Step 23.
   3631    if (largestUnit > smallestUnit) {
   3632      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3633                                JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
   3634      return false;
   3635    }
   3636 
   3637    // Steps 24-25.
   3638    if (smallestUnit > TemporalUnit::Day) {
   3639      // Step 24.
   3640      auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
   3641 
   3642      // Step 25.
   3643      if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
   3644                                             false)) {
   3645        return false;
   3646      }
   3647    }
   3648 
   3649    // Step 26.
   3650    if (roundingIncrement > Increment{1} && largestUnit != smallestUnit &&
   3651        smallestUnit <= TemporalUnit::Day) {
   3652      Int32ToCStringBuf cbuf;
   3653      const char* numStr =
   3654          Int32ToCString(&cbuf, int32_t(roundingIncrement.value()));
   3655 
   3656      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3657                                JSMSG_INVALID_OPTION_VALUE, "roundingIncrement",
   3658                                numStr);
   3659      return false;
   3660    }
   3661  }
   3662 
   3663  // Step 27.
   3664  if (zonedRelativeTo) {
   3665    // Step 27.a.
   3666    auto internalDuration = ToInternalDurationRecord(duration);
   3667 
   3668    // Steps 27.b-d. (Not applicable in our implementation.)
   3669 
   3670    // Step 27.e.
   3671    EpochNanoseconds targetEpochNs;
   3672    if (!AddZonedDateTime(cx, zonedRelativeTo, internalDuration,
   3673                          &targetEpochNs)) {
   3674      return false;
   3675    }
   3676 
   3677    // Step 27.f.
   3678    if (!DifferenceZonedDateTimeWithRounding(cx, zonedRelativeTo, targetEpochNs,
   3679                                             {
   3680                                                 smallestUnit,
   3681                                                 largestUnit,
   3682                                                 roundingMode,
   3683                                                 roundingIncrement,
   3684                                             },
   3685                                             &internalDuration)) {
   3686      return false;
   3687    }
   3688 
   3689    // Step 27.g.
   3690    largestUnit = std::max(largestUnit, TemporalUnit::Hour);
   3691 
   3692    // Step 27.h
   3693    Duration result;
   3694    if (!TemporalDurationFromInternal(cx, internalDuration, largestUnit,
   3695                                      &result)) {
   3696      return false;
   3697    }
   3698 
   3699    auto* obj = CreateTemporalDuration(cx, result);
   3700    if (!obj) {
   3701      return false;
   3702    }
   3703 
   3704    args.rval().setObject(*obj);
   3705    return true;
   3706  }
   3707 
   3708  // Step 28.
   3709  if (plainRelativeTo) {
   3710    // Step 28.a.
   3711    auto internalDuration = ToInternalDurationRecordWith24HourDays(duration);
   3712 
   3713    // Step 28.b.
   3714    auto targetTime = AddTime(Time{}, internalDuration.time);
   3715 
   3716    // Step 28.c.
   3717    auto calendar = plainRelativeTo.calendar();
   3718 
   3719    // Step 28.d.
   3720    auto dateDuration = DateDuration{
   3721        internalDuration.date.years,
   3722        internalDuration.date.months,
   3723        internalDuration.date.weeks,
   3724        targetTime.days,
   3725    };
   3726    MOZ_ASSERT(IsValidDuration(dateDuration));
   3727 
   3728    // Step 28.e.
   3729    ISODate targetDate;
   3730    if (!CalendarDateAdd(cx, calendar, plainRelativeTo, dateDuration,
   3731                         TemporalOverflow::Constrain, &targetDate)) {
   3732      return false;
   3733    }
   3734 
   3735    // Step 28.f.
   3736    auto isoDateTime = ISODateTime{plainRelativeTo, {}};
   3737 
   3738    // Step 28.g.
   3739    auto targetDateTime = ISODateTime{targetDate, targetTime.time};
   3740 
   3741    // Step 28.h.
   3742    if (!DifferencePlainDateTimeWithRounding(cx, isoDateTime, targetDateTime,
   3743                                             calendar,
   3744                                             {
   3745                                                 smallestUnit,
   3746                                                 largestUnit,
   3747                                                 roundingMode,
   3748                                                 roundingIncrement,
   3749                                             },
   3750                                             &internalDuration)) {
   3751      return false;
   3752    }
   3753 
   3754    // Step 28.i
   3755    Duration result;
   3756    if (!TemporalDurationFromInternal(cx, internalDuration, largestUnit,
   3757                                      &result)) {
   3758      return false;
   3759    }
   3760 
   3761    auto* obj = CreateTemporalDuration(cx, result);
   3762    if (!obj) {
   3763      return false;
   3764    }
   3765 
   3766    args.rval().setObject(*obj);
   3767    return true;
   3768  }
   3769 
   3770  // Step 29.
   3771  if (existingLargestUnit < TemporalUnit::Day ||
   3772      largestUnit < TemporalUnit::Day) {
   3773    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3774                              JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
   3775                              "relativeTo");
   3776    return false;
   3777  }
   3778 
   3779  // Step 30.
   3780  MOZ_ASSERT(smallestUnit >= TemporalUnit::Day);
   3781 
   3782  // Step 31.
   3783  auto internalDuration = ToInternalDurationRecordWith24HourDays(duration);
   3784  MOZ_ASSERT(internalDuration.date == DateDuration{});
   3785 
   3786  // Steps 32-33.
   3787  if (smallestUnit == TemporalUnit::Day) {
   3788    // Steps 32.a-b.
   3789    constexpr auto nsPerDay = ToNanoseconds(TemporalUnit::Day);
   3790    auto rounded =
   3791        RoundNumberToIncrement(internalDuration.time.toNanoseconds(), nsPerDay,
   3792                               roundingIncrement, roundingMode);
   3793    MOZ_ASSERT(Int128{INT64_MIN} <= rounded && rounded <= Int128{INT64_MAX},
   3794               "rounded days fits in int64");
   3795    auto days = static_cast<int64_t>(rounded);
   3796 
   3797    // Step 32.c. (Inlined CreateDateDurationRecord)
   3798    if (std::abs(days) > TimeDuration::max().toDays()) {
   3799      JS_ReportErrorNumberASCII(
   3800          cx, GetErrorMessage, nullptr,
   3801          JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
   3802      return false;
   3803    }
   3804    auto dateDuration = DateDuration{0, 0, 0, days};
   3805    MOZ_ASSERT(IsValidDuration(dateDuration));
   3806 
   3807    // Step 32.d.
   3808    internalDuration = {dateDuration, {}};
   3809  } else {
   3810    // Step 33.a.
   3811    TimeDuration timeDuration;
   3812    if (!RoundTimeDuration(cx, internalDuration.time, roundingIncrement,
   3813                           smallestUnit, roundingMode, &timeDuration)) {
   3814      return false;
   3815    }
   3816 
   3817    // Step 33.b.
   3818    internalDuration = {{}, timeDuration};
   3819  }
   3820 
   3821  // Step 34.
   3822  Duration result;
   3823  if (!TemporalDurationFromInternal(cx, internalDuration, largestUnit,
   3824                                    &result)) {
   3825    return false;
   3826  }
   3827 
   3828  auto* obj = CreateTemporalDuration(cx, result);
   3829  if (!obj) {
   3830    return false;
   3831  }
   3832 
   3833  args.rval().setObject(*obj);
   3834  return true;
   3835 }
   3836 
   3837 /**
   3838 * Temporal.Duration.prototype.round ( options )
   3839 */
   3840 static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
   3841  // Steps 1-2.
   3842  CallArgs args = CallArgsFromVp(argc, vp);
   3843  return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
   3844 }
   3845 
   3846 /**
   3847 * Temporal.Duration.prototype.total ( totalOf )
   3848 */
   3849 static bool Duration_total(JSContext* cx, const CallArgs& args) {
   3850  auto* durationObj = &args.thisv().toObject().as<DurationObject>();
   3851  auto duration = ToDuration(durationObj);
   3852 
   3853  // Steps 3-10.
   3854  Rooted<PlainDate> plainRelativeTo(cx);
   3855  Rooted<ZonedDateTime> zonedRelativeTo(cx);
   3856  auto unit = TemporalUnit::Unset;
   3857  if (args.get(0).isString()) {
   3858    // Step 4. (Not applicable in our implementation.)
   3859 
   3860    // Steps 6-9. (Implicit)
   3861    MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
   3862 
   3863    // Step 10.
   3864    Rooted<JSString*> paramString(cx, args[0].toString());
   3865    if (!GetTemporalUnitValuedOption(cx, paramString, TemporalUnitKey::Unit,
   3866                                     &unit)) {
   3867      return false;
   3868    }
   3869  } else {
   3870    // Steps 3 and 5.
   3871    Rooted<JSObject*> totalOf(
   3872        cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
   3873    if (!totalOf) {
   3874      return false;
   3875    }
   3876 
   3877    // Steps 6-9.
   3878    if (!GetTemporalRelativeToOption(cx, totalOf, &plainRelativeTo,
   3879                                     &zonedRelativeTo)) {
   3880      return false;
   3881    }
   3882    MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
   3883 
   3884    // Step 10.
   3885    if (!GetTemporalUnitValuedOption(cx, totalOf, TemporalUnitKey::Unit,
   3886                                     &unit)) {
   3887      return false;
   3888    }
   3889 
   3890    if (unit == TemporalUnit::Unset) {
   3891      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3892                                JSMSG_TEMPORAL_MISSING_OPTION, "unit");
   3893      return false;
   3894    }
   3895  }
   3896 
   3897  // Step 11.
   3898  if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::Unit, unit,
   3899                                 TemporalUnitGroup::DateTime)) {
   3900    return false;
   3901  }
   3902 
   3903  // Steps 12-14.
   3904  double total;
   3905  if (zonedRelativeTo) {
   3906    // Step 12.a.
   3907    auto internalDuration = ToInternalDurationRecord(duration);
   3908 
   3909    // Steps 12.b-d. (Not applicable in our implementation.)
   3910 
   3911    // Step 12.e.
   3912    EpochNanoseconds targetEpochNs;
   3913    if (!AddZonedDateTime(cx, zonedRelativeTo, internalDuration,
   3914                          &targetEpochNs)) {
   3915      return false;
   3916    }
   3917 
   3918    // Step 12.f.
   3919    if (!DifferenceZonedDateTimeWithTotal(cx, zonedRelativeTo, targetEpochNs,
   3920                                          unit, &total)) {
   3921      return false;
   3922    }
   3923  } else if (plainRelativeTo) {
   3924    // Step 13.a.
   3925    auto internalDuration = ToInternalDurationRecordWith24HourDays(duration);
   3926 
   3927    // Step 13.b.
   3928    auto targetTime = AddTime(Time{}, internalDuration.time);
   3929 
   3930    // Step 13.c.
   3931    auto calendar = plainRelativeTo.calendar();
   3932 
   3933    // Step 13.d.
   3934    auto dateDuration = DateDuration{
   3935        internalDuration.date.years,
   3936        internalDuration.date.months,
   3937        internalDuration.date.weeks,
   3938        targetTime.days,
   3939    };
   3940    MOZ_ASSERT(IsValidDuration(dateDuration));
   3941 
   3942    // Step 13.e.
   3943    ISODate targetDate;
   3944    if (!CalendarDateAdd(cx, calendar, plainRelativeTo, dateDuration,
   3945                         TemporalOverflow::Constrain, &targetDate)) {
   3946      return false;
   3947    }
   3948 
   3949    // Step 13.f.
   3950    auto isoDateTime = ISODateTime{plainRelativeTo, {}};
   3951 
   3952    // Step 13.g.
   3953    auto targetDateTime = ISODateTime{targetDate, targetTime.time};
   3954 
   3955    // Step 13.h.
   3956    if (!DifferencePlainDateTimeWithTotal(cx, isoDateTime, targetDateTime,
   3957                                          calendar, unit, &total)) {
   3958      return false;
   3959    }
   3960  } else {
   3961    // Steps 14.a-b.
   3962    if (duration.years != 0 || duration.months != 0 || duration.weeks != 0 ||
   3963        unit < TemporalUnit::Day) {
   3964      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   3965                                JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
   3966                                "relativeTo");
   3967      return false;
   3968    }
   3969 
   3970    // Step 14.c.
   3971    auto internalDuration = ToInternalDurationRecordWith24HourDays(duration);
   3972 
   3973    // Step 14.d.
   3974    total = TotalTimeDuration(internalDuration.time, unit);
   3975  }
   3976 
   3977  // Step 15.
   3978  args.rval().setNumber(total);
   3979  return true;
   3980 }
   3981 
   3982 /**
   3983 * Temporal.Duration.prototype.total ( totalOf )
   3984 */
   3985 static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
   3986  // Steps 1-2.
   3987  CallArgs args = CallArgsFromVp(argc, vp);
   3988  return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
   3989 }
   3990 
   3991 /**
   3992 * Temporal.Duration.prototype.toString ( [ options ] )
   3993 */
   3994 static bool Duration_toString(JSContext* cx, const CallArgs& args) {
   3995  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   3996 
   3997  SecondsStringPrecision precision = {Precision::Auto(),
   3998                                      TemporalUnit::Nanosecond, Increment{1}};
   3999  auto roundingMode = TemporalRoundingMode::Trunc;
   4000  if (args.hasDefined(0)) {
   4001    // Step 3.
   4002    Rooted<JSObject*> options(
   4003        cx, RequireObjectArg(cx, "options", "toString", args[0]));
   4004    if (!options) {
   4005      return false;
   4006    }
   4007 
   4008    // Steps 4-5.
   4009    auto digits = Precision::Auto();
   4010    if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) {
   4011      return false;
   4012    }
   4013 
   4014    // Step 6.
   4015    if (!GetRoundingModeOption(cx, options, &roundingMode)) {
   4016      return false;
   4017    }
   4018 
   4019    // Step 7.
   4020    auto smallestUnit = TemporalUnit::Unset;
   4021    if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
   4022                                     &smallestUnit)) {
   4023      return false;
   4024    }
   4025 
   4026    // Step 8.
   4027    if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   4028                                   smallestUnit, TemporalUnitGroup::Time)) {
   4029      return false;
   4030    }
   4031 
   4032    // Step 9.
   4033    if (smallestUnit == TemporalUnit::Hour ||
   4034        smallestUnit == TemporalUnit::Minute) {
   4035      const char* smallestUnitStr =
   4036          smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
   4037      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   4038                                JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
   4039                                smallestUnitStr, "smallestUnit");
   4040      return false;
   4041    }
   4042 
   4043    // Step 10.
   4044    precision = ToSecondsStringPrecision(smallestUnit, digits);
   4045  }
   4046  MOZ_ASSERT(precision.unit >= TemporalUnit::Minute);
   4047 
   4048  // Steps 11-17.
   4049  auto roundedDuration = duration;
   4050  if (precision.unit != TemporalUnit::Nanosecond ||
   4051      precision.increment != Increment{1}) {
   4052    // Step 12.
   4053    auto largestUnit = DefaultTemporalLargestUnit(duration);
   4054 
   4055    // Step 13.
   4056    auto internalDuration = ToInternalDurationRecord(duration);
   4057 
   4058    // Step 14.
   4059    TimeDuration timeDuration;
   4060    if (!RoundTimeDuration(cx, internalDuration.time, precision.increment,
   4061                           precision.unit, roundingMode, &timeDuration)) {
   4062      return false;
   4063    }
   4064 
   4065    // Step 15.
   4066    internalDuration = {internalDuration.date, timeDuration};
   4067 
   4068    // Step 16.
   4069    auto roundedLargestUnit = std::min(largestUnit, TemporalUnit::Second);
   4070 
   4071    // Step 17.
   4072    if (!TemporalDurationFromInternal(cx, internalDuration, roundedLargestUnit,
   4073                                      &roundedDuration)) {
   4074      return false;
   4075    }
   4076    MOZ_ASSERT(IsValidDuration(roundedDuration));
   4077  }
   4078 
   4079  // Steps 11.a. and 18.
   4080  JSString* str =
   4081      TemporalDurationToString(cx, roundedDuration, precision.precision);
   4082  if (!str) {
   4083    return false;
   4084  }
   4085 
   4086  args.rval().setString(str);
   4087  return true;
   4088 }
   4089 
   4090 /**
   4091 * Temporal.Duration.prototype.toString ( [ options ] )
   4092 */
   4093 static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
   4094  // Steps 1-2.
   4095  CallArgs args = CallArgsFromVp(argc, vp);
   4096  return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
   4097 }
   4098 
   4099 /**
   4100 * Temporal.Duration.prototype.toJSON ( )
   4101 */
   4102 static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
   4103  auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
   4104 
   4105  // Step 3.
   4106  JSString* str = TemporalDurationToString(cx, duration, Precision::Auto());
   4107  if (!str) {
   4108    return false;
   4109  }
   4110 
   4111  args.rval().setString(str);
   4112  return true;
   4113 }
   4114 
   4115 /**
   4116 * Temporal.Duration.prototype.toJSON ( )
   4117 */
   4118 static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
   4119  // Steps 1-2.
   4120  CallArgs args = CallArgsFromVp(argc, vp);
   4121  return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
   4122 }
   4123 
   4124 /**
   4125 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
   4126 */
   4127 static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
   4128  // Steps 3-7.
   4129  return TemporalDurationToLocaleString(cx, args);
   4130 }
   4131 
   4132 /**
   4133 * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
   4134 */
   4135 static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
   4136  // Steps 1-2.
   4137  CallArgs args = CallArgsFromVp(argc, vp);
   4138  return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
   4139 }
   4140 
   4141 /**
   4142 * Temporal.Duration.prototype.valueOf ( )
   4143 */
   4144 static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
   4145  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
   4146                            "Duration", "primitive type");
   4147  return false;
   4148 }
   4149 
   4150 const JSClass DurationObject::class_ = {
   4151    "Temporal.Duration",
   4152    JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
   4153        JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
   4154    JS_NULL_CLASS_OPS,
   4155    &DurationObject::classSpec_,
   4156 };
   4157 
   4158 const JSClass& DurationObject::protoClass_ = PlainObject::class_;
   4159 
   4160 static const JSFunctionSpec Duration_methods[] = {
   4161    JS_FN("from", Duration_from, 1, 0),
   4162    JS_FN("compare", Duration_compare, 2, 0),
   4163    JS_FS_END,
   4164 };
   4165 
   4166 static const JSFunctionSpec Duration_prototype_methods[] = {
   4167    JS_FN("with", Duration_with, 1, 0),
   4168    JS_FN("negated", Duration_negated, 0, 0),
   4169    JS_FN("abs", Duration_abs, 0, 0),
   4170    JS_FN("add", Duration_add, 1, 0),
   4171    JS_FN("subtract", Duration_subtract, 1, 0),
   4172    JS_FN("round", Duration_round, 1, 0),
   4173    JS_FN("total", Duration_total, 1, 0),
   4174    JS_FN("toString", Duration_toString, 0, 0),
   4175    JS_FN("toJSON", Duration_toJSON, 0, 0),
   4176    JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
   4177    JS_FN("valueOf", Duration_valueOf, 0, 0),
   4178    JS_FS_END,
   4179 };
   4180 
   4181 static const JSPropertySpec Duration_prototype_properties[] = {
   4182    JS_PSG("years", Duration_years, 0),
   4183    JS_PSG("months", Duration_months, 0),
   4184    JS_PSG("weeks", Duration_weeks, 0),
   4185    JS_PSG("days", Duration_days, 0),
   4186    JS_PSG("hours", Duration_hours, 0),
   4187    JS_PSG("minutes", Duration_minutes, 0),
   4188    JS_PSG("seconds", Duration_seconds, 0),
   4189    JS_PSG("milliseconds", Duration_milliseconds, 0),
   4190    JS_PSG("microseconds", Duration_microseconds, 0),
   4191    JS_PSG("nanoseconds", Duration_nanoseconds, 0),
   4192    JS_PSG("sign", Duration_sign, 0),
   4193    JS_PSG("blank", Duration_blank, 0),
   4194    JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
   4195    JS_PS_END,
   4196 };
   4197 
   4198 const ClassSpec DurationObject::classSpec_ = {
   4199    GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
   4200    GenericCreatePrototype<DurationObject>,
   4201    Duration_methods,
   4202    nullptr,
   4203    Duration_prototype_methods,
   4204    Duration_prototype_properties,
   4205    nullptr,
   4206    ClassSpec::DontDefineConstructor,
   4207 };