tor-browser

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

TimeZone.cpp (36708B)


      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/TimeZone.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/intl/TimeZone.h"
     11 #include "mozilla/Likely.h"
     12 #include "mozilla/Maybe.h"
     13 #include "mozilla/Span.h"
     14 #include "mozilla/UniquePtr.h"
     15 
     16 #include <cmath>
     17 #include <cstdlib>
     18 #include <string_view>
     19 #include <utility>
     20 
     21 #include "jsdate.h"
     22 #include "jstypes.h"
     23 #include "NamespaceImports.h"
     24 
     25 #include "builtin/intl/CommonFunctions.h"
     26 #include "builtin/intl/FormatBuffer.h"
     27 #include "builtin/intl/SharedIntlData.h"
     28 #include "builtin/temporal/Calendar.h"
     29 #include "builtin/temporal/Instant.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/TemporalTypes.h"
     36 #include "builtin/temporal/TemporalUnit.h"
     37 #include "builtin/temporal/ZonedDateTime.h"
     38 #include "gc/Barrier.h"
     39 #include "gc/GCContext.h"
     40 #include "gc/GCEnum.h"
     41 #include "gc/Tracer.h"
     42 #include "js/AllocPolicy.h"
     43 #include "js/Class.h"
     44 #include "js/ErrorReport.h"
     45 #include "js/friend/ErrorMessages.h"
     46 #include "js/Printer.h"
     47 #include "js/RootingAPI.h"
     48 #include "js/StableStringChars.h"
     49 #include "vm/BytecodeUtil.h"
     50 #include "vm/Compartment.h"
     51 #include "vm/DateTime.h"
     52 #include "vm/JSAtomState.h"
     53 #include "vm/JSContext.h"
     54 #include "vm/JSObject.h"
     55 #include "vm/Runtime.h"
     56 #include "vm/StringType.h"
     57 
     58 #include "vm/JSObject-inl.h"
     59 
     60 using namespace js;
     61 using namespace js::temporal;
     62 
     63 void js::temporal::TimeZoneValue::trace(JSTracer* trc) {
     64  TraceNullableRoot(trc, &object_, "TimeZoneValue::object");
     65 }
     66 
     67 /**
     68 * FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] )
     69 */
     70 static JSLinearString* FormatOffsetTimeZoneIdentifier(JSContext* cx,
     71                                                      int32_t offsetMinutes) {
     72  MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute));
     73 
     74  // Step 1.
     75  char sign = offsetMinutes >= 0 ? '+' : '-';
     76 
     77  // Step 2.
     78  int32_t absoluteMinutes = std::abs(offsetMinutes);
     79 
     80  // Step 3.
     81  int32_t hour = absoluteMinutes / 60;
     82 
     83  // Step 4.
     84  int32_t minute = absoluteMinutes % 60;
     85 
     86  // Step 5. (Inlined FormatTimeString).
     87  //
     88  // Format: "sign hour{2} : minute{2}"
     89  char result[] = {
     90      sign, char('0' + (hour / 10)),   char('0' + (hour % 10)),
     91      ':',  char('0' + (minute / 10)), char('0' + (minute % 10)),
     92  };
     93 
     94  // Step 6.
     95  return NewStringCopyN<CanGC>(cx, result, std::size(result));
     96 }
     97 
     98 TimeZoneObject* js::temporal::CreateTimeZoneObject(
     99    JSContext* cx, Handle<JSLinearString*> identifier,
    100    Handle<JSLinearString*> primaryIdentifier) {
    101  auto* object = NewObjectWithGivenProto<TimeZoneObject>(cx, nullptr);
    102  if (!object) {
    103    return nullptr;
    104  }
    105 
    106  object->initFixedSlot(TimeZoneObject::IDENTIFIER_SLOT,
    107                        StringValue(identifier));
    108 
    109  object->initFixedSlot(TimeZoneObject::PRIMARY_IDENTIFIER_SLOT,
    110                        StringValue(primaryIdentifier));
    111 
    112  object->initFixedSlot(TimeZoneObject::OFFSET_MINUTES_SLOT, UndefinedValue());
    113 
    114  return object;
    115 }
    116 
    117 static TimeZoneObject* GetOrCreateTimeZoneObject(
    118    JSContext* cx, Handle<JSLinearString*> identifier,
    119    Handle<JSLinearString*> primaryIdentifier) {
    120  return cx->global()->globalIntlData().getOrCreateTimeZone(cx, identifier,
    121                                                            primaryIdentifier);
    122 }
    123 
    124 static TimeZoneObject* CreateTimeZoneObject(JSContext* cx,
    125                                            int32_t offsetMinutes) {
    126  // TODO: It's unclear if offset time zones should also be cached. Real world
    127  // experience will tell if a cache should be added.
    128 
    129  MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute));
    130 
    131  Rooted<JSLinearString*> identifier(
    132      cx, FormatOffsetTimeZoneIdentifier(cx, offsetMinutes));
    133  if (!identifier) {
    134    return nullptr;
    135  }
    136 
    137  auto* object = NewObjectWithGivenProto<TimeZoneObject>(cx, nullptr);
    138  if (!object) {
    139    return nullptr;
    140  }
    141 
    142  object->initFixedSlot(TimeZoneObject::IDENTIFIER_SLOT,
    143                        StringValue(identifier));
    144 
    145  object->initFixedSlot(TimeZoneObject::PRIMARY_IDENTIFIER_SLOT,
    146                        UndefinedValue());
    147 
    148  object->initFixedSlot(TimeZoneObject::OFFSET_MINUTES_SLOT,
    149                        Int32Value(offsetMinutes));
    150 
    151  return object;
    152 }
    153 
    154 static mozilla::UniquePtr<mozilla::intl::TimeZone> CreateIntlTimeZone(
    155    JSContext* cx, JSLinearString* identifier) {
    156  MOZ_ASSERT(StringIsAscii(identifier));
    157 
    158  Vector<char, mozilla::intl::TimeZone::TimeZoneIdentifierLength> chars(cx);
    159  if (!chars.resize(identifier->length())) {
    160    return nullptr;
    161  }
    162 
    163  js::CopyChars(reinterpret_cast<JS::Latin1Char*>(chars.begin()), *identifier);
    164 
    165  auto result = mozilla::intl::TimeZone::TryCreate(
    166      mozilla::Some(static_cast<mozilla::Span<const char>>(chars)));
    167  if (result.isErr()) {
    168    intl::ReportInternalError(cx, result.unwrapErr());
    169    return nullptr;
    170  }
    171  return result.unwrap();
    172 }
    173 
    174 static mozilla::intl::TimeZone* GetOrCreateIntlTimeZone(
    175    JSContext* cx, Handle<TimeZoneValue> timeZone) {
    176  MOZ_ASSERT(!timeZone.isOffset());
    177 
    178  // Obtain a cached mozilla::intl::TimeZone object.
    179  if (auto* tz = timeZone.getTimeZone()) {
    180    return tz;
    181  }
    182 
    183  auto* tz = CreateIntlTimeZone(cx, timeZone.primaryIdentifier()).release();
    184  if (!tz) {
    185    return nullptr;
    186  }
    187 
    188  auto* obj = timeZone.get().toTimeZoneObject();
    189  obj->setTimeZone(tz);
    190 
    191  intl::AddICUCellMemory(obj, TimeZoneObject::EstimatedMemoryUse);
    192  return tz;
    193 }
    194 
    195 /**
    196 * IsValidTimeZoneName ( timeZone )
    197 * IsAvailableTimeZoneName ( timeZone )
    198 * CanonicalizeTimeZoneName ( timeZone )
    199 */
    200 static bool ValidateAndCanonicalizeTimeZoneName(
    201    JSContext* cx, Handle<JSLinearString*> timeZone,
    202    MutableHandle<JSLinearString*> identifier,
    203    MutableHandle<JSLinearString*> primaryIdentifier) {
    204  Rooted<JSAtom*> availableTimeZone(cx);
    205  Rooted<JSAtom*> primaryTimeZone(cx);
    206  intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
    207  if (!sharedIntlData.validateAndCanonicalizeTimeZone(
    208          cx, timeZone, &availableTimeZone, &primaryTimeZone)) {
    209    return false;
    210  }
    211 
    212  if (!primaryTimeZone) {
    213    if (auto chars = QuoteString(cx, timeZone)) {
    214      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    215                               JSMSG_TEMPORAL_TIMEZONE_INVALID_IDENTIFIER,
    216                               chars.get());
    217    }
    218    return false;
    219  }
    220  MOZ_ASSERT(availableTimeZone);
    221 
    222  // Links to UTC are handled by SharedIntlData.
    223  MOZ_ASSERT(!StringEqualsLiteral(primaryTimeZone, "Etc/UTC"));
    224  MOZ_ASSERT(!StringEqualsLiteral(primaryTimeZone, "Etc/GMT"));
    225 
    226  // We don't need to check against "GMT", because ICU uses the tzdata rearguard
    227  // format, where "GMT" is a link to "Etc/GMT".
    228  MOZ_ASSERT(!StringEqualsLiteral(primaryTimeZone, "GMT"));
    229 
    230  identifier.set(availableTimeZone);
    231  primaryIdentifier.set(primaryTimeZone);
    232  return true;
    233 }
    234 
    235 static bool SystemTimeZoneOffset(JSContext* cx, int32_t* offset) {
    236  auto rawOffset = DateTimeInfo::getRawOffsetMs(cx->realm()->getDateTimeInfo());
    237  if (rawOffset.isErr()) {
    238    intl::ReportInternalError(cx);
    239    return false;
    240  }
    241 
    242  *offset = rawOffset.unwrap();
    243  return true;
    244 }
    245 
    246 /**
    247 * SystemTimeZoneIdentifier ( )
    248 *
    249 * Returns the IANA time zone name for the host environment's current time zone.
    250 */
    251 JSLinearString* js::temporal::ComputeSystemTimeZoneIdentifier(JSContext* cx) {
    252  TimeZoneIdentifierVector timeZoneId;
    253  if (!DateTimeInfo::timeZoneId(cx->realm()->getDateTimeInfo(), timeZoneId)) {
    254    ReportOutOfMemory(cx);
    255    return nullptr;
    256  }
    257 
    258  Rooted<JSAtom*> availableTimeZone(cx);
    259  Rooted<JSAtom*> primaryTimeZone(cx);
    260  intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
    261  if (!sharedIntlData.validateAndCanonicalizeTimeZone(
    262          cx, static_cast<mozilla::Span<const char>>(timeZoneId),
    263          &availableTimeZone, &primaryTimeZone)) {
    264    return nullptr;
    265  }
    266  if (primaryTimeZone) {
    267    return primaryTimeZone;
    268  }
    269 
    270  // Before defaulting to "UTC", try to represent the system time zone using
    271  // the Etc/GMT + offset format. This format only accepts full hour offsets.
    272  int32_t offset;
    273  if (!SystemTimeZoneOffset(cx, &offset)) {
    274    return nullptr;
    275  }
    276 
    277  constexpr int32_t msPerHour = 60 * 60 * 1000;
    278  int32_t offsetHours = std::abs(offset / msPerHour);
    279  int32_t offsetHoursFraction = offset % msPerHour;
    280  if (offsetHoursFraction == 0 && offsetHours < 24) {
    281    // Etc/GMT + offset uses POSIX-style signs, i.e. a positive offset
    282    // means a location west of GMT.
    283    constexpr std::string_view etcGMT = "Etc/GMT";
    284 
    285    char offsetString[etcGMT.length() + 3];
    286 
    287    size_t n = etcGMT.copy(offsetString, etcGMT.length());
    288    offsetString[n++] = offset < 0 ? '+' : '-';
    289    if (offsetHours >= 10) {
    290      offsetString[n++] = char('0' + (offsetHours / 10));
    291    }
    292    offsetString[n++] = char('0' + (offsetHours % 10));
    293 
    294    MOZ_ASSERT(n == etcGMT.length() + 2 || n == etcGMT.length() + 3);
    295 
    296    // Check if the fallback is valid.
    297    if (!sharedIntlData.validateAndCanonicalizeTimeZone(
    298            cx, mozilla::Span<const char>{offsetString, n}, &availableTimeZone,
    299            &primaryTimeZone)) {
    300      return nullptr;
    301    }
    302    if (primaryTimeZone) {
    303      return primaryTimeZone;
    304    }
    305  }
    306 
    307  // Fallback to "UTC" if everything else fails.
    308  return cx->names().UTC;
    309 }
    310 
    311 /**
    312 * SystemTimeZoneIdentifier ( )
    313 *
    314 * Returns the IANA time zone name for the host environment's current time zone.
    315 */
    316 JSLinearString* js::temporal::SystemTimeZoneIdentifier(JSContext* cx) {
    317  return cx->global()->globalIntlData().defaultTimeZone(cx);
    318 }
    319 
    320 /**
    321 * SystemTimeZoneIdentifier ( )
    322 */
    323 bool js::temporal::SystemTimeZone(JSContext* cx,
    324                                  MutableHandle<TimeZoneValue> result) {
    325  auto* timeZone =
    326      cx->global()->globalIntlData().getOrCreateDefaultTimeZone(cx);
    327  if (!timeZone) {
    328    return false;
    329  }
    330 
    331  result.set(TimeZoneValue(timeZone));
    332  return true;
    333 }
    334 
    335 /**
    336 * GetNamedTimeZoneEpochNanoseconds ( timeZoneIdentifier, isoDateTime )
    337 */
    338 static bool GetNamedTimeZoneEpochNanoseconds(JSContext* cx,
    339                                             Handle<TimeZoneValue> timeZone,
    340                                             const ISODateTime& isoDateTime,
    341                                             PossibleEpochNanoseconds* result) {
    342  MOZ_ASSERT(!timeZone.isOffset());
    343  MOZ_ASSERT(IsValidISODateTime(isoDateTime));
    344  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
    345 
    346  // FIXME: spec issue - assert ISODateTimeWithinLimits instead of
    347  // IsValidISODate
    348 
    349  int64_t ms = MakeDate(isoDateTime);
    350 
    351  auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
    352  if (!tz) {
    353    return false;
    354  }
    355 
    356  auto getOffset = [&](mozilla::intl::TimeZone::LocalOption skippedTime,
    357                       mozilla::intl::TimeZone::LocalOption repeatedTime,
    358                       int32_t* offset) {
    359    auto result = tz->GetUTCOffsetMs(ms, skippedTime, repeatedTime);
    360    if (result.isErr()) {
    361      intl::ReportInternalError(cx, result.unwrapErr());
    362      return false;
    363    }
    364 
    365    *offset = result.unwrap();
    366    MOZ_ASSERT(std::abs(*offset) < UnitsPerDay(TemporalUnit::Millisecond));
    367 
    368    return true;
    369  };
    370 
    371  constexpr auto formerTime = mozilla::intl::TimeZone::LocalOption::Former;
    372  constexpr auto latterTime = mozilla::intl::TimeZone::LocalOption::Latter;
    373 
    374  int32_t formerOffset;
    375  if (!getOffset(formerTime, formerTime, &formerOffset)) {
    376    return false;
    377  }
    378 
    379  int32_t latterOffset;
    380  if (!getOffset(latterTime, latterTime, &latterOffset)) {
    381    return false;
    382  }
    383 
    384  if (formerOffset == latterOffset) {
    385    auto epochNs = GetUTCEpochNanoseconds(isoDateTime) -
    386                   EpochDuration::fromMilliseconds(formerOffset);
    387    *result = PossibleEpochNanoseconds{epochNs};
    388    return true;
    389  }
    390 
    391  int32_t disambiguationOffset;
    392  if (!getOffset(formerTime, latterTime, &disambiguationOffset)) {
    393    return false;
    394  }
    395 
    396  // Skipped time.
    397  if (disambiguationOffset == formerOffset) {
    398    *result = {};
    399    return true;
    400  }
    401 
    402  // Repeated time.
    403  auto formerInstant = GetUTCEpochNanoseconds(isoDateTime) -
    404                       EpochDuration::fromMilliseconds(formerOffset);
    405  auto latterInstant = GetUTCEpochNanoseconds(isoDateTime) -
    406                       EpochDuration::fromMilliseconds(latterOffset);
    407 
    408  // Ensure the returned epoch nanoseconds are sorted in numerical order.
    409  if (formerInstant > latterInstant) {
    410    std::swap(formerInstant, latterInstant);
    411  }
    412 
    413  *result = PossibleEpochNanoseconds{formerInstant, latterInstant};
    414  return true;
    415 }
    416 
    417 /**
    418 * GetNamedTimeZoneOffsetNanoseconds ( timeZoneIdentifier, epochNanoseconds )
    419 */
    420 static bool GetNamedTimeZoneOffsetNanoseconds(
    421    JSContext* cx, Handle<TimeZoneValue> timeZone,
    422    const EpochNanoseconds& epochNanoseconds, int64_t* offset) {
    423  MOZ_ASSERT(!timeZone.isOffset());
    424 
    425  // Round down (floor) to the previous full milliseconds.
    426  int64_t millis = epochNanoseconds.floorToMilliseconds();
    427 
    428  auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
    429  if (!tz) {
    430    return false;
    431  }
    432 
    433  auto result = tz->GetOffsetMs(millis);
    434  if (result.isErr()) {
    435    intl::ReportInternalError(cx, result.unwrapErr());
    436    return false;
    437  }
    438 
    439  // FIXME: spec issue - should constrain the range to not exceed 24-hours.
    440  // https://github.com/tc39/ecma262/issues/3101
    441 
    442  int64_t nanoPerMs = 1'000'000;
    443  *offset = result.unwrap() * nanoPerMs;
    444  return true;
    445 }
    446 
    447 /**
    448 * Check if the time zone offset at UTC time |utcMilliseconds1| is the same as
    449 * the time zone offset at UTC time |utcMilliseconds2|.
    450 */
    451 static bool EqualTimeZoneOffset(JSContext* cx,
    452                                mozilla::intl::TimeZone* timeZone,
    453                                int64_t utcMilliseconds1,
    454                                int64_t utcMilliseconds2, bool* result) {
    455  auto offset1 = timeZone->GetOffsetMs(utcMilliseconds1);
    456  if (offset1.isErr()) {
    457    intl::ReportInternalError(cx, offset1.unwrapErr());
    458    return false;
    459  }
    460 
    461  auto offset2 = timeZone->GetOffsetMs(utcMilliseconds2);
    462  if (offset2.isErr()) {
    463    intl::ReportInternalError(cx, offset2.unwrapErr());
    464    return false;
    465  }
    466 
    467  *result = offset1.unwrap() == offset2.unwrap();
    468  return true;
    469 }
    470 
    471 /**
    472 * GetNamedTimeZoneNextTransition ( timeZoneIdentifier, epochNanoseconds )
    473 */
    474 bool js::temporal::GetNamedTimeZoneNextTransition(
    475    JSContext* cx, Handle<TimeZoneValue> timeZone,
    476    const EpochNanoseconds& epochNanoseconds,
    477    mozilla::Maybe<EpochNanoseconds>* result) {
    478  MOZ_ASSERT(!timeZone.isOffset());
    479 
    480  // Round down (floor) to the previous full millisecond.
    481  //
    482  // IANA has experimental support for transitions at sub-second precision, but
    483  // the default configuration doesn't enable it, therefore it's safe to round
    484  // to milliseconds here. In addition to that, ICU also only supports
    485  // transitions at millisecond precision.
    486  int64_t millis = epochNanoseconds.floorToMilliseconds();
    487 
    488  auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
    489  if (!tz) {
    490    return false;
    491  }
    492 
    493  // Skip over transitions which don't change the time zone offset.
    494  //
    495  // ICU4C returns all time zone rule changes as transitions, even if the
    496  // actual time zone offset didn't change. Temporal requires to ignore these
    497  // rule changes and instead only return transitions if the time zone offset
    498  // did change.
    499  while (true) {
    500    auto next = tz->GetNextTransition(millis);
    501    if (next.isErr()) {
    502      intl::ReportInternalError(cx, next.unwrapErr());
    503      return false;
    504    }
    505 
    506    // If there's no next transition, we're done.
    507    auto transition = next.unwrap();
    508    if (!transition) {
    509      *result = mozilla::Nothing();
    510      return true;
    511    }
    512 
    513    // Check if the time offset at the next transition is equal to the current
    514    // time zone offset.
    515    bool equalOffset;
    516    if (!EqualTimeZoneOffset(cx, tz, millis, *transition, &equalOffset)) {
    517      return false;
    518    }
    519 
    520    // If the time zone offset is equal, then search for the next transition
    521    // after |transition|.
    522    if (equalOffset) {
    523      millis = *transition;
    524      continue;
    525    }
    526 
    527    // Otherwise return |transition| as the next transition.
    528    auto transitionInstant = EpochNanoseconds::fromMilliseconds(*transition);
    529    if (!IsValidEpochNanoseconds(transitionInstant)) {
    530      *result = mozilla::Nothing();
    531      return true;
    532    }
    533 
    534    *result = mozilla::Some(transitionInstant);
    535    return true;
    536  }
    537 }
    538 
    539 /**
    540 * GetNamedTimeZonePreviousTransition ( timeZoneIdentifier, epochNanoseconds )
    541 */
    542 bool js::temporal::GetNamedTimeZonePreviousTransition(
    543    JSContext* cx, Handle<TimeZoneValue> timeZone,
    544    const EpochNanoseconds& epochNanoseconds,
    545    mozilla::Maybe<EpochNanoseconds>* result) {
    546  MOZ_ASSERT(!timeZone.isOffset());
    547 
    548  // Round up (ceil) to the next full millisecond.
    549  //
    550  // IANA has experimental support for transitions at sub-second precision, but
    551  // the default configuration doesn't enable it, therefore it's safe to round
    552  // to milliseconds here. In addition to that, ICU also only supports
    553  // transitions at millisecond precision.
    554  int64_t millis = epochNanoseconds.ceilToMilliseconds();
    555 
    556  auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
    557  if (!tz) {
    558    return false;
    559  }
    560 
    561  auto previous = tz->GetPreviousTransition(millis);
    562  if (previous.isErr()) {
    563    intl::ReportInternalError(cx, previous.unwrapErr());
    564    return false;
    565  }
    566 
    567  // If there's no previous transition, we're done.
    568  auto transition = previous.unwrap();
    569  if (!transition) {
    570    *result = mozilla::Nothing();
    571    return true;
    572  }
    573 
    574  // Skip over transitions which don't change the time zone offset.
    575  //
    576  // ICU4C returns all time zone rule changes as transitions, even if the
    577  // actual time zone offset didn't change. Temporal requires to ignore these
    578  // rule changes and instead only return transitions if the time zone offset
    579  // did change.
    580  while (true) {
    581    // Request the transition before |transition|.
    582    auto beforePrevious = tz->GetPreviousTransition(*transition);
    583    if (beforePrevious.isErr()) {
    584      intl::ReportInternalError(cx, beforePrevious.unwrapErr());
    585      return false;
    586    }
    587 
    588    // If there's no before transition, stop searching.
    589    auto beforePreviousTransition = beforePrevious.unwrap();
    590    if (!beforePreviousTransition) {
    591      break;
    592    }
    593 
    594    // Check if the time zone offset at both transition points is equal.
    595    bool equalOffset;
    596    if (!EqualTimeZoneOffset(cx, tz, *transition, *beforePreviousTransition,
    597                             &equalOffset)) {
    598      return false;
    599    }
    600 
    601    // If time zone offset is not equal, then return |transition|.
    602    if (!equalOffset) {
    603      break;
    604    }
    605 
    606    // Otherwise continue searching from |beforePreviousTransition|.
    607    transition = beforePreviousTransition;
    608  }
    609 
    610  auto transitionInstant = EpochNanoseconds::fromMilliseconds(*transition);
    611  if (!IsValidEpochNanoseconds(transitionInstant)) {
    612    *result = mozilla::Nothing();
    613    return true;
    614  }
    615 
    616  *result = mozilla::Some(transitionInstant);
    617  return true;
    618 }
    619 
    620 /**
    621 * GetStartOfDay ( timeZone, isoDate )
    622 */
    623 bool js::temporal::GetStartOfDay(JSContext* cx, Handle<TimeZoneValue> timeZone,
    624                                 const ISODate& isoDate,
    625                                 EpochNanoseconds* result) {
    626  MOZ_ASSERT(IsValidISODate(isoDate));
    627 
    628  // Step 1.
    629  auto isoDateTime = ISODateTime{isoDate, {}};
    630 
    631  // Step 2.
    632  PossibleEpochNanoseconds possibleEpochNs;
    633  if (!GetPossibleEpochNanoseconds(cx, timeZone, isoDateTime,
    634                                   &possibleEpochNs)) {
    635    return false;
    636  }
    637  MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime));
    638 
    639  // Step 3.
    640  if (!possibleEpochNs.empty()) {
    641    *result = possibleEpochNs[0];
    642    return true;
    643  }
    644 
    645  // Step 4.
    646  MOZ_ASSERT(!timeZone.isOffset());
    647 
    648  constexpr auto oneDay = EpochDuration::fromDays(1);
    649 
    650  // Step 5.
    651  auto previousDayEpochNs = GetUTCEpochNanoseconds(isoDateTime) - oneDay;
    652  mozilla::Maybe<EpochNanoseconds> transition{};
    653  if (!GetNamedTimeZoneNextTransition(cx, timeZone, previousDayEpochNs,
    654                                      &transition)) {
    655    return false;
    656  }
    657 
    658  // Step 6.
    659  MOZ_ASSERT(transition, "time zone transition not found");
    660 
    661  // Step 7.
    662  *result = *transition;
    663  return true;
    664 }
    665 
    666 /**
    667 * ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike )
    668 */
    669 bool js::temporal::ToTemporalTimeZone(JSContext* cx,
    670                                      Handle<ParsedTimeZone> string,
    671                                      MutableHandle<TimeZoneValue> result) {
    672  // Steps 1-3. (Not applicable)
    673 
    674  // Steps 4-5.
    675  if (!string.name()) {
    676    auto* obj = CreateTimeZoneObject(cx, string.offset());
    677    if (!obj) {
    678      return false;
    679    }
    680 
    681    result.set(TimeZoneValue(obj));
    682    return true;
    683  }
    684 
    685  // Steps 6-8.
    686  Rooted<JSLinearString*> identifier(cx);
    687  Rooted<JSLinearString*> primaryIdentifier(cx);
    688  if (!ValidateAndCanonicalizeTimeZoneName(cx, string.name(), &identifier,
    689                                           &primaryIdentifier)) {
    690    return false;
    691  }
    692 
    693  // Step 9.
    694  auto* obj = GetOrCreateTimeZoneObject(cx, identifier, primaryIdentifier);
    695  if (!obj) {
    696    return false;
    697  }
    698 
    699  result.set(TimeZoneValue(obj));
    700  return true;
    701 }
    702 
    703 /**
    704 * ToTemporalTimeZoneIdentifier ( temporalTimeZoneLike )
    705 */
    706 bool js::temporal::ToTemporalTimeZone(JSContext* cx,
    707                                      Handle<Value> temporalTimeZoneLike,
    708                                      MutableHandle<TimeZoneValue> result) {
    709  // Step 1.
    710  if (temporalTimeZoneLike.isObject()) {
    711    JSObject* obj = &temporalTimeZoneLike.toObject();
    712 
    713    // Step 1.a.
    714    if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
    715      result.set(zonedDateTime->timeZone());
    716      return result.wrap(cx);
    717    }
    718  }
    719 
    720  // Step 2.
    721  if (!temporalTimeZoneLike.isString()) {
    722    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
    723                     temporalTimeZoneLike, nullptr, "not a string");
    724    return false;
    725  }
    726  Rooted<JSString*> identifier(cx, temporalTimeZoneLike.toString());
    727 
    728  // Step 3.
    729  Rooted<ParsedTimeZone> timeZoneName(cx);
    730  if (!ParseTemporalTimeZoneString(cx, identifier, &timeZoneName)) {
    731    return false;
    732  }
    733 
    734  // Steps 4-9.
    735  return ToTemporalTimeZone(cx, timeZoneName, result);
    736 }
    737 
    738 JSLinearString* js::temporal::ToValidCanonicalTimeZoneIdentifier(
    739    JSContext* cx, Handle<JSString*> timeZone) {
    740  Rooted<ParsedTimeZone> parsedTimeZone(cx);
    741  if (!ParseTimeZoneIdentifier(cx, timeZone, &parsedTimeZone)) {
    742    // TODO: Test262 expects the time zone string is part of the error message,
    743    // so we have to overwrite the error message.
    744    //
    745    // https://github.com/tc39/test262/pull/4463
    746    if (!cx->isExceptionPending() || cx->isThrowingOutOfMemory()) {
    747      return nullptr;
    748    }
    749 
    750    // Clear the previous exception to ensure the error stack is recomputed.
    751    cx->clearPendingException();
    752 
    753    if (auto chars = QuoteString(cx, timeZone)) {
    754      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    755                               JSMSG_TEMPORAL_TIMEZONE_INVALID_IDENTIFIER,
    756                               chars.get());
    757    }
    758    return nullptr;
    759  }
    760 
    761  auto timeZoneId = parsedTimeZone.name();
    762  if (timeZoneId) {
    763    Rooted<JSLinearString*> identifier(cx);
    764    Rooted<JSLinearString*> primaryIdentifier(cx);
    765    if (!ValidateAndCanonicalizeTimeZoneName(cx, timeZoneId, &identifier,
    766                                             &primaryIdentifier)) {
    767      return nullptr;
    768    }
    769    return primaryIdentifier;
    770  }
    771 
    772  int32_t offsetMinutes = parsedTimeZone.offset();
    773  MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute));
    774 
    775  return FormatOffsetTimeZoneIdentifier(cx, offsetMinutes);
    776 }
    777 
    778 /**
    779 * GetOffsetNanosecondsFor ( timeZone, epochNs )
    780 */
    781 bool js::temporal::GetOffsetNanosecondsFor(JSContext* cx,
    782                                           Handle<TimeZoneValue> timeZone,
    783                                           const EpochNanoseconds& epochNs,
    784                                           int64_t* offsetNanoseconds) {
    785  // Step 1. (Not applicable)
    786 
    787  // Step 2.
    788  if (timeZone.isOffset()) {
    789    int32_t offset = timeZone.offsetMinutes();
    790    MOZ_ASSERT(std::abs(offset) < UnitsPerDay(TemporalUnit::Minute));
    791 
    792    *offsetNanoseconds = int64_t(offset) * ToNanoseconds(TemporalUnit::Minute);
    793    return true;
    794  }
    795 
    796  // Step 3.
    797  int64_t offset;
    798  if (!GetNamedTimeZoneOffsetNanoseconds(cx, timeZone, epochNs, &offset)) {
    799    return false;
    800  }
    801  MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day));
    802 
    803  *offsetNanoseconds = offset;
    804  return true;
    805 }
    806 
    807 /**
    808 * TimeZoneEquals ( one, two )
    809 */
    810 bool js::temporal::TimeZoneEquals(const TimeZoneValue& one,
    811                                  const TimeZoneValue& two) {
    812  // Steps 1-3. (Not applicable in our implementation.)
    813 
    814  // Step 4.
    815  if (!one.isOffset() && !two.isOffset()) {
    816    return EqualStrings(one.primaryIdentifier(), two.primaryIdentifier());
    817  }
    818 
    819  // Step 5.
    820  if (one.isOffset() && two.isOffset()) {
    821    return one.offsetMinutes() == two.offsetMinutes();
    822  }
    823 
    824  // Step 6.
    825  return false;
    826 }
    827 
    828 /**
    829 * GetISOPartsFromEpoch ( epochNanoseconds )
    830 */
    831 static ISODateTime GetISOPartsFromEpoch(
    832    const EpochNanoseconds& epochNanoseconds, int64_t offsetNanoseconds) {
    833  // Step 1.
    834  MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
    835  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    836 
    837  auto totalNanoseconds =
    838      epochNanoseconds + EpochDuration::fromNanoseconds(offsetNanoseconds);
    839 
    840  // Step 2.
    841  int32_t remainderNs = totalNanoseconds.nanoseconds % 1'000'000;
    842 
    843  // Step 10. (Reordered)
    844  //
    845  // Reordered so the compiler can merge the divisons in steps 2, 3, and 10.
    846  int32_t millisecond = totalNanoseconds.nanoseconds / 1'000'000;
    847 
    848  // Step 3.
    849  int64_t epochMilliseconds = totalNanoseconds.floorToMilliseconds();
    850 
    851  // Steps 4-6.
    852  auto [year, month, day] = ToYearMonthDay(epochMilliseconds);
    853 
    854  // Steps 7-9.
    855  auto [hour, minute, second] = ToHourMinuteSecond(epochMilliseconds);
    856 
    857  // Step 10. (Moved above)
    858 
    859  // Steps 11-12.
    860  int32_t microsecond = remainderNs / 1000;
    861 
    862  // Step 13.
    863  int32_t nanosecond = remainderNs % 1000;
    864 
    865  // Step 14.
    866  auto isoDate = ISODate{year, month + 1, day};
    867  MOZ_ASSERT(IsValidISODate(isoDate));
    868 
    869  // Step 15.
    870  auto time = Time{hour, minute, second, millisecond, microsecond, nanosecond};
    871  MOZ_ASSERT(IsValidTime(time));
    872 
    873  // Step 16.
    874  auto result = ISODateTime{isoDate, time};
    875 
    876  // Always within date-time limits when the epoch nanoseconds are within limit.
    877  MOZ_ASSERT(ISODateTimeWithinLimits(result));
    878 
    879  return result;
    880 }
    881 
    882 /**
    883 * GetISODateTimeFor ( timeZone, epochNs )
    884 */
    885 ISODateTime js::temporal::GetISODateTimeFor(const EpochNanoseconds& epochNs,
    886                                            int64_t offsetNanoseconds) {
    887  MOZ_ASSERT(IsValidEpochNanoseconds(epochNs));
    888  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    889 
    890  // Step 1. (Not applicable)
    891 
    892  // Steps 2-3.
    893  return GetISOPartsFromEpoch(epochNs, offsetNanoseconds);
    894 }
    895 
    896 /**
    897 * GetISODateTimeFor ( timeZone, epochNs )
    898 */
    899 bool js::temporal::GetISODateTimeFor(JSContext* cx,
    900                                     Handle<TimeZoneValue> timeZone,
    901                                     const EpochNanoseconds& epochNs,
    902                                     ISODateTime* result) {
    903  MOZ_ASSERT(IsValidEpochNanoseconds(epochNs));
    904 
    905  // Step 1.
    906  int64_t offsetNanoseconds;
    907  if (!GetOffsetNanosecondsFor(cx, timeZone, epochNs, &offsetNanoseconds)) {
    908    return false;
    909  }
    910  MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
    911 
    912  // Steps 2-3.
    913  *result = GetISODateTimeFor(epochNs, offsetNanoseconds);
    914  return true;
    915 }
    916 
    917 /**
    918 * GetPossibleEpochNanoseconds ( timeZone, isoDateTime )
    919 */
    920 bool js::temporal::GetPossibleEpochNanoseconds(
    921    JSContext* cx, Handle<TimeZoneValue> timeZone,
    922    const ISODateTime& isoDateTime, PossibleEpochNanoseconds* result) {
    923  // TODO: https://github.com/tc39/proposal-temporal/pull/3014
    924  if (!ISODateTimeWithinLimits(isoDateTime)) {
    925    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    926                              JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
    927    return false;
    928  }
    929 
    930  // Step 1. (Not applicable)
    931 
    932  // Step 2.
    933  PossibleEpochNanoseconds possibleEpochNanoseconds;
    934  if (timeZone.isOffset()) {
    935    int32_t offsetMin = timeZone.offsetMinutes();
    936    MOZ_ASSERT(std::abs(offsetMin) < UnitsPerDay(TemporalUnit::Minute));
    937 
    938    // Step 2.a.
    939    auto epochInstant = GetUTCEpochNanoseconds(isoDateTime) -
    940                        EpochDuration::fromMinutes(offsetMin);
    941 
    942    // Step 2.b.
    943    possibleEpochNanoseconds = PossibleEpochNanoseconds{epochInstant};
    944  } else {
    945    // Step 3.
    946    if (!GetNamedTimeZoneEpochNanoseconds(cx, timeZone, isoDateTime,
    947                                          &possibleEpochNanoseconds)) {
    948      return false;
    949    }
    950  }
    951 
    952  MOZ_ASSERT(possibleEpochNanoseconds.length() <= 2);
    953 
    954  // Step 4.
    955  for (const auto& epochInstant : possibleEpochNanoseconds) {
    956    if (!IsValidEpochNanoseconds(epochInstant)) {
    957      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    958                                JSMSG_TEMPORAL_INSTANT_INVALID);
    959      return false;
    960    }
    961  }
    962 
    963  // Step 5.
    964  *result = possibleEpochNanoseconds;
    965  return true;
    966 }
    967 
    968 /**
    969 * AddTime ( time, timeDuration )
    970 */
    971 static auto AddTime(const Time& time, int64_t nanoseconds) {
    972  MOZ_ASSERT(IsValidTime(time));
    973  MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
    974 
    975  // Steps 1-2.
    976  return BalanceTime(time, nanoseconds);
    977 }
    978 
    979 /**
    980 * DisambiguatePossibleEpochNanoseconds ( possibleEpochNs, timeZone,
    981 * isoDateTime, disambiguation )
    982 */
    983 bool js::temporal::DisambiguatePossibleEpochNanoseconds(
    984    JSContext* cx, const PossibleEpochNanoseconds& possibleEpochNs,
    985    Handle<TimeZoneValue> timeZone, const ISODateTime& isoDateTime,
    986    TemporalDisambiguation disambiguation, EpochNanoseconds* result) {
    987  MOZ_ASSERT(IsValidISODateTime(isoDateTime));
    988 
    989  // Steps 1-2.
    990  if (possibleEpochNs.length() == 1) {
    991    *result = possibleEpochNs.front();
    992    return true;
    993  }
    994 
    995  // Steps 3-4.
    996  if (!possibleEpochNs.empty()) {
    997    // Step 3.a.
    998    if (disambiguation == TemporalDisambiguation::Earlier ||
    999        disambiguation == TemporalDisambiguation::Compatible) {
   1000      *result = possibleEpochNs.front();
   1001      return true;
   1002    }
   1003 
   1004    // Step 3.b.
   1005    if (disambiguation == TemporalDisambiguation::Later) {
   1006      *result = possibleEpochNs.back();
   1007      return true;
   1008    }
   1009 
   1010    // Step 3.c.
   1011    MOZ_ASSERT(disambiguation == TemporalDisambiguation::Reject);
   1012 
   1013    // Step 3.d.
   1014    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1015                              JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
   1016    return false;
   1017  }
   1018 
   1019  // Step 5.
   1020  if (disambiguation == TemporalDisambiguation::Reject) {
   1021    JS_ReportErrorNumberASCII(
   1022        cx, GetErrorMessage, nullptr,
   1023        JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS_DATE_SKIPPED);
   1024    return false;
   1025  }
   1026 
   1027  constexpr auto oneDay = EpochDuration::fromDays(1);
   1028 
   1029  auto epochNanoseconds = GetUTCEpochNanoseconds(isoDateTime);
   1030 
   1031  // Step 6 and 8-9.
   1032  auto dayBefore = epochNanoseconds - oneDay;
   1033  MOZ_ASSERT(IsValidEpochNanoseconds(dayBefore));
   1034 
   1035  // Step 7 and 10-11.
   1036  auto dayAfter = epochNanoseconds + oneDay;
   1037  MOZ_ASSERT(IsValidEpochNanoseconds(dayAfter));
   1038 
   1039  // Step 12.
   1040  int64_t offsetBefore;
   1041  if (!GetOffsetNanosecondsFor(cx, timeZone, dayBefore, &offsetBefore)) {
   1042    return false;
   1043  }
   1044  MOZ_ASSERT(std::abs(offsetBefore) < ToNanoseconds(TemporalUnit::Day));
   1045 
   1046  // Step 13.
   1047  int64_t offsetAfter;
   1048  if (!GetOffsetNanosecondsFor(cx, timeZone, dayAfter, &offsetAfter)) {
   1049    return false;
   1050  }
   1051  MOZ_ASSERT(std::abs(offsetAfter) < ToNanoseconds(TemporalUnit::Day));
   1052 
   1053  // Step 14.
   1054  int64_t nanoseconds = offsetAfter - offsetBefore;
   1055 
   1056  // Step 15.
   1057  MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
   1058 
   1059  // Step 16.
   1060  if (disambiguation == TemporalDisambiguation::Earlier) {
   1061    // Steps 16.a-b.
   1062    auto earlierTime = ::AddTime(isoDateTime.time, -nanoseconds);
   1063    MOZ_ASSERT(std::abs(earlierTime.days) <= 1,
   1064               "subtracting nanoseconds is at most one day");
   1065 
   1066    // Step 16.c.
   1067    auto earlierDate = BalanceISODate(isoDateTime.date, earlierTime.days);
   1068 
   1069    // Step 16.d.
   1070    auto earlierDateTime = ISODateTime{earlierDate, earlierTime.time};
   1071 
   1072    // Step 16.e.
   1073    PossibleEpochNanoseconds earlierEpochNs;
   1074    if (!GetPossibleEpochNanoseconds(cx, timeZone, earlierDateTime,
   1075                                     &earlierEpochNs)) {
   1076      return false;
   1077    }
   1078 
   1079    // Step 16.f.
   1080    MOZ_ASSERT(!earlierEpochNs.empty());
   1081 
   1082    // Step 16.g.
   1083    *result = earlierEpochNs.front();
   1084    return true;
   1085  }
   1086 
   1087  // Step 17.
   1088  MOZ_ASSERT(disambiguation == TemporalDisambiguation::Compatible ||
   1089             disambiguation == TemporalDisambiguation::Later);
   1090 
   1091  // Steps 18-19.
   1092  auto laterTime = ::AddTime(isoDateTime.time, nanoseconds);
   1093  MOZ_ASSERT(std::abs(laterTime.days) <= 1,
   1094             "adding nanoseconds is at most one day");
   1095 
   1096  // Step 20.
   1097  auto laterDate = BalanceISODate(isoDateTime.date, laterTime.days);
   1098 
   1099  // Step 21.
   1100  auto laterDateTime = ISODateTime{laterDate, laterTime.time};
   1101 
   1102  // Step 22.
   1103  PossibleEpochNanoseconds laterEpochNs;
   1104  if (!GetPossibleEpochNanoseconds(cx, timeZone, laterDateTime,
   1105                                   &laterEpochNs)) {
   1106    return false;
   1107  }
   1108 
   1109  // Steps 23-24.
   1110  MOZ_ASSERT(!laterEpochNs.empty());
   1111 
   1112  // Step 25.
   1113  *result = laterEpochNs.back();
   1114  return true;
   1115 }
   1116 
   1117 /**
   1118 * GetEpochNanosecondsFor ( timeZone, isoDateTime, disambiguation )
   1119 */
   1120 bool js::temporal::GetEpochNanosecondsFor(JSContext* cx,
   1121                                          Handle<TimeZoneValue> timeZone,
   1122                                          const ISODateTime& isoDateTime,
   1123                                          TemporalDisambiguation disambiguation,
   1124                                          EpochNanoseconds* result) {
   1125  // Step 1.
   1126  PossibleEpochNanoseconds possibleEpochNs;
   1127  if (!GetPossibleEpochNanoseconds(cx, timeZone, isoDateTime,
   1128                                   &possibleEpochNs)) {
   1129    return false;
   1130  }
   1131 
   1132  // Step 2.
   1133  return DisambiguatePossibleEpochNanoseconds(
   1134      cx, possibleEpochNs, timeZone, isoDateTime, disambiguation, result);
   1135 }
   1136 
   1137 bool js::temporal::WrapTimeZoneValueObject(
   1138    JSContext* cx, MutableHandle<TimeZoneObject*> timeZone) {
   1139  // Handle the common case when |timeZone| is from the current compartment.
   1140  if (MOZ_LIKELY(timeZone->compartment() == cx->compartment())) {
   1141    return true;
   1142  }
   1143 
   1144  if (timeZone->isOffset()) {
   1145    auto* obj = CreateTimeZoneObject(cx, timeZone->offsetMinutes());
   1146    if (!obj) {
   1147      return false;
   1148    }
   1149 
   1150    timeZone.set(obj);
   1151    return true;
   1152  }
   1153 
   1154  Rooted<JSString*> identifier(cx, timeZone->identifier());
   1155  if (!cx->compartment()->wrap(cx, &identifier)) {
   1156    return false;
   1157  }
   1158 
   1159  Rooted<JSString*> primaryIdentifier(cx, timeZone->primaryIdentifier());
   1160  if (!cx->compartment()->wrap(cx, &primaryIdentifier)) {
   1161    return false;
   1162  }
   1163 
   1164  Rooted<JSLinearString*> identifierLinear(cx, identifier->ensureLinear(cx));
   1165  if (!identifierLinear) {
   1166    return false;
   1167  }
   1168 
   1169  Rooted<JSLinearString*> primaryIdentifierLinear(
   1170      cx, primaryIdentifier->ensureLinear(cx));
   1171  if (!primaryIdentifierLinear) {
   1172    return false;
   1173  }
   1174 
   1175  auto* obj =
   1176      GetOrCreateTimeZoneObject(cx, identifierLinear, primaryIdentifierLinear);
   1177  if (!obj) {
   1178    return false;
   1179  }
   1180 
   1181  timeZone.set(obj);
   1182  return true;
   1183 }
   1184 
   1185 void js::temporal::TimeZoneObject::finalize(JS::GCContext* gcx, JSObject* obj) {
   1186  MOZ_ASSERT(gcx->onMainThread());
   1187 
   1188  if (auto* timeZone = obj->as<TimeZoneObject>().getTimeZone()) {
   1189    intl::RemoveICUCellMemory(gcx, obj, EstimatedMemoryUse);
   1190    delete timeZone;
   1191  }
   1192 }
   1193 
   1194 const JSClassOps TimeZoneObject::classOps_ = {
   1195    nullptr,                   // addProperty
   1196    nullptr,                   // delProperty
   1197    nullptr,                   // enumerate
   1198    nullptr,                   // newEnumerate
   1199    nullptr,                   // resolve
   1200    nullptr,                   // mayResolve
   1201    TimeZoneObject::finalize,  // finalize
   1202    nullptr,                   // call
   1203    nullptr,                   // construct
   1204    nullptr,                   // trace
   1205 };
   1206 
   1207 const JSClass TimeZoneObject::class_ = {
   1208    "Temporal.TimeZone",
   1209    JSCLASS_HAS_RESERVED_SLOTS(TimeZoneObject::SLOT_COUNT) |
   1210        JSCLASS_FOREGROUND_FINALIZE,
   1211    &TimeZoneObject::classOps_,
   1212 };