tor-browser

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

DateTimeFormat.cpp (85517B)


      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 /* Intl.DateTimeFormat implementation. */
      8 
      9 #include "builtin/intl/DateTimeFormat.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/intl/Calendar.h"
     13 #include "mozilla/intl/DateIntervalFormat.h"
     14 #include "mozilla/intl/DateTimeFormat.h"
     15 #include "mozilla/intl/DateTimePart.h"
     16 #include "mozilla/intl/Locale.h"
     17 #include "mozilla/intl/TimeZone.h"
     18 #include "mozilla/Span.h"
     19 
     20 #include "jsdate.h"
     21 
     22 #include "builtin/Array.h"
     23 #include "builtin/intl/CommonFunctions.h"
     24 #include "builtin/intl/FormatBuffer.h"
     25 #include "builtin/intl/LanguageTag.h"
     26 #include "builtin/intl/LocaleNegotiation.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/PlainMonthDay.h"
     33 #include "builtin/temporal/PlainTime.h"
     34 #include "builtin/temporal/PlainYearMonth.h"
     35 #include "builtin/temporal/Temporal.h"
     36 #include "builtin/temporal/TemporalParser.h"
     37 #include "builtin/temporal/TimeZone.h"
     38 #include "builtin/temporal/ZonedDateTime.h"
     39 #include "gc/GCContext.h"
     40 #include "js/Date.h"
     41 #include "js/experimental/Intl.h"     // JS::AddMozDateTimeFormatConstructor
     42 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     43 #include "js/GCAPI.h"
     44 #include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperties
     45 #include "js/PropertySpec.h"
     46 #include "js/StableStringChars.h"
     47 #include "js/Wrapper.h"
     48 #include "vm/DateTime.h"
     49 #include "vm/GlobalObject.h"
     50 #include "vm/JSContext.h"
     51 #include "vm/PlainObject.h"  // js::PlainObject
     52 #include "vm/Runtime.h"
     53 
     54 #include "vm/GeckoProfiler-inl.h"
     55 #include "vm/JSObject-inl.h"
     56 #include "vm/NativeObject-inl.h"
     57 
     58 using namespace js;
     59 using namespace js::intl;
     60 using namespace js::temporal;
     61 
     62 using JS::AutoStableStringChars;
     63 using JS::ClippedTime;
     64 using JS::TimeClip;
     65 
     66 using js::intl::DateTimeFormatKind;
     67 using js::intl::DateTimeFormatOptions;
     68 using js::intl::FormatBuffer;
     69 using js::intl::INITIAL_CHAR_BUFFER_SIZE;
     70 using js::intl::SharedIntlData;
     71 
     72 const JSClassOps DateTimeFormatObject::classOps_ = {
     73    nullptr,                         // addProperty
     74    nullptr,                         // delProperty
     75    nullptr,                         // enumerate
     76    nullptr,                         // newEnumerate
     77    nullptr,                         // resolve
     78    nullptr,                         // mayResolve
     79    DateTimeFormatObject::finalize,  // finalize
     80    nullptr,                         // call
     81    nullptr,                         // construct
     82    nullptr,                         // trace
     83 };
     84 
     85 const JSClass DateTimeFormatObject::class_ = {
     86    "Intl.DateTimeFormat",
     87    JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) |
     88        JSCLASS_HAS_CACHED_PROTO(JSProto_DateTimeFormat) |
     89        JSCLASS_FOREGROUND_FINALIZE,
     90    &DateTimeFormatObject::classOps_,
     91    &DateTimeFormatObject::classSpec_,
     92 };
     93 
     94 const JSClass& DateTimeFormatObject::protoClass_ = PlainObject::class_;
     95 
     96 static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
     97                                              Value* vp);
     98 
     99 static bool dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
    100  CallArgs args = CallArgsFromVp(argc, vp);
    101  args.rval().setString(cx->names().DateTimeFormat);
    102  return true;
    103 }
    104 
    105 static const JSFunctionSpec dateTimeFormat_static_methods[] = {
    106    JS_FN("supportedLocalesOf", dateTimeFormat_supportedLocalesOf, 1, 0),
    107    JS_FS_END,
    108 };
    109 
    110 static const JSFunctionSpec dateTimeFormat_methods[] = {
    111    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions",
    112                      0, 0),
    113    JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1,
    114                      0),
    115    JS_SELF_HOSTED_FN("formatRange", "Intl_DateTimeFormat_formatRange", 2, 0),
    116    JS_SELF_HOSTED_FN("formatRangeToParts",
    117                      "Intl_DateTimeFormat_formatRangeToParts", 2, 0),
    118    JS_FN("toSource", dateTimeFormat_toSource, 0, 0),
    119    JS_FS_END,
    120 };
    121 
    122 static const JSPropertySpec dateTimeFormat_properties[] = {
    123    JS_SELF_HOSTED_GET("format", "$Intl_DateTimeFormat_format_get", 0),
    124    JS_STRING_SYM_PS(toStringTag, "Intl.DateTimeFormat", JSPROP_READONLY),
    125    JS_PS_END,
    126 };
    127 
    128 static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp);
    129 
    130 const ClassSpec DateTimeFormatObject::classSpec_ = {
    131    GenericCreateConstructor<DateTimeFormat, 0, gc::AllocKind::FUNCTION>,
    132    GenericCreatePrototype<DateTimeFormatObject>,
    133    dateTimeFormat_static_methods,
    134    nullptr,
    135    dateTimeFormat_methods,
    136    dateTimeFormat_properties,
    137    nullptr,
    138    ClassSpec::DontDefineConstructor,
    139 };
    140 
    141 /**
    142 * 12.2.1 Intl.DateTimeFormat([ locales [, options]])
    143 *
    144 * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
    145 */
    146 static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct,
    147                           HandleString required, HandleString defaults,
    148                           DateTimeFormatOptions dtfOptions) {
    149  AutoJSConstructorProfilerEntry pseudoFrame(cx, "Intl.DateTimeFormat");
    150 
    151  // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
    152 
    153  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    154  JSProtoKey protoKey = dtfOptions == DateTimeFormatOptions::Standard
    155                            ? JSProto_DateTimeFormat
    156                            : JSProto_Null;
    157  RootedObject proto(cx);
    158  if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) {
    159    return false;
    160  }
    161 
    162  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
    163  dateTimeFormat = NewObjectWithClassProto<DateTimeFormatObject>(cx, proto);
    164  if (!dateTimeFormat) {
    165    return false;
    166  }
    167 
    168  RootedValue thisValue(
    169      cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
    170  HandleValue locales = args.get(0);
    171  HandleValue options = args.get(1);
    172 
    173  // Step 3.
    174  return intl::InitializeDateTimeFormatObject(
    175      cx, dateTimeFormat, thisValue, locales, options, required, defaults,
    176      UndefinedHandleValue, dtfOptions, args.rval());
    177 }
    178 
    179 static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
    180  CallArgs args = CallArgsFromVp(argc, vp);
    181 
    182  Handle<PropertyName*> required = cx->names().any;
    183  Handle<PropertyName*> defaults = cx->names().date;
    184  return DateTimeFormat(cx, args, args.isConstructing(), required, defaults,
    185                        DateTimeFormatOptions::Standard);
    186 }
    187 
    188 static bool MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
    189  CallArgs args = CallArgsFromVp(argc, vp);
    190 
    191  // Don't allow to call mozIntl.DateTimeFormat as a function. That way we
    192  // don't need to worry how to handle the legacy initialization semantics
    193  // when applied on mozIntl.DateTimeFormat.
    194  if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat")) {
    195    return false;
    196  }
    197 
    198  Handle<PropertyName*> required = cx->names().any;
    199  Handle<PropertyName*> defaults = cx->names().date;
    200  return DateTimeFormat(cx, args, true, required, defaults,
    201                        DateTimeFormatOptions::EnableMozExtensions);
    202 }
    203 
    204 static Handle<PropertyName*> ToRequired(JSContext* cx,
    205                                        DateTimeFormatKind kind) {
    206  switch (kind) {
    207    case DateTimeFormatKind::All:
    208      return cx->names().any;
    209    case DateTimeFormatKind::Date:
    210      return cx->names().date;
    211    case DateTimeFormatKind::Time:
    212      return cx->names().time;
    213  }
    214  MOZ_CRASH("invalid date time format kind");
    215 }
    216 
    217 static Handle<PropertyName*> ToDefaults(JSContext* cx,
    218                                        DateTimeFormatKind kind) {
    219  switch (kind) {
    220    case DateTimeFormatKind::All:
    221      return cx->names().all;
    222    case DateTimeFormatKind::Date:
    223      return cx->names().date;
    224    case DateTimeFormatKind::Time:
    225      return cx->names().time;
    226  }
    227  MOZ_CRASH("invalid date time format kind");
    228 }
    229 
    230 static DateTimeFormatObject* CreateDateTimeFormat(
    231    JSContext* cx, Handle<Value> locales, Handle<Value> options,
    232    Handle<Value> toLocaleStringTimeZone, DateTimeFormatKind kind) {
    233  Rooted<DateTimeFormatObject*> dateTimeFormat(
    234      cx, NewBuiltinClassInstance<DateTimeFormatObject>(cx));
    235  if (!dateTimeFormat) {
    236    return nullptr;
    237  }
    238 
    239  Handle<PropertyName*> required = ToRequired(cx, kind);
    240  Handle<PropertyName*> defaults = ToDefaults(cx, kind);
    241 
    242  Rooted<Value> thisValue(cx, ObjectValue(*dateTimeFormat));
    243  Rooted<Value> ignored(cx);
    244  if (!InitializeDateTimeFormatObject(
    245          cx, dateTimeFormat, thisValue, locales, options, required, defaults,
    246          toLocaleStringTimeZone, DateTimeFormatOptions::Standard, &ignored)) {
    247    return nullptr;
    248  }
    249  MOZ_ASSERT(&ignored.toObject() == dateTimeFormat);
    250 
    251  return dateTimeFormat;
    252 }
    253 
    254 DateTimeFormatObject* js::intl::CreateDateTimeFormat(JSContext* cx,
    255                                                     Handle<Value> locales,
    256                                                     Handle<Value> options,
    257                                                     DateTimeFormatKind kind) {
    258  return CreateDateTimeFormat(cx, locales, options, UndefinedHandleValue, kind);
    259 }
    260 
    261 DateTimeFormatObject* js::intl::GetOrCreateDateTimeFormat(
    262    JSContext* cx, Handle<Value> locales, Handle<Value> options,
    263    DateTimeFormatKind kind) {
    264  // Try to use a cached instance when |locales| is either undefined or a
    265  // string, and |options| is undefined.
    266  if ((locales.isUndefined() || locales.isString()) && options.isUndefined()) {
    267    Rooted<JSLinearString*> locale(cx);
    268    if (locales.isString()) {
    269      locale = locales.toString()->ensureLinear(cx);
    270      if (!locale) {
    271        return nullptr;
    272      }
    273    }
    274    return cx->global()->globalIntlData().getOrCreateDateTimeFormat(cx, kind,
    275                                                                    locale);
    276  }
    277 
    278  // Create a new Intl.DateTimeFormat instance.
    279  return CreateDateTimeFormat(cx, locales, options, UndefinedHandleValue, kind);
    280 }
    281 
    282 void js::DateTimeFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    283  MOZ_ASSERT(gcx->onMainThread());
    284 
    285  auto* dateTimeFormat = &obj->as<DateTimeFormatObject>();
    286  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
    287  mozilla::intl::DateIntervalFormat* dif =
    288      dateTimeFormat->getDateIntervalFormat();
    289 
    290  if (df) {
    291    intl::RemoveICUCellMemory(
    292        gcx, obj, DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
    293 
    294    delete df;
    295  }
    296 
    297  if (dif) {
    298    intl::RemoveICUCellMemory(
    299        gcx, obj, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
    300 
    301    delete dif;
    302  }
    303 }
    304 
    305 bool JS::AddMozDateTimeFormatConstructor(JSContext* cx,
    306                                         JS::Handle<JSObject*> intl) {
    307  RootedObject ctor(
    308      cx, GlobalObject::createConstructor(cx, MozDateTimeFormat,
    309                                          cx->names().DateTimeFormat, 0));
    310  if (!ctor) {
    311    return false;
    312  }
    313 
    314  RootedObject proto(
    315      cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()));
    316  if (!proto) {
    317    return false;
    318  }
    319 
    320  if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
    321    return false;
    322  }
    323 
    324  // 12.3.2
    325  if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) {
    326    return false;
    327  }
    328 
    329  // 12.4.4 and 12.4.5
    330  if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) {
    331    return false;
    332  }
    333 
    334  // 12.4.2 and 12.4.3
    335  if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) {
    336    return false;
    337  }
    338 
    339  RootedValue ctorValue(cx, ObjectValue(*ctor));
    340  return DefineDataProperty(cx, intl, cx->names().DateTimeFormat, ctorValue, 0);
    341 }
    342 
    343 static bool DefaultCalendar(JSContext* cx, const UniqueChars& locale,
    344                            MutableHandleValue rval) {
    345  auto calendar = mozilla::intl::Calendar::TryCreate(locale.get());
    346  if (calendar.isErr()) {
    347    intl::ReportInternalError(cx, calendar.unwrapErr());
    348    return false;
    349  }
    350 
    351  auto type = calendar.unwrap()->GetBcp47Type();
    352  if (type.isErr()) {
    353    intl::ReportInternalError(cx, type.unwrapErr());
    354    return false;
    355  }
    356 
    357  JSString* str = NewStringCopy<CanGC>(cx, type.unwrap());
    358  if (!str) {
    359    return false;
    360  }
    361 
    362  rval.setString(str);
    363  return true;
    364 }
    365 
    366 bool js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) {
    367  CallArgs args = CallArgsFromVp(argc, vp);
    368  MOZ_ASSERT(args.length() == 1);
    369  MOZ_ASSERT(args[0].isString());
    370 
    371  UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
    372  if (!locale) {
    373    return false;
    374  }
    375 
    376  RootedObject calendars(cx, NewDenseEmptyArray(cx));
    377  if (!calendars) {
    378    return false;
    379  }
    380 
    381  // We need the default calendar for the locale as the first result.
    382  RootedValue defaultCalendar(cx);
    383  if (!DefaultCalendar(cx, locale, &defaultCalendar)) {
    384    return false;
    385  }
    386 
    387  if (!NewbornArrayPush(cx, calendars, defaultCalendar)) {
    388    return false;
    389  }
    390 
    391  // Now get the calendars that "would make a difference", i.e., not the
    392  // default.
    393  auto keywords =
    394      mozilla::intl::Calendar::GetBcp47KeywordValuesForLocale(locale.get());
    395  if (keywords.isErr()) {
    396    intl::ReportInternalError(cx, keywords.unwrapErr());
    397    return false;
    398  }
    399 
    400  for (auto keyword : keywords.unwrap()) {
    401    if (keyword.isErr()) {
    402      intl::ReportInternalError(cx);
    403      return false;
    404    }
    405 
    406    JSString* jscalendar = NewStringCopy<CanGC>(cx, keyword.unwrap());
    407    if (!jscalendar) {
    408      return false;
    409    }
    410    if (!NewbornArrayPush(cx, calendars, StringValue(jscalendar))) {
    411      return false;
    412    }
    413  }
    414 
    415  args.rval().setObject(*calendars);
    416  return true;
    417 }
    418 
    419 bool js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp) {
    420  CallArgs args = CallArgsFromVp(argc, vp);
    421  MOZ_ASSERT(args.length() == 1);
    422  MOZ_ASSERT(args[0].isString());
    423 
    424  UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
    425  if (!locale) {
    426    return false;
    427  }
    428 
    429  return DefaultCalendar(cx, locale, args.rval());
    430 }
    431 
    432 enum class HourCycle {
    433  // 12 hour cycle, from 0 to 11.
    434  H11,
    435 
    436  // 12 hour cycle, from 1 to 12.
    437  H12,
    438 
    439  // 24 hour cycle, from 0 to 23.
    440  H23,
    441 
    442  // 24 hour cycle, from 1 to 24.
    443  H24
    444 };
    445 
    446 static UniqueChars DateTimeFormatLocale(
    447    JSContext* cx, HandleObject internals,
    448    mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hourCycle =
    449        mozilla::Nothing()) {
    450  // ICU expects calendar, numberingSystem, and hourCycle as Unicode locale
    451  // extensions on locale.
    452 
    453  JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
    454 
    455  RootedValue value(cx);
    456  if (!GetProperty(cx, internals, internals, cx->names().calendar, &value)) {
    457    return nullptr;
    458  }
    459 
    460  {
    461    JSLinearString* calendar = value.toString()->ensureLinear(cx);
    462    if (!calendar) {
    463      return nullptr;
    464    }
    465 
    466    if (!keywords.emplaceBack("ca", calendar)) {
    467      return nullptr;
    468    }
    469  }
    470 
    471  if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
    472                   &value)) {
    473    return nullptr;
    474  }
    475 
    476  {
    477    JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
    478    if (!numberingSystem) {
    479      return nullptr;
    480    }
    481 
    482    if (!keywords.emplaceBack("nu", numberingSystem)) {
    483      return nullptr;
    484    }
    485  }
    486 
    487  if (hourCycle) {
    488    JSAtom* hourCycleStr;
    489    switch (*hourCycle) {
    490      case mozilla::intl::DateTimeFormat::HourCycle::H11:
    491        hourCycleStr = cx->names().h11;
    492        break;
    493      case mozilla::intl::DateTimeFormat::HourCycle::H12:
    494        hourCycleStr = cx->names().h12;
    495        break;
    496      case mozilla::intl::DateTimeFormat::HourCycle::H23:
    497        hourCycleStr = cx->names().h23;
    498        break;
    499      case mozilla::intl::DateTimeFormat::HourCycle::H24:
    500        hourCycleStr = cx->names().h24;
    501        break;
    502    }
    503 
    504    if (!keywords.emplaceBack("hc", hourCycleStr)) {
    505      return nullptr;
    506    }
    507  }
    508 
    509  return intl::FormatLocale(cx, internals, keywords);
    510 }
    511 
    512 static bool AssignTextComponent(
    513    JSContext* cx, HandleObject internals, Handle<PropertyName*> property,
    514    mozilla::Maybe<mozilla::intl::DateTimeFormat::Text>* text) {
    515  RootedValue value(cx);
    516  if (!GetProperty(cx, internals, internals, property, &value)) {
    517    return false;
    518  }
    519 
    520  if (value.isString()) {
    521    JSLinearString* string = value.toString()->ensureLinear(cx);
    522    if (!string) {
    523      return false;
    524    }
    525    if (StringEqualsLiteral(string, "narrow")) {
    526      *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Narrow);
    527    } else if (StringEqualsLiteral(string, "short")) {
    528      *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Short);
    529    } else {
    530      MOZ_ASSERT(StringEqualsLiteral(string, "long"));
    531      *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Long);
    532    }
    533  } else {
    534    MOZ_ASSERT(value.isUndefined());
    535  }
    536 
    537  return true;
    538 }
    539 
    540 static bool AssignNumericComponent(
    541    JSContext* cx, HandleObject internals, Handle<PropertyName*> property,
    542    mozilla::Maybe<mozilla::intl::DateTimeFormat::Numeric>* numeric) {
    543  RootedValue value(cx);
    544  if (!GetProperty(cx, internals, internals, property, &value)) {
    545    return false;
    546  }
    547 
    548  if (value.isString()) {
    549    JSLinearString* string = value.toString()->ensureLinear(cx);
    550    if (!string) {
    551      return false;
    552    }
    553    if (StringEqualsLiteral(string, "numeric")) {
    554      *numeric = mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::Numeric);
    555    } else {
    556      MOZ_ASSERT(StringEqualsLiteral(string, "2-digit"));
    557      *numeric =
    558          mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::TwoDigit);
    559    }
    560  } else {
    561    MOZ_ASSERT(value.isUndefined());
    562  }
    563 
    564  return true;
    565 }
    566 
    567 static bool AssignMonthComponent(
    568    JSContext* cx, HandleObject internals, Handle<PropertyName*> property,
    569    mozilla::Maybe<mozilla::intl::DateTimeFormat::Month>* month) {
    570  RootedValue value(cx);
    571  if (!GetProperty(cx, internals, internals, property, &value)) {
    572    return false;
    573  }
    574 
    575  if (value.isString()) {
    576    JSLinearString* string = value.toString()->ensureLinear(cx);
    577    if (!string) {
    578      return false;
    579    }
    580    if (StringEqualsLiteral(string, "numeric")) {
    581      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Numeric);
    582    } else if (StringEqualsLiteral(string, "2-digit")) {
    583      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::TwoDigit);
    584    } else if (StringEqualsLiteral(string, "long")) {
    585      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Long);
    586    } else if (StringEqualsLiteral(string, "short")) {
    587      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Short);
    588    } else {
    589      MOZ_ASSERT(StringEqualsLiteral(string, "narrow"));
    590      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Narrow);
    591    }
    592  } else {
    593    MOZ_ASSERT(value.isUndefined());
    594  }
    595 
    596  return true;
    597 }
    598 
    599 static bool AssignTimeZoneNameComponent(
    600    JSContext* cx, HandleObject internals, Handle<PropertyName*> property,
    601    mozilla::Maybe<mozilla::intl::DateTimeFormat::TimeZoneName>* tzName) {
    602  RootedValue value(cx);
    603  if (!GetProperty(cx, internals, internals, property, &value)) {
    604    return false;
    605  }
    606 
    607  if (value.isString()) {
    608    JSLinearString* string = value.toString()->ensureLinear(cx);
    609    if (!string) {
    610      return false;
    611    }
    612    if (StringEqualsLiteral(string, "long")) {
    613      *tzName =
    614          mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Long);
    615    } else if (StringEqualsLiteral(string, "short")) {
    616      *tzName =
    617          mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Short);
    618    } else if (StringEqualsLiteral(string, "shortOffset")) {
    619      *tzName = mozilla::Some(
    620          mozilla::intl::DateTimeFormat::TimeZoneName::ShortOffset);
    621    } else if (StringEqualsLiteral(string, "longOffset")) {
    622      *tzName = mozilla::Some(
    623          mozilla::intl::DateTimeFormat::TimeZoneName::LongOffset);
    624    } else if (StringEqualsLiteral(string, "shortGeneric")) {
    625      *tzName = mozilla::Some(
    626          mozilla::intl::DateTimeFormat::TimeZoneName::ShortGeneric);
    627    } else {
    628      MOZ_ASSERT(StringEqualsLiteral(string, "longGeneric"));
    629      *tzName = mozilla::Some(
    630          mozilla::intl::DateTimeFormat::TimeZoneName::LongGeneric);
    631    }
    632  } else {
    633    MOZ_ASSERT(value.isUndefined());
    634  }
    635 
    636  return true;
    637 }
    638 
    639 static bool AssignHourCycleComponent(
    640    JSContext* cx, HandleObject internals, Handle<PropertyName*> property,
    641    mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle>* hourCycle) {
    642  RootedValue value(cx);
    643  if (!GetProperty(cx, internals, internals, property, &value)) {
    644    return false;
    645  }
    646 
    647  if (value.isString()) {
    648    JSLinearString* string = value.toString()->ensureLinear(cx);
    649    if (!string) {
    650      return false;
    651    }
    652    if (StringEqualsLiteral(string, "h11")) {
    653      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H11);
    654    } else if (StringEqualsLiteral(string, "h12")) {
    655      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H12);
    656    } else if (StringEqualsLiteral(string, "h23")) {
    657      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H23);
    658    } else {
    659      MOZ_ASSERT(StringEqualsLiteral(string, "h24"));
    660      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H24);
    661    }
    662  } else {
    663    MOZ_ASSERT(value.isUndefined());
    664  }
    665 
    666  return true;
    667 }
    668 
    669 static bool AssignHour12Component(JSContext* cx, HandleObject internals,
    670                                  mozilla::Maybe<bool>* hour12) {
    671  RootedValue value(cx);
    672  if (!GetProperty(cx, internals, internals, cx->names().hour12, &value)) {
    673    return false;
    674  }
    675  if (value.isBoolean()) {
    676    *hour12 = mozilla::Some(value.toBoolean());
    677  } else {
    678    MOZ_ASSERT(value.isUndefined());
    679  }
    680 
    681  return true;
    682 }
    683 
    684 static bool AssignDateTimeLength(
    685    JSContext* cx, HandleObject internals, Handle<PropertyName*> property,
    686    mozilla::Maybe<mozilla::intl::DateTimeFormat::Style>* style) {
    687  RootedValue value(cx);
    688  if (!GetProperty(cx, internals, internals, property, &value)) {
    689    return false;
    690  }
    691 
    692  if (value.isString()) {
    693    JSLinearString* string = value.toString()->ensureLinear(cx);
    694    if (!string) {
    695      return false;
    696    }
    697    if (StringEqualsLiteral(string, "full")) {
    698      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Full);
    699    } else if (StringEqualsLiteral(string, "long")) {
    700      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
    701    } else if (StringEqualsLiteral(string, "medium")) {
    702      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Medium);
    703    } else {
    704      MOZ_ASSERT(StringEqualsLiteral(string, "short"));
    705      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
    706    }
    707  } else {
    708    MOZ_ASSERT(value.isUndefined());
    709  }
    710 
    711  return true;
    712 }
    713 
    714 enum class Required { Date, Time, YearMonth, MonthDay, Any };
    715 
    716 enum class Defaults { Date, Time, YearMonth, MonthDay, ZonedDateTime, All };
    717 
    718 enum class Inherit { All, Relevant };
    719 
    720 struct DateTimeFormatArgs {
    721  Required required;
    722  Defaults defaults;
    723  Inherit inherit;
    724 };
    725 
    726 /**
    727 * Get the "required" argument passed to CreateDateTimeFormat.
    728 */
    729 static bool GetRequired(JSContext* cx, Handle<JSObject*> internals,
    730                        Required* result) {
    731  Rooted<Value> value(cx);
    732  if (!GetProperty(cx, internals, internals, cx->names().required, &value)) {
    733    return false;
    734  }
    735  MOZ_ASSERT(value.isString());
    736 
    737  JSLinearString* string = value.toString()->ensureLinear(cx);
    738  if (!string) {
    739    return false;
    740  }
    741 
    742  if (StringEqualsLiteral(string, "date")) {
    743    *result = Required::Date;
    744  } else if (StringEqualsLiteral(string, "time")) {
    745    *result = Required::Time;
    746  } else {
    747    MOZ_ASSERT(StringEqualsLiteral(string, "any"));
    748    *result = Required::Any;
    749  }
    750  return true;
    751 }
    752 
    753 /**
    754 * Get the "defaults" argument passed to CreateDateTimeFormat.
    755 */
    756 static bool GetDefaults(JSContext* cx, Handle<JSObject*> internals,
    757                        Defaults* result) {
    758  Rooted<Value> value(cx);
    759  if (!GetProperty(cx, internals, internals, cx->names().defaults, &value)) {
    760    return false;
    761  }
    762  MOZ_ASSERT(value.isString());
    763 
    764  JSLinearString* string = value.toString()->ensureLinear(cx);
    765  if (!string) {
    766    return false;
    767  }
    768 
    769  if (StringEqualsLiteral(string, "date")) {
    770    *result = Defaults::Date;
    771  } else if (StringEqualsLiteral(string, "time")) {
    772    *result = Defaults::Time;
    773  } else {
    774    MOZ_ASSERT(StringEqualsLiteral(string, "all"));
    775    *result = Defaults::All;
    776  }
    777  return true;
    778 }
    779 
    780 /**
    781 * Compute the (required, defaults, inherit) arguments passed to
    782 * GetDateTimeFormat.
    783 */
    784 static bool GetDateTimeFormatArgs(JSContext* cx, Handle<JSObject*> internals,
    785                                  DateTimeValueKind kind,
    786                                  DateTimeFormatArgs* result) {
    787  switch (kind) {
    788    case DateTimeValueKind::Number: {
    789      Required required;
    790      if (!GetRequired(cx, internals, &required)) {
    791        return false;
    792      }
    793      Defaults defaults;
    794      if (!GetDefaults(cx, internals, &defaults)) {
    795        return false;
    796      }
    797      *result = {required, defaults, Inherit::All};
    798      return true;
    799    }
    800    case DateTimeValueKind::TemporalDate:
    801      *result = {Required::Date, Defaults::Date, Inherit::Relevant};
    802      return true;
    803    case DateTimeValueKind::TemporalTime:
    804      *result = {Required::Time, Defaults::Time, Inherit::Relevant};
    805      return true;
    806    case DateTimeValueKind::TemporalDateTime:
    807      *result = {Required::Any, Defaults::All, Inherit::Relevant};
    808      return true;
    809    case DateTimeValueKind::TemporalYearMonth:
    810      *result = {Required::YearMonth, Defaults::YearMonth, Inherit::Relevant};
    811      return true;
    812    case DateTimeValueKind::TemporalMonthDay:
    813      *result = {Required::MonthDay, Defaults::MonthDay, Inherit::Relevant};
    814      return true;
    815    case DateTimeValueKind::TemporalZonedDateTime:
    816      *result = {Required::Any, Defaults::ZonedDateTime, Inherit::All};
    817      return true;
    818    case DateTimeValueKind::TemporalInstant:
    819      *result = {Required::Any, Defaults::All, Inherit::All};
    820      return true;
    821  }
    822  MOZ_CRASH("invalid date-time value kind");
    823 }
    824 
    825 enum class DateTimeField {
    826  Weekday,
    827  Era,
    828  Year,
    829  Month,
    830  Day,
    831  DayPeriod,
    832  Hour,
    833  Minute,
    834  Second,
    835  FractionalSecondDigits,
    836 };
    837 
    838 /**
    839 * GetDateTimeFormat ( formats, matcher, options, required, defaults, inherit )
    840 *
    841 * https://tc39.es/proposal-temporal/#sec-getdatetimeformat
    842 */
    843 static mozilla::Maybe<mozilla::intl::DateTimeFormat::ComponentsBag>
    844 GetDateTimeFormat(const mozilla::intl::DateTimeFormat::ComponentsBag& options,
    845                  Required required, Defaults defaults, Inherit inherit) {
    846  // Steps 1-5.
    847  mozilla::EnumSet<DateTimeField> requiredOptions;
    848  switch (required) {
    849    case Required::Date:
    850      requiredOptions = {
    851          DateTimeField::Weekday,
    852          DateTimeField::Year,
    853          DateTimeField::Month,
    854          DateTimeField::Day,
    855      };
    856      break;
    857    case Required::Time:
    858      requiredOptions = {
    859          DateTimeField::DayPeriod,
    860          DateTimeField::Hour,
    861          DateTimeField::Minute,
    862          DateTimeField::Second,
    863          DateTimeField::FractionalSecondDigits,
    864      };
    865      break;
    866    case Required::YearMonth:
    867      requiredOptions = {
    868          DateTimeField::Year,
    869          DateTimeField::Month,
    870      };
    871      break;
    872    case Required::MonthDay:
    873      requiredOptions = {
    874          DateTimeField::Month,
    875          DateTimeField::Day,
    876      };
    877      break;
    878    case Required::Any:
    879      requiredOptions = {
    880          DateTimeField::Weekday,
    881          DateTimeField::Year,
    882          DateTimeField::Month,
    883          DateTimeField::Day,
    884          DateTimeField::DayPeriod,
    885          DateTimeField::Hour,
    886          DateTimeField::Minute,
    887          DateTimeField::Second,
    888          DateTimeField::FractionalSecondDigits,
    889      };
    890      break;
    891  }
    892  MOZ_ASSERT(!requiredOptions.contains(DateTimeField::Era),
    893             "standalone era not supported");
    894 
    895  // Steps 6-10.
    896  mozilla::EnumSet<DateTimeField> defaultOptions;
    897  switch (defaults) {
    898    case Defaults::Date:
    899      defaultOptions = {
    900          DateTimeField::Year,
    901          DateTimeField::Month,
    902          DateTimeField::Day,
    903      };
    904      break;
    905    case Defaults::Time:
    906      defaultOptions = {
    907          DateTimeField::Hour,
    908          DateTimeField::Minute,
    909          DateTimeField::Second,
    910      };
    911      break;
    912    case Defaults::YearMonth:
    913      defaultOptions = {
    914          DateTimeField::Year,
    915          DateTimeField::Month,
    916      };
    917      break;
    918    case Defaults::MonthDay:
    919      defaultOptions = {
    920          DateTimeField::Month,
    921          DateTimeField::Day,
    922      };
    923      break;
    924    case Defaults::ZonedDateTime:
    925    case Defaults::All:
    926      defaultOptions = {
    927          DateTimeField::Year, DateTimeField::Month,  DateTimeField::Day,
    928          DateTimeField::Hour, DateTimeField::Minute, DateTimeField::Second,
    929      };
    930      break;
    931  }
    932  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::Weekday));
    933  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::Era));
    934  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::DayPeriod));
    935  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::FractionalSecondDigits));
    936 
    937  // Steps 11-12.
    938  mozilla::intl::DateTimeFormat::ComponentsBag formatOptions;
    939  if (inherit == Inherit::All) {
    940    // Step 11.a.
    941    formatOptions = options;
    942  } else {
    943    // Step 12.a. (Implicit)
    944 
    945    // Step 12.b.
    946    switch (required) {
    947      case Required::Date:
    948      case Required::YearMonth:
    949      case Required::Any:
    950        formatOptions.era = options.era;
    951        break;
    952      case Required::Time:
    953      case Required::MonthDay:
    954        // |era| option not applicable for these types.
    955        break;
    956    }
    957 
    958    // Step 12.c.
    959    switch (required) {
    960      case Required::Time:
    961      case Required::Any:
    962        formatOptions.hourCycle = options.hourCycle;
    963        formatOptions.hour12 = options.hour12;
    964        break;
    965      case Required::Date:
    966      case Required::YearMonth:
    967      case Required::MonthDay:
    968        // |hourCycle| and |hour12| options not applicable for these types.
    969        break;
    970    }
    971  }
    972 
    973  // Steps 13-14.
    974  bool anyPresent = options.weekday || options.year || options.month ||
    975                    options.day || options.dayPeriod || options.hour ||
    976                    options.minute || options.second ||
    977                    options.fractionalSecondDigits;
    978 
    979  // Step 15.
    980  bool needDefaults = true;
    981 
    982  // Step 16. (Loop unrolled)
    983  if (requiredOptions.contains(DateTimeField::Weekday) && options.weekday) {
    984    formatOptions.weekday = options.weekday;
    985    needDefaults = false;
    986  }
    987  if (requiredOptions.contains(DateTimeField::Year) && options.year) {
    988    formatOptions.year = options.year;
    989    needDefaults = false;
    990  }
    991  if (requiredOptions.contains(DateTimeField::Month) && options.month) {
    992    formatOptions.month = options.month;
    993    needDefaults = false;
    994  }
    995  if (requiredOptions.contains(DateTimeField::Day) && options.day) {
    996    formatOptions.day = options.day;
    997    needDefaults = false;
    998  }
    999  if (requiredOptions.contains(DateTimeField::DayPeriod) && options.dayPeriod) {
   1000    formatOptions.dayPeriod = options.dayPeriod;
   1001    needDefaults = false;
   1002  }
   1003  if (requiredOptions.contains(DateTimeField::Hour) && options.hour) {
   1004    formatOptions.hour = options.hour;
   1005    needDefaults = false;
   1006  }
   1007  if (requiredOptions.contains(DateTimeField::Minute) && options.minute) {
   1008    formatOptions.minute = options.minute;
   1009    needDefaults = false;
   1010  }
   1011  if (requiredOptions.contains(DateTimeField::Second) && options.second) {
   1012    formatOptions.second = options.second;
   1013    needDefaults = false;
   1014  }
   1015  if (requiredOptions.contains(DateTimeField::FractionalSecondDigits) &&
   1016      options.fractionalSecondDigits) {
   1017    formatOptions.fractionalSecondDigits = options.fractionalSecondDigits;
   1018    needDefaults = false;
   1019  }
   1020 
   1021  // Step 17.
   1022  if (needDefaults) {
   1023    // Step 17.a.
   1024    if (anyPresent && inherit == Inherit::Relevant) {
   1025      return mozilla::Nothing();
   1026    }
   1027 
   1028    // Step 17.b. (Loop unrolled)
   1029    auto numericOption =
   1030        mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::Numeric);
   1031    if (defaultOptions.contains(DateTimeField::Year)) {
   1032      formatOptions.year = numericOption;
   1033    }
   1034    if (defaultOptions.contains(DateTimeField::Month)) {
   1035      formatOptions.month =
   1036          mozilla::Some(mozilla::intl::DateTimeFormat::Month::Numeric);
   1037    }
   1038    if (defaultOptions.contains(DateTimeField::Day)) {
   1039      formatOptions.day = numericOption;
   1040    }
   1041    if (defaultOptions.contains(DateTimeField::Hour)) {
   1042      formatOptions.hour = numericOption;
   1043    }
   1044    if (defaultOptions.contains(DateTimeField::Minute)) {
   1045      formatOptions.minute = numericOption;
   1046    }
   1047    if (defaultOptions.contains(DateTimeField::Second)) {
   1048      formatOptions.second = numericOption;
   1049    }
   1050 
   1051    // Step 17.c.
   1052    if (defaults == Defaults::ZonedDateTime && !formatOptions.timeZoneName) {
   1053      formatOptions.timeZoneName =
   1054          mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Short);
   1055    }
   1056  }
   1057 
   1058  // Steps 18-20. (Performed in caller).
   1059 
   1060  return mozilla::Some(formatOptions);
   1061 }
   1062 
   1063 /**
   1064 * AdjustDateTimeStyleFormat ( formats, baseFormat, matcher, allowedOptions )
   1065 *
   1066 * https://tc39.es/proposal-temporal/#sec-adjustdatetimestyleformat
   1067 */
   1068 static mozilla::Result<mozilla::intl::DateTimeFormat::ComponentsBag,
   1069                       mozilla::intl::ICUError>
   1070 AdjustDateTimeStyleFormat(mozilla::intl::DateTimeFormat* baseFormat,
   1071                          mozilla::EnumSet<DateTimeField> allowedOptions) {
   1072  // Step 1.
   1073  mozilla::intl::DateTimeFormat::ComponentsBag formatOptions;
   1074 
   1075  // Step 2. (Loop unrolled)
   1076  auto result = baseFormat->ResolveComponents();
   1077  if (result.isErr()) {
   1078    return result.propagateErr();
   1079  }
   1080  auto options = result.unwrap();
   1081 
   1082  if (allowedOptions.contains(DateTimeField::Era) && options.era) {
   1083    formatOptions.era = options.era;
   1084  }
   1085  if (allowedOptions.contains(DateTimeField::Weekday) && options.weekday) {
   1086    formatOptions.weekday = options.weekday;
   1087  }
   1088  if (allowedOptions.contains(DateTimeField::Year) && options.year) {
   1089    formatOptions.year = options.year;
   1090  }
   1091  if (allowedOptions.contains(DateTimeField::Month) && options.month) {
   1092    formatOptions.month = options.month;
   1093  }
   1094  if (allowedOptions.contains(DateTimeField::Day) && options.day) {
   1095    formatOptions.day = options.day;
   1096  }
   1097  if (allowedOptions.contains(DateTimeField::DayPeriod) && options.dayPeriod) {
   1098    formatOptions.dayPeriod = options.dayPeriod;
   1099  }
   1100  if (allowedOptions.contains(DateTimeField::Hour) && options.hour) {
   1101    formatOptions.hour = options.hour;
   1102    formatOptions.hourCycle = options.hourCycle;
   1103  }
   1104  if (allowedOptions.contains(DateTimeField::Minute) && options.minute) {
   1105    formatOptions.minute = options.minute;
   1106  }
   1107  if (allowedOptions.contains(DateTimeField::Second) && options.second) {
   1108    formatOptions.second = options.second;
   1109  }
   1110  if (allowedOptions.contains(DateTimeField::FractionalSecondDigits) &&
   1111      options.fractionalSecondDigits) {
   1112    formatOptions.fractionalSecondDigits = options.fractionalSecondDigits;
   1113  }
   1114 
   1115  // Steps 3-5. (Performed in caller)
   1116 
   1117  return formatOptions;
   1118 }
   1119 
   1120 static const char* DateTimeValueKindToString(DateTimeValueKind kind) {
   1121  switch (kind) {
   1122    case DateTimeValueKind::Number:
   1123      return "number";
   1124    case DateTimeValueKind::TemporalDate:
   1125      return "Temporal.PlainDate";
   1126    case DateTimeValueKind::TemporalTime:
   1127      return "Temporal.PlainTime";
   1128    case DateTimeValueKind::TemporalDateTime:
   1129      return "Temporal.PlainDateTime";
   1130    case DateTimeValueKind::TemporalYearMonth:
   1131      return "Temporal.PlainYearMonth";
   1132    case DateTimeValueKind::TemporalMonthDay:
   1133      return "Temporal.PlainMonthDay";
   1134    case DateTimeValueKind::TemporalZonedDateTime:
   1135      return "Temporal.ZonedDateTime";
   1136    case DateTimeValueKind::TemporalInstant:
   1137      return "Temporal.Instant";
   1138  }
   1139  MOZ_CRASH("invalid date-time value kind");
   1140 }
   1141 
   1142 class TimeZoneOffsetString {
   1143  static constexpr std::u16string_view GMT = u"GMT";
   1144 
   1145  // Time zone offset string format is "±hh:mm".
   1146  static constexpr size_t offsetLength = 6;
   1147 
   1148  // ICU custom time zones are in the format "GMT±hh:mm".
   1149  char16_t timeZone_[GMT.size() + offsetLength] = {};
   1150 
   1151  TimeZoneOffsetString() = default;
   1152 
   1153 public:
   1154  TimeZoneOffsetString(const TimeZoneOffsetString& other) { *this = other; }
   1155 
   1156  TimeZoneOffsetString& operator=(const TimeZoneOffsetString& other) {
   1157    std::copy_n(other.timeZone_, std::size(timeZone_), timeZone_);
   1158    return *this;
   1159  }
   1160 
   1161  operator mozilla::Span<const char16_t>() const {
   1162    return mozilla::Span(timeZone_);
   1163  }
   1164 
   1165  /**
   1166   * |timeZone| is either a canonical IANA time zone identifier or a normalized
   1167   * time zone offset string.
   1168   */
   1169  static mozilla::Maybe<TimeZoneOffsetString> from(
   1170      const JSLinearString* timeZone) {
   1171    MOZ_RELEASE_ASSERT(!timeZone->empty(), "time zone is a non-empty string");
   1172 
   1173    // If the time zone string starts with either "+" or "-", it is a normalized
   1174    // time zone offset string, because (canonical) IANA time zone identifiers
   1175    // can't start with "+" or "-".
   1176    char16_t timeZoneSign = timeZone->latin1OrTwoByteChar(0);
   1177    MOZ_ASSERT(timeZoneSign != 0x2212,
   1178               "Minus sign is normalized to Ascii minus");
   1179    if (timeZoneSign != '+' && timeZoneSign != '-') {
   1180      return mozilla::Nothing();
   1181    }
   1182 
   1183    // Release assert because we don't want CopyChars to write out-of-bounds.
   1184    MOZ_RELEASE_ASSERT(timeZone->length() == offsetLength);
   1185 
   1186    // Self-hosted code has normalized offset strings to the format "±hh:mm".
   1187    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(1)));
   1188    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(2)));
   1189    MOZ_ASSERT(timeZone->latin1OrTwoByteChar(3) == ':');
   1190    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(4)));
   1191    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(5)));
   1192 
   1193    // Self-hosted code has verified the offset is at most ±23:59.
   1194 #ifdef DEBUG
   1195    auto twoDigit = [&](size_t offset) {
   1196      auto c1 = timeZone->latin1OrTwoByteChar(offset);
   1197      auto c2 = timeZone->latin1OrTwoByteChar(offset + 1);
   1198      return mozilla::AsciiAlphanumericToNumber(c1) * 10 +
   1199             mozilla::AsciiAlphanumericToNumber(c2);
   1200    };
   1201 
   1202    int32_t hours = twoDigit(1);
   1203    MOZ_ASSERT(0 <= hours && hours <= 23);
   1204 
   1205    int32_t minutes = twoDigit(4);
   1206    MOZ_ASSERT(0 <= minutes && minutes <= 59);
   1207 #endif
   1208 
   1209    TimeZoneOffsetString result{};
   1210 
   1211    // Copy the string "GMT" followed by the offset string.
   1212    size_t copied = GMT.copy(result.timeZone_, GMT.size());
   1213    CopyChars(result.timeZone_ + copied, *timeZone);
   1214 
   1215    return mozilla::Some(result);
   1216  }
   1217 };
   1218 
   1219 /**
   1220 * Returns a new mozilla::intl::DateTimeFormat with the locale and date-time
   1221 * formatting options of the given DateTimeFormat.
   1222 */
   1223 static mozilla::intl::DateTimeFormat* NewDateTimeFormat(
   1224    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1225    DateTimeValueKind kind) {
   1226  RootedValue value(cx);
   1227 
   1228  RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
   1229  if (!internals) {
   1230    return nullptr;
   1231  }
   1232 
   1233  UniqueChars locale = DateTimeFormatLocale(cx, internals);
   1234  if (!locale) {
   1235    return nullptr;
   1236  }
   1237 
   1238  if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) {
   1239    return nullptr;
   1240  }
   1241 
   1242  Rooted<JSLinearString*> timeZoneString(cx,
   1243                                         value.toString()->ensureLinear(cx));
   1244  if (!timeZoneString) {
   1245    return nullptr;
   1246  }
   1247 
   1248  AutoStableStringChars timeZone(cx);
   1249  mozilla::Span<const char16_t> timeZoneChars{};
   1250 
   1251  auto timeZoneOffset = TimeZoneOffsetString::from(timeZoneString);
   1252  if (timeZoneOffset) {
   1253    timeZoneChars = *timeZoneOffset;
   1254  } else {
   1255    if (!timeZone.initTwoByte(cx, timeZoneString)) {
   1256      return nullptr;
   1257    }
   1258    timeZoneChars = timeZone.twoByteRange();
   1259  }
   1260 
   1261  if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) {
   1262    return nullptr;
   1263  }
   1264  bool hasPattern = value.isString();
   1265 
   1266  if (!GetProperty(cx, internals, internals, cx->names().timeStyle, &value)) {
   1267    return nullptr;
   1268  }
   1269  bool hasStyle = value.isString();
   1270  if (!hasStyle) {
   1271    if (!GetProperty(cx, internals, internals, cx->names().dateStyle, &value)) {
   1272      return nullptr;
   1273    }
   1274    hasStyle = value.isString();
   1275  }
   1276 
   1277  mozilla::UniquePtr<mozilla::intl::DateTimeFormat> df = nullptr;
   1278  if (hasPattern) {
   1279    // This is a DateTimeFormat defined by a pattern option. This is internal
   1280    // to Mozilla, and not part of the ECMA-402 API.
   1281    if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) {
   1282      return nullptr;
   1283    }
   1284 
   1285    AutoStableStringChars pattern(cx);
   1286    if (!pattern.initTwoByte(cx, value.toString())) {
   1287      return nullptr;
   1288    }
   1289 
   1290    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromPattern(
   1291        mozilla::MakeStringSpan(locale.get()), pattern.twoByteRange(),
   1292        mozilla::Some(timeZoneChars));
   1293    if (dfResult.isErr()) {
   1294      intl::ReportInternalError(cx, dfResult.unwrapErr());
   1295      return nullptr;
   1296    }
   1297 
   1298    df = dfResult.unwrap();
   1299  } else if (hasStyle) {
   1300    // This is a DateTimeFormat defined by a time style or date style.
   1301    mozilla::intl::DateTimeFormat::StyleBag style;
   1302    if (!AssignDateTimeLength(cx, internals, cx->names().timeStyle,
   1303                              &style.time)) {
   1304      return nullptr;
   1305    }
   1306    if (!AssignDateTimeLength(cx, internals, cx->names().dateStyle,
   1307                              &style.date)) {
   1308      return nullptr;
   1309    }
   1310    if (!AssignHourCycleComponent(cx, internals, cx->names().hourCycle,
   1311                                  &style.hourCycle)) {
   1312      return nullptr;
   1313    }
   1314 
   1315    if (!AssignHour12Component(cx, internals, &style.hour12)) {
   1316      return nullptr;
   1317    }
   1318 
   1319    switch (kind) {
   1320      case DateTimeValueKind::TemporalDate:
   1321      case DateTimeValueKind::TemporalYearMonth:
   1322      case DateTimeValueKind::TemporalMonthDay: {
   1323        if (!style.date) {
   1324          JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1325                                    JSMSG_INVALID_FORMAT_OPTIONS,
   1326                                    DateTimeValueKindToString(kind));
   1327          return nullptr;
   1328        }
   1329        break;
   1330      }
   1331 
   1332      case DateTimeValueKind::TemporalTime: {
   1333        if (!style.time) {
   1334          JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1335                                    JSMSG_INVALID_FORMAT_OPTIONS,
   1336                                    DateTimeValueKindToString(kind));
   1337          return nullptr;
   1338        }
   1339        break;
   1340      }
   1341 
   1342      case DateTimeValueKind::Number:
   1343      case DateTimeValueKind::TemporalDateTime:
   1344      case DateTimeValueKind::TemporalZonedDateTime:
   1345      case DateTimeValueKind::TemporalInstant:
   1346        break;
   1347    }
   1348 
   1349    SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
   1350    auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
   1351    if (!dtpg) {
   1352      return nullptr;
   1353    }
   1354 
   1355    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromStyle(
   1356        mozilla::MakeStringSpan(locale.get()), style, dtpg,
   1357        mozilla::Some(timeZoneChars));
   1358    if (dfResult.isErr()) {
   1359      intl::ReportInternalError(cx, dfResult.unwrapErr());
   1360      return nullptr;
   1361    }
   1362    df = dfResult.unwrap();
   1363 
   1364    mozilla::EnumSet<DateTimeField> allowedOptions;
   1365    switch (kind) {
   1366      case DateTimeValueKind::TemporalDate:
   1367        allowedOptions = {
   1368            DateTimeField::Weekday, DateTimeField::Era, DateTimeField::Year,
   1369            DateTimeField::Month,   DateTimeField::Day,
   1370        };
   1371        break;
   1372      case DateTimeValueKind::TemporalTime:
   1373        allowedOptions = {
   1374            DateTimeField::DayPeriod,
   1375            DateTimeField::Hour,
   1376            DateTimeField::Minute,
   1377            DateTimeField::Second,
   1378            DateTimeField::FractionalSecondDigits,
   1379        };
   1380        break;
   1381      case DateTimeValueKind::TemporalDateTime:
   1382        allowedOptions = {
   1383            DateTimeField::Weekday, DateTimeField::Era,
   1384            DateTimeField::Year,    DateTimeField::Month,
   1385            DateTimeField::Day,     DateTimeField::DayPeriod,
   1386            DateTimeField::Hour,    DateTimeField::Minute,
   1387            DateTimeField::Second,  DateTimeField::FractionalSecondDigits,
   1388        };
   1389        break;
   1390      case DateTimeValueKind::TemporalYearMonth:
   1391        allowedOptions = {
   1392            DateTimeField::Era,
   1393            DateTimeField::Year,
   1394            DateTimeField::Month,
   1395        };
   1396        break;
   1397      case DateTimeValueKind::TemporalMonthDay:
   1398        allowedOptions = {
   1399            DateTimeField::Month,
   1400            DateTimeField::Day,
   1401        };
   1402        break;
   1403 
   1404      case DateTimeValueKind::Number:
   1405      case DateTimeValueKind::TemporalZonedDateTime:
   1406      case DateTimeValueKind::TemporalInstant:
   1407        break;
   1408    }
   1409 
   1410    if (!allowedOptions.isEmpty()) {
   1411      auto adjusted = AdjustDateTimeStyleFormat(df.get(), allowedOptions);
   1412      if (adjusted.isErr()) {
   1413        intl::ReportInternalError(cx, dfResult.unwrapErr());
   1414        return nullptr;
   1415      }
   1416      auto bag = adjusted.unwrap();
   1417 
   1418      auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromComponents(
   1419          mozilla::MakeStringSpan(locale.get()), bag, dtpg,
   1420          mozilla::Some(timeZoneChars));
   1421      if (dfResult.isErr()) {
   1422        intl::ReportInternalError(cx, dfResult.unwrapErr());
   1423        return nullptr;
   1424      }
   1425      df = dfResult.unwrap();
   1426    }
   1427  } else {
   1428    // This is a DateTimeFormat defined by a components bag.
   1429    mozilla::intl::DateTimeFormat::ComponentsBag bag;
   1430 
   1431    if (!AssignTextComponent(cx, internals, cx->names().era, &bag.era)) {
   1432      return nullptr;
   1433    }
   1434    if (!AssignNumericComponent(cx, internals, cx->names().year, &bag.year)) {
   1435      return nullptr;
   1436    }
   1437    if (!AssignMonthComponent(cx, internals, cx->names().month, &bag.month)) {
   1438      return nullptr;
   1439    }
   1440    if (!AssignNumericComponent(cx, internals, cx->names().day, &bag.day)) {
   1441      return nullptr;
   1442    }
   1443    if (!AssignTextComponent(cx, internals, cx->names().weekday,
   1444                             &bag.weekday)) {
   1445      return nullptr;
   1446    }
   1447    if (!AssignNumericComponent(cx, internals, cx->names().hour, &bag.hour)) {
   1448      return nullptr;
   1449    }
   1450    if (!AssignNumericComponent(cx, internals, cx->names().minute,
   1451                                &bag.minute)) {
   1452      return nullptr;
   1453    }
   1454    if (!AssignNumericComponent(cx, internals, cx->names().second,
   1455                                &bag.second)) {
   1456      return nullptr;
   1457    }
   1458    if (!AssignTimeZoneNameComponent(cx, internals, cx->names().timeZoneName,
   1459                                     &bag.timeZoneName)) {
   1460      return nullptr;
   1461    }
   1462    if (!AssignHourCycleComponent(cx, internals, cx->names().hourCycle,
   1463                                  &bag.hourCycle)) {
   1464      return nullptr;
   1465    }
   1466    if (!AssignTextComponent(cx, internals, cx->names().dayPeriod,
   1467                             &bag.dayPeriod)) {
   1468      return nullptr;
   1469    }
   1470    if (!AssignHour12Component(cx, internals, &bag.hour12)) {
   1471      return nullptr;
   1472    }
   1473 
   1474    if (!GetProperty(cx, internals, internals,
   1475                     cx->names().fractionalSecondDigits, &value)) {
   1476      return nullptr;
   1477    }
   1478    if (value.isInt32()) {
   1479      bag.fractionalSecondDigits = mozilla::Some(value.toInt32());
   1480    } else {
   1481      MOZ_ASSERT(value.isUndefined());
   1482    }
   1483 
   1484    DateTimeFormatArgs dateTimeFormatArgs;
   1485    if (!GetDateTimeFormatArgs(cx, internals, kind, &dateTimeFormatArgs)) {
   1486      return nullptr;
   1487    }
   1488    auto [required, defaults, inherit] = dateTimeFormatArgs;
   1489 
   1490    auto resolvedBag = GetDateTimeFormat(bag, required, defaults, inherit);
   1491    if (!resolvedBag) {
   1492      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1493                                JSMSG_INVALID_FORMAT_OPTIONS,
   1494                                DateTimeValueKindToString(kind));
   1495      return nullptr;
   1496    }
   1497    bag = *resolvedBag;
   1498 
   1499    SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
   1500    auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
   1501    if (!dtpg) {
   1502      return nullptr;
   1503    }
   1504 
   1505    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromComponents(
   1506        mozilla::MakeStringSpan(locale.get()), bag, dtpg,
   1507        mozilla::Some(timeZoneChars));
   1508    if (dfResult.isErr()) {
   1509      intl::ReportInternalError(cx, dfResult.unwrapErr());
   1510      return nullptr;
   1511    }
   1512    df = dfResult.unwrap();
   1513  }
   1514 
   1515  return df.release();
   1516 }
   1517 
   1518 void js::DateTimeFormatObject::maybeClearCache(DateTimeValueKind kind) {
   1519  if (getDateTimeValueKind() == kind) {
   1520    return;
   1521  }
   1522  setDateTimeValueKind(kind);
   1523 
   1524  if (auto* df = getDateFormat()) {
   1525    intl::RemoveICUCellMemory(
   1526        this, DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
   1527    delete df;
   1528 
   1529    setDateFormat(nullptr);
   1530  }
   1531 
   1532  if (auto* dif = getDateIntervalFormat()) {
   1533    intl::RemoveICUCellMemory(
   1534        this, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
   1535    delete dif;
   1536 
   1537    setDateIntervalFormat(nullptr);
   1538  }
   1539 }
   1540 
   1541 static mozilla::intl::DateTimeFormat* GetOrCreateDateTimeFormat(
   1542    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1543    DateTimeValueKind kind) {
   1544  // Clear previously created formatters if their type doesn't match.
   1545  dateTimeFormat->maybeClearCache(kind);
   1546 
   1547  // Obtain a cached mozilla::intl::DateTimeFormat object.
   1548  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
   1549  if (df) {
   1550    return df;
   1551  }
   1552 
   1553  df = NewDateTimeFormat(cx, dateTimeFormat, kind);
   1554  if (!df) {
   1555    return nullptr;
   1556  }
   1557  dateTimeFormat->setDateFormat(df);
   1558 
   1559  intl::AddICUCellMemory(dateTimeFormat,
   1560                         DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
   1561  return df;
   1562 }
   1563 
   1564 template <typename T>
   1565 static bool SetResolvedProperty(JSContext* cx, HandleObject resolved,
   1566                                Handle<PropertyName*> name,
   1567                                mozilla::Maybe<T> intlProp) {
   1568  if (!intlProp) {
   1569    return true;
   1570  }
   1571  JSString* str = NewStringCopyZ<CanGC>(
   1572      cx, mozilla::intl::DateTimeFormat::ToString(*intlProp));
   1573  if (!str) {
   1574    return false;
   1575  }
   1576  RootedValue value(cx, StringValue(str));
   1577  return DefineDataProperty(cx, resolved, name, value);
   1578 }
   1579 
   1580 bool js::intl_resolveDateTimeFormatComponents(JSContext* cx, unsigned argc,
   1581                                              Value* vp) {
   1582  CallArgs args = CallArgsFromVp(argc, vp);
   1583  MOZ_ASSERT(args.length() == 3);
   1584  MOZ_ASSERT(args[0].isObject());
   1585  MOZ_ASSERT(args[1].isObject());
   1586  MOZ_ASSERT(args[2].isBoolean());
   1587 
   1588  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
   1589  dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
   1590 
   1591  RootedObject resolved(cx, &args[1].toObject());
   1592 
   1593  bool includeDateTimeFields = args[2].toBoolean();
   1594 
   1595  mozilla::intl::DateTimeFormat* df =
   1596      GetOrCreateDateTimeFormat(cx, dateTimeFormat, DateTimeValueKind::Number);
   1597  if (!df) {
   1598    return false;
   1599  }
   1600 
   1601  auto result = df->ResolveComponents();
   1602  if (result.isErr()) {
   1603    intl::ReportInternalError(cx, result.unwrapErr());
   1604    return false;
   1605  }
   1606 
   1607  mozilla::intl::DateTimeFormat::ComponentsBag components = result.unwrap();
   1608 
   1609  // Map the resolved mozilla::intl::DateTimeFormat::ComponentsBag to the
   1610  // options object as returned by DateTimeFormat.prototype.resolvedOptions.
   1611  //
   1612  // Resolved options must match the ordering as defined in:
   1613  // https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.resolvedoptions
   1614 
   1615  if (!SetResolvedProperty(cx, resolved, cx->names().hourCycle,
   1616                           components.hourCycle)) {
   1617    return false;
   1618  }
   1619 
   1620  if (components.hour12) {
   1621    RootedValue value(cx, BooleanValue(*components.hour12));
   1622    if (!DefineDataProperty(cx, resolved, cx->names().hour12, value)) {
   1623      return false;
   1624    }
   1625  }
   1626 
   1627  if (!includeDateTimeFields) {
   1628    args.rval().setUndefined();
   1629    // Do not include date time fields.
   1630    return true;
   1631  }
   1632 
   1633  if (!SetResolvedProperty(cx, resolved, cx->names().weekday,
   1634                           components.weekday)) {
   1635    return false;
   1636  }
   1637  if (!SetResolvedProperty(cx, resolved, cx->names().era, components.era)) {
   1638    return false;
   1639  }
   1640  if (!SetResolvedProperty(cx, resolved, cx->names().year, components.year)) {
   1641    return false;
   1642  }
   1643  if (!SetResolvedProperty(cx, resolved, cx->names().month, components.month)) {
   1644    return false;
   1645  }
   1646  if (!SetResolvedProperty(cx, resolved, cx->names().day, components.day)) {
   1647    return false;
   1648  }
   1649  if (!SetResolvedProperty(cx, resolved, cx->names().dayPeriod,
   1650                           components.dayPeriod)) {
   1651    return false;
   1652  }
   1653  if (!SetResolvedProperty(cx, resolved, cx->names().hour, components.hour)) {
   1654    return false;
   1655  }
   1656  if (!SetResolvedProperty(cx, resolved, cx->names().minute,
   1657                           components.minute)) {
   1658    return false;
   1659  }
   1660  if (!SetResolvedProperty(cx, resolved, cx->names().second,
   1661                           components.second)) {
   1662    return false;
   1663  }
   1664  if (!SetResolvedProperty(cx, resolved, cx->names().timeZoneName,
   1665                           components.timeZoneName)) {
   1666    return false;
   1667  }
   1668 
   1669  if (components.fractionalSecondDigits) {
   1670    RootedValue value(cx, Int32Value(*components.fractionalSecondDigits));
   1671    if (!DefineDataProperty(cx, resolved, cx->names().fractionalSecondDigits,
   1672                            value)) {
   1673      return false;
   1674    }
   1675  }
   1676 
   1677  args.rval().setUndefined();
   1678  return true;
   1679 }
   1680 
   1681 /**
   1682 * ToDateTimeFormattable ( value )
   1683 *
   1684 * https://tc39.es/proposal-temporal/#sec-todatetimeformattable
   1685 */
   1686 static auto ToDateTimeFormattable(const Value& value) {
   1687  // Step 1. (Inlined IsTemporalObject)
   1688  if (value.isObject()) {
   1689    auto* obj = CheckedUnwrapStatic(&value.toObject());
   1690    if (obj) {
   1691      if (obj->is<PlainDateObject>()) {
   1692        return DateTimeValueKind::TemporalDate;
   1693      }
   1694      if (obj->is<PlainDateTimeObject>()) {
   1695        return DateTimeValueKind::TemporalDateTime;
   1696      }
   1697      if (obj->is<PlainTimeObject>()) {
   1698        return DateTimeValueKind::TemporalTime;
   1699      }
   1700      if (obj->is<PlainYearMonthObject>()) {
   1701        return DateTimeValueKind::TemporalYearMonth;
   1702      }
   1703      if (obj->is<PlainMonthDayObject>()) {
   1704        return DateTimeValueKind::TemporalMonthDay;
   1705      }
   1706      if (obj->is<ZonedDateTimeObject>()) {
   1707        return DateTimeValueKind::TemporalZonedDateTime;
   1708      }
   1709      if (obj->is<InstantObject>()) {
   1710        return DateTimeValueKind::TemporalInstant;
   1711      }
   1712      return DateTimeValueKind::Number;
   1713    }
   1714  }
   1715 
   1716  // Step 2. (ToNumber performed in caller)
   1717  return DateTimeValueKind::Number;
   1718 }
   1719 
   1720 static bool ResolveCalendarAndTimeZone(
   1721    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) {
   1722  Rooted<JSObject*> internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
   1723  if (!internals) {
   1724    return false;
   1725  }
   1726 
   1727  Rooted<Value> calendarValue(cx);
   1728  if (!GetProperty(cx, internals, internals, cx->names().calendar,
   1729                   &calendarValue)) {
   1730    return false;
   1731  }
   1732  Rooted<JSString*> calendarString(cx, calendarValue.toString());
   1733 
   1734  Rooted<CalendarValue> calendar(cx);
   1735  if (!CanonicalizeCalendar(cx, calendarString, &calendar)) {
   1736    return false;
   1737  }
   1738 
   1739  Rooted<Value> timeZoneValue(cx);
   1740  if (!GetProperty(cx, internals, internals, cx->names().timeZone,
   1741                   &timeZoneValue)) {
   1742    return false;
   1743  }
   1744  Rooted<JSString*> timeZoneString(cx, timeZoneValue.toString());
   1745 
   1746  Rooted<ParsedTimeZone> parsedTimeZone(cx);
   1747  Rooted<TimeZoneValue> timeZone(cx);
   1748  if (!ParseTemporalTimeZoneString(cx, timeZoneString, &parsedTimeZone) ||
   1749      !ToTemporalTimeZone(cx, parsedTimeZone, &timeZone)) {
   1750    return false;
   1751  }
   1752 
   1753  dateTimeFormat->setCalendar(calendar);
   1754  dateTimeFormat->setTimeZone(timeZone);
   1755  return true;
   1756 }
   1757 
   1758 /**
   1759 * HandleDateTimeTemporalDate ( dateTimeFormat, temporalDate )
   1760 *
   1761 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldate
   1762 */
   1763 static bool HandleDateTimeTemporalDate(
   1764    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1765    Handle<PlainDateObject*> unwrappedTemporalDate, ClippedTime* result) {
   1766  auto isoDate = unwrappedTemporalDate->date();
   1767  auto calendarId = unwrappedTemporalDate->calendar().identifier();
   1768 
   1769  Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar());
   1770  Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone());
   1771  if (!calendar || !timeZone) {
   1772    if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) {
   1773      return false;
   1774    }
   1775    calendar.set(dateTimeFormat->getCalendar());
   1776    timeZone.set(dateTimeFormat->getTimeZone());
   1777  }
   1778  MOZ_ASSERT(calendar && timeZone);
   1779 
   1780  // Step 1.
   1781  if (calendarId != CalendarId::ISO8601 &&
   1782      calendarId != calendar.identifier()) {
   1783    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1784                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
   1785                              CalendarIdentifier(calendarId).data(),
   1786                              CalendarIdentifier(calendar).data());
   1787    return false;
   1788  }
   1789 
   1790  // Step 2.
   1791  auto isoDateTime = ISODateTime{isoDate, {12, 0, 0}};
   1792 
   1793  // Step 3.
   1794  EpochNanoseconds epochNs;
   1795  if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime,
   1796                              TemporalDisambiguation::Compatible, &epochNs)) {
   1797    return false;
   1798  }
   1799 
   1800  // Steps 4-5. (Performed in NewDateTimeFormat)
   1801 
   1802  // Step 6.
   1803  int64_t milliseconds = epochNs.floorToMilliseconds();
   1804  *result = JS::TimeClip(double(milliseconds));
   1805  return true;
   1806 }
   1807 
   1808 /**
   1809 * HandleDateTimeTemporalYearMonth ( dateTimeFormat, temporalYearMonth )
   1810 *
   1811 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalyearmonth
   1812 */
   1813 static bool HandleDateTimeTemporalYearMonth(
   1814    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1815    Handle<PlainYearMonthObject*> unwrappedTemporalYearMonth,
   1816    ClippedTime* result) {
   1817  auto isoDate = unwrappedTemporalYearMonth->date();
   1818  auto calendarId = unwrappedTemporalYearMonth->calendar().identifier();
   1819 
   1820  Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar());
   1821  Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone());
   1822  if (!calendar || !timeZone) {
   1823    if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) {
   1824      return false;
   1825    }
   1826    calendar.set(dateTimeFormat->getCalendar());
   1827    timeZone.set(dateTimeFormat->getTimeZone());
   1828  }
   1829  MOZ_ASSERT(calendar && timeZone);
   1830 
   1831  // Step 1.
   1832  if (calendarId != calendar.identifier()) {
   1833    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1834                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
   1835                              CalendarIdentifier(calendarId).data(),
   1836                              CalendarIdentifier(calendar).data());
   1837    return false;
   1838  }
   1839 
   1840  // Step 2.
   1841  auto isoDateTime = ISODateTime{isoDate, {12, 0, 0}};
   1842 
   1843  // Step 3.
   1844  EpochNanoseconds epochNs;
   1845  if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime,
   1846                              TemporalDisambiguation::Compatible, &epochNs)) {
   1847    return false;
   1848  }
   1849 
   1850  // Steps 4-5. (Performed in NewDateTimeFormat)
   1851 
   1852  // Step 6.
   1853  int64_t milliseconds = epochNs.floorToMilliseconds();
   1854  *result = JS::TimeClip(double(milliseconds));
   1855  return true;
   1856 }
   1857 
   1858 /**
   1859 * HandleDateTimeTemporalMonthDay ( dateTimeFormat, temporalMonthDay )
   1860 *
   1861 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalmonthday
   1862 */
   1863 static bool HandleDateTimeTemporalMonthDay(
   1864    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1865    Handle<PlainMonthDayObject*> unwrappedTemporalMonthDay,
   1866    ClippedTime* result) {
   1867  auto isoDate = unwrappedTemporalMonthDay->date();
   1868  auto calendarId = unwrappedTemporalMonthDay->calendar().identifier();
   1869 
   1870  Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar());
   1871  Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone());
   1872  if (!calendar || !timeZone) {
   1873    if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) {
   1874      return false;
   1875    }
   1876    calendar.set(dateTimeFormat->getCalendar());
   1877    timeZone.set(dateTimeFormat->getTimeZone());
   1878  }
   1879  MOZ_ASSERT(calendar && timeZone);
   1880 
   1881  // Step 1.
   1882  if (calendarId != calendar.identifier()) {
   1883    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1884                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
   1885                              CalendarIdentifier(calendarId).data(),
   1886                              CalendarIdentifier(calendar).data());
   1887    return false;
   1888  }
   1889 
   1890  // Step 2.
   1891  auto isoDateTime = ISODateTime{isoDate, {12, 0, 0}};
   1892 
   1893  // Step 3.
   1894  EpochNanoseconds epochNs;
   1895  if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime,
   1896                              TemporalDisambiguation::Compatible, &epochNs)) {
   1897    return false;
   1898  }
   1899 
   1900  // Steps 4-5. (Performed in NewDateTimeFormat)
   1901 
   1902  // Step 6.
   1903  int64_t milliseconds = epochNs.floorToMilliseconds();
   1904  *result = JS::TimeClip(double(milliseconds));
   1905  return true;
   1906 }
   1907 
   1908 /**
   1909 * HandleDateTimeTemporalTime ( dateTimeFormat, temporalTime )
   1910 *
   1911 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaltime
   1912 */
   1913 static bool HandleDateTimeTemporalTime(
   1914    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1915    Handle<PlainTimeObject*> unwrappedTemporalTime, ClippedTime* result) {
   1916  auto time = unwrappedTemporalTime->time();
   1917 
   1918  Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone());
   1919  if (!timeZone) {
   1920    if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) {
   1921      return false;
   1922    }
   1923    timeZone.set(dateTimeFormat->getTimeZone());
   1924  }
   1925  MOZ_ASSERT(timeZone);
   1926 
   1927  // Steps 1-2.
   1928  auto isoDateTime = ISODateTime{{1970, 1, 1}, time};
   1929 
   1930  // Step 3.
   1931  EpochNanoseconds epochNs;
   1932  if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime,
   1933                              TemporalDisambiguation::Compatible, &epochNs)) {
   1934    return false;
   1935  }
   1936 
   1937  // Steps 4-5. (Performed in NewDateTimeFormat)
   1938 
   1939  // Step 6.
   1940  int64_t milliseconds = epochNs.floorToMilliseconds();
   1941  *result = JS::TimeClip(double(milliseconds));
   1942  return true;
   1943 }
   1944 
   1945 /**
   1946 * HandleDateTimeTemporalDateTime ( dateTimeFormat, dateTime )
   1947 *
   1948 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldatetime
   1949 */
   1950 static bool HandleDateTimeTemporalDateTime(
   1951    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   1952    Handle<PlainDateTimeObject*> unwrappedDateTime, ClippedTime* result) {
   1953  auto isoDateTime = unwrappedDateTime->dateTime();
   1954  auto calendarId = unwrappedDateTime->calendar().identifier();
   1955 
   1956  Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar());
   1957  Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone());
   1958  if (!calendar || !timeZone) {
   1959    if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) {
   1960      return false;
   1961    }
   1962    calendar.set(dateTimeFormat->getCalendar());
   1963    timeZone.set(dateTimeFormat->getTimeZone());
   1964  }
   1965  MOZ_ASSERT(calendar && timeZone);
   1966 
   1967  // Step 1.
   1968  if (calendarId != CalendarId::ISO8601 &&
   1969      calendarId != calendar.identifier()) {
   1970    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1971                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
   1972                              CalendarIdentifier(calendarId).data(),
   1973                              CalendarIdentifier(calendar).data());
   1974    return false;
   1975  }
   1976 
   1977  // Step 2.
   1978  EpochNanoseconds epochNs;
   1979  if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime,
   1980                              TemporalDisambiguation::Compatible, &epochNs)) {
   1981    return false;
   1982  }
   1983 
   1984  // Step 3. (Performed in NewDateTimeFormat)
   1985 
   1986  // Step 4.
   1987  int64_t milliseconds = epochNs.floorToMilliseconds();
   1988  *result = JS::TimeClip(double(milliseconds));
   1989  return true;
   1990 }
   1991 
   1992 /**
   1993 * HandleDateTimeTemporalInstant ( dateTimeFormat, instant )
   1994 *
   1995 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalinstant
   1996 */
   1997 static bool HandleDateTimeTemporalInstant(InstantObject* unwrappedInstant,
   1998                                          ClippedTime* result) {
   1999  // Step 1. (Performed in NewDateTimeFormat)
   2000 
   2001  // Step 2.
   2002  auto epochNs = unwrappedInstant->epochNanoseconds();
   2003  int64_t milliseconds = epochNs.floorToMilliseconds();
   2004  *result = JS::TimeClip(double(milliseconds));
   2005  return true;
   2006 }
   2007 
   2008 /**
   2009 * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
   2010 */
   2011 static bool HandleDateTimeTemporalZonedDateTime(
   2012    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   2013    Handle<ZonedDateTimeObject*> unwrappedZonedDateTime, ClippedTime* result) {
   2014  auto epochNs = unwrappedZonedDateTime->epochNanoseconds();
   2015  auto calendarId = unwrappedZonedDateTime->calendar().identifier();
   2016 
   2017  Rooted<CalendarValue> calendar(cx, dateTimeFormat->getCalendar());
   2018  Rooted<TimeZoneValue> timeZone(cx, dateTimeFormat->getTimeZone());
   2019  if (!calendar || !timeZone) {
   2020    if (!ResolveCalendarAndTimeZone(cx, dateTimeFormat)) {
   2021      return false;
   2022    }
   2023    calendar.set(dateTimeFormat->getCalendar());
   2024    timeZone.set(dateTimeFormat->getTimeZone());
   2025  }
   2026  MOZ_ASSERT(calendar && timeZone);
   2027 
   2028  // Step 4.
   2029  if (calendarId != CalendarId::ISO8601 &&
   2030      calendarId != calendar.identifier()) {
   2031    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2032                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
   2033                              CalendarIdentifier(calendarId).data(),
   2034                              CalendarIdentifier(calendar).data());
   2035    return false;
   2036  }
   2037 
   2038  // Step 5.
   2039  int64_t milliseconds = epochNs.floorToMilliseconds();
   2040  *result = JS::TimeClip(double(milliseconds));
   2041  return true;
   2042 }
   2043 
   2044 /**
   2045 * HandleDateTimeOthers ( dateTimeFormat, x )
   2046 *
   2047 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimeothers
   2048 */
   2049 static bool HandleDateTimeOthers(JSContext* cx, const char* method, double x,
   2050                                 ClippedTime* result) {
   2051  // Step 1.
   2052  auto clipped = JS::TimeClip(x);
   2053 
   2054  // Step 2.
   2055  if (!clipped.isValid()) {
   2056    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2057                              JSMSG_DATE_NOT_FINITE, "DateTimeFormat", method);
   2058    return false;
   2059  }
   2060 
   2061  // Step 4. (Performed in NewDateTimeFormat)
   2062 
   2063  // Steps 3 and 5.
   2064  *result = clipped;
   2065  return true;
   2066 }
   2067 
   2068 /**
   2069 * HandleDateTimeValue ( dateTimeFormat, x )
   2070 *
   2071 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue
   2072 */
   2073 static bool HandleDateTimeValue(JSContext* cx, const char* method,
   2074                                Handle<DateTimeFormatObject*> dateTimeFormat,
   2075                                Handle<Value> x, ClippedTime* result) {
   2076  MOZ_ASSERT(x.isObject() || x.isNumber());
   2077 
   2078  // Step 1.
   2079  if (x.isObject()) {
   2080    Rooted<JSObject*> unwrapped(cx, CheckedUnwrapStatic(&x.toObject()));
   2081    if (!unwrapped) {
   2082      ReportAccessDenied(cx);
   2083      return false;
   2084    }
   2085 
   2086    // Step 1.a.
   2087    if (unwrapped->is<PlainDateObject>()) {
   2088      return HandleDateTimeTemporalDate(
   2089          cx, dateTimeFormat, unwrapped.as<PlainDateObject>(), result);
   2090    }
   2091 
   2092    // Step 1.b.
   2093    if (unwrapped->is<PlainYearMonthObject>()) {
   2094      return HandleDateTimeTemporalYearMonth(
   2095          cx, dateTimeFormat, unwrapped.as<PlainYearMonthObject>(), result);
   2096    }
   2097 
   2098    // Step 1.c.
   2099    if (unwrapped->is<PlainMonthDayObject>()) {
   2100      return HandleDateTimeTemporalMonthDay(
   2101          cx, dateTimeFormat, unwrapped.as<PlainMonthDayObject>(), result);
   2102    }
   2103 
   2104    // Step 1.d.
   2105    if (unwrapped->is<PlainTimeObject>()) {
   2106      return HandleDateTimeTemporalTime(
   2107          cx, dateTimeFormat, unwrapped.as<PlainTimeObject>(), result);
   2108    }
   2109 
   2110    // Step 1.e.
   2111    if (unwrapped->is<PlainDateTimeObject>()) {
   2112      return HandleDateTimeTemporalDateTime(
   2113          cx, dateTimeFormat, unwrapped.as<PlainDateTimeObject>(), result);
   2114    }
   2115 
   2116    // Step 1.f.
   2117    if (unwrapped->is<InstantObject>()) {
   2118      return HandleDateTimeTemporalInstant(&unwrapped->as<InstantObject>(),
   2119                                           result);
   2120    }
   2121 
   2122    // Step 1.g.
   2123    MOZ_ASSERT(unwrapped->is<ZonedDateTimeObject>());
   2124 
   2125    // Step 1.h.
   2126    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2127                              JSMSG_UNEXPECTED_TYPE, "object",
   2128                              unwrapped->getClass()->name);
   2129    return false;
   2130  }
   2131 
   2132  // Step 2.
   2133  return HandleDateTimeOthers(cx, method, x.toNumber(), result);
   2134 }
   2135 
   2136 static bool intl_FormatDateTime(JSContext* cx,
   2137                                const mozilla::intl::DateTimeFormat* df,
   2138                                ClippedTime x, MutableHandleValue result) {
   2139  MOZ_ASSERT(x.isValid());
   2140 
   2141  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
   2142  auto dfResult = df->TryFormat(x.toDouble(), buffer);
   2143  if (dfResult.isErr()) {
   2144    intl::ReportInternalError(cx, dfResult.unwrapErr());
   2145    return false;
   2146  }
   2147 
   2148  JSString* str = buffer.toString(cx);
   2149  if (!str) {
   2150    return false;
   2151  }
   2152 
   2153  result.setString(str);
   2154  return true;
   2155 }
   2156 
   2157 using FieldType = js::ImmutableTenuredPtr<PropertyName*> JSAtomState::*;
   2158 
   2159 static FieldType GetFieldTypeForPartType(mozilla::intl::DateTimePartType type) {
   2160  switch (type) {
   2161    case mozilla::intl::DateTimePartType::Literal:
   2162      return &JSAtomState::literal;
   2163    case mozilla::intl::DateTimePartType::Era:
   2164      return &JSAtomState::era;
   2165    case mozilla::intl::DateTimePartType::Year:
   2166      return &JSAtomState::year;
   2167    case mozilla::intl::DateTimePartType::YearName:
   2168      return &JSAtomState::yearName;
   2169    case mozilla::intl::DateTimePartType::RelatedYear:
   2170      return &JSAtomState::relatedYear;
   2171    case mozilla::intl::DateTimePartType::Month:
   2172      return &JSAtomState::month;
   2173    case mozilla::intl::DateTimePartType::Day:
   2174      return &JSAtomState::day;
   2175    case mozilla::intl::DateTimePartType::Hour:
   2176      return &JSAtomState::hour;
   2177    case mozilla::intl::DateTimePartType::Minute:
   2178      return &JSAtomState::minute;
   2179    case mozilla::intl::DateTimePartType::Second:
   2180      return &JSAtomState::second;
   2181    case mozilla::intl::DateTimePartType::Weekday:
   2182      return &JSAtomState::weekday;
   2183    case mozilla::intl::DateTimePartType::DayPeriod:
   2184      return &JSAtomState::dayPeriod;
   2185    case mozilla::intl::DateTimePartType::TimeZoneName:
   2186      return &JSAtomState::timeZoneName;
   2187    case mozilla::intl::DateTimePartType::FractionalSecondDigits:
   2188      return &JSAtomState::fractionalSecond;
   2189    case mozilla::intl::DateTimePartType::Unknown:
   2190      return &JSAtomState::unknown;
   2191  }
   2192 
   2193  MOZ_CRASH(
   2194      "unenumerated, undocumented format field returned "
   2195      "by iterator");
   2196 }
   2197 
   2198 static FieldType GetFieldTypeForPartSource(
   2199    mozilla::intl::DateTimePartSource source) {
   2200  switch (source) {
   2201    case mozilla::intl::DateTimePartSource::Shared:
   2202      return &JSAtomState::shared;
   2203    case mozilla::intl::DateTimePartSource::StartRange:
   2204      return &JSAtomState::startRange;
   2205    case mozilla::intl::DateTimePartSource::EndRange:
   2206      return &JSAtomState::endRange;
   2207  }
   2208 
   2209  MOZ_CRASH(
   2210      "unenumerated, undocumented format field returned "
   2211      "by iterator");
   2212 }
   2213 
   2214 // A helper function to create an ArrayObject from DateTimePart objects.
   2215 // When hasNoSource is true, we don't need to create the ||Source|| property for
   2216 // the DateTimePart object.
   2217 static bool CreateDateTimePartArray(
   2218    JSContext* cx, mozilla::Span<const char16_t> formattedSpan,
   2219    bool hasNoSource, const mozilla::intl::DateTimePartVector& parts,
   2220    MutableHandleValue result) {
   2221  RootedString overallResult(cx, NewStringCopy<CanGC>(cx, formattedSpan));
   2222  if (!overallResult) {
   2223    return false;
   2224  }
   2225 
   2226  Rooted<ArrayObject*> partsArray(
   2227      cx, NewDenseFullyAllocatedArray(cx, parts.length()));
   2228  if (!partsArray) {
   2229    return false;
   2230  }
   2231  partsArray->ensureDenseInitializedLength(0, parts.length());
   2232 
   2233  if (overallResult->length() == 0) {
   2234    // An empty string contains no parts, so avoid extra work below.
   2235    result.setObject(*partsArray);
   2236    return true;
   2237  }
   2238 
   2239  RootedObject singlePart(cx);
   2240  RootedValue val(cx);
   2241 
   2242  size_t index = 0;
   2243  size_t beginIndex = 0;
   2244  for (const mozilla::intl::DateTimePart& part : parts) {
   2245    singlePart = NewPlainObject(cx);
   2246    if (!singlePart) {
   2247      return false;
   2248    }
   2249 
   2250    FieldType type = GetFieldTypeForPartType(part.mType);
   2251    val = StringValue(cx->names().*type);
   2252    if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
   2253      return false;
   2254    }
   2255 
   2256    MOZ_ASSERT(part.mEndIndex > beginIndex);
   2257    JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex,
   2258                                                 part.mEndIndex - beginIndex);
   2259    if (!partStr) {
   2260      return false;
   2261    }
   2262    val = StringValue(partStr);
   2263    if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
   2264      return false;
   2265    }
   2266 
   2267    if (!hasNoSource) {
   2268      FieldType source = GetFieldTypeForPartSource(part.mSource);
   2269      val = StringValue(cx->names().*source);
   2270      if (!DefineDataProperty(cx, singlePart, cx->names().source, val)) {
   2271        return false;
   2272      }
   2273    }
   2274 
   2275    beginIndex = part.mEndIndex;
   2276    partsArray->initDenseElement(index++, ObjectValue(*singlePart));
   2277  }
   2278 
   2279  MOZ_ASSERT(index == parts.length());
   2280  MOZ_ASSERT(beginIndex == formattedSpan.size());
   2281  result.setObject(*partsArray);
   2282  return true;
   2283 }
   2284 
   2285 static bool intl_FormatToPartsDateTime(JSContext* cx,
   2286                                       const mozilla::intl::DateTimeFormat* df,
   2287                                       ClippedTime x, bool hasNoSource,
   2288                                       MutableHandleValue result) {
   2289  MOZ_ASSERT(x.isValid());
   2290 
   2291  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
   2292  mozilla::intl::DateTimePartVector parts;
   2293  auto r = df->TryFormatToParts(x.toDouble(), buffer, parts);
   2294  if (r.isErr()) {
   2295    intl::ReportInternalError(cx, r.unwrapErr());
   2296    return false;
   2297  }
   2298 
   2299  return CreateDateTimePartArray(cx, buffer, hasNoSource, parts, result);
   2300 }
   2301 
   2302 bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) {
   2303  CallArgs args = CallArgsFromVp(argc, vp);
   2304  MOZ_ASSERT(args.length() == 3);
   2305  MOZ_ASSERT(args[0].isObject());
   2306  MOZ_ASSERT(args[2].isBoolean());
   2307 
   2308  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
   2309  dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
   2310 
   2311  bool formatToParts = args[2].toBoolean();
   2312  const char* method = formatToParts ? "formatToParts" : "format";
   2313 
   2314  auto kind = ToDateTimeFormattable(args[1]);
   2315 
   2316  JS::ClippedTime x;
   2317  if (!args[1].isUndefined()) {
   2318    Rooted<Value> value(cx, args[1]);
   2319    if (kind == DateTimeValueKind::Number) {
   2320      if (!ToNumber(cx, &value)) {
   2321        return false;
   2322      }
   2323    }
   2324    MOZ_ASSERT(value.isNumber() || value.isObject());
   2325 
   2326    if (!HandleDateTimeValue(cx, method, dateTimeFormat, value, &x)) {
   2327      return false;
   2328    }
   2329  } else {
   2330    x = DateNow(cx);
   2331  }
   2332  MOZ_ASSERT(x.isValid());
   2333 
   2334  mozilla::intl::DateTimeFormat* df =
   2335      GetOrCreateDateTimeFormat(cx, dateTimeFormat, kind);
   2336  if (!df) {
   2337    return false;
   2338  }
   2339 
   2340  // Use the DateTimeFormat to actually format the time stamp.
   2341  return formatToParts ? intl_FormatToPartsDateTime(
   2342                             cx, df, x, /* hasNoSource */ true, args.rval())
   2343                       : intl_FormatDateTime(cx, df, x, args.rval());
   2344 }
   2345 
   2346 bool js::intl::FormatDateTime(JSContext* cx,
   2347                              Handle<DateTimeFormatObject*> dateTimeFormat,
   2348                              double millis, MutableHandle<Value> result) {
   2349  auto x = JS::TimeClip(millis);
   2350  MOZ_ASSERT(x.isValid());
   2351 
   2352  mozilla::intl::DateTimeFormat* df =
   2353      GetOrCreateDateTimeFormat(cx, dateTimeFormat, DateTimeValueKind::Number);
   2354  if (!df) {
   2355    return false;
   2356  }
   2357 
   2358  return intl_FormatDateTime(cx, df, x, result);
   2359 }
   2360 
   2361 /**
   2362 * Returns a new DateIntervalFormat with the locale and date-time formatting
   2363 * options of the given DateTimeFormat.
   2364 */
   2365 static mozilla::intl::DateIntervalFormat* NewDateIntervalFormat(
   2366    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   2367    mozilla::intl::DateTimeFormat& mozDtf) {
   2368  RootedValue value(cx);
   2369  RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
   2370  if (!internals) {
   2371    return nullptr;
   2372  }
   2373 
   2374  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
   2375  auto result = mozDtf.GetPattern(pattern);
   2376  if (result.isErr()) {
   2377    intl::ReportInternalError(cx, result.unwrapErr());
   2378    return nullptr;
   2379  }
   2380 
   2381  // Determine the hour cycle used in the resolved pattern.
   2382  mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hcPattern =
   2383      mozilla::intl::DateTimeFormat::HourCycleFromPattern(pattern);
   2384 
   2385  UniqueChars locale = DateTimeFormatLocale(cx, internals, hcPattern);
   2386  if (!locale) {
   2387    return nullptr;
   2388  }
   2389 
   2390  if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) {
   2391    return nullptr;
   2392  }
   2393 
   2394  Rooted<JSLinearString*> timeZoneString(cx,
   2395                                         value.toString()->ensureLinear(cx));
   2396  if (!timeZoneString) {
   2397    return nullptr;
   2398  }
   2399 
   2400  AutoStableStringChars timeZone(cx);
   2401  mozilla::Span<const char16_t> timeZoneChars{};
   2402 
   2403  auto timeZoneOffset = TimeZoneOffsetString::from(timeZoneString);
   2404  if (timeZoneOffset) {
   2405    timeZoneChars = *timeZoneOffset;
   2406  } else {
   2407    if (!timeZone.initTwoByte(cx, timeZoneString)) {
   2408      return nullptr;
   2409    }
   2410    timeZoneChars = timeZone.twoByteRange();
   2411  }
   2412 
   2413  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx);
   2414  auto skelResult = mozDtf.GetOriginalSkeleton(skeleton);
   2415  if (skelResult.isErr()) {
   2416    intl::ReportInternalError(cx, skelResult.unwrapErr());
   2417    return nullptr;
   2418  }
   2419 
   2420  auto dif = mozilla::intl::DateIntervalFormat::TryCreate(
   2421      mozilla::MakeStringSpan(locale.get()), skeleton, timeZoneChars);
   2422 
   2423  if (dif.isErr()) {
   2424    js::intl::ReportInternalError(cx, dif.unwrapErr());
   2425    return nullptr;
   2426  }
   2427 
   2428  return dif.unwrap().release();
   2429 }
   2430 
   2431 static mozilla::intl::DateIntervalFormat* GetOrCreateDateIntervalFormat(
   2432    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
   2433    mozilla::intl::DateTimeFormat& mozDtf, DateTimeValueKind kind) {
   2434  dateTimeFormat->maybeClearCache(kind);
   2435 
   2436  // Obtain a cached DateIntervalFormat object.
   2437  mozilla::intl::DateIntervalFormat* dif =
   2438      dateTimeFormat->getDateIntervalFormat();
   2439  if (dif) {
   2440    return dif;
   2441  }
   2442 
   2443  dif = NewDateIntervalFormat(cx, dateTimeFormat, mozDtf);
   2444  if (!dif) {
   2445    return nullptr;
   2446  }
   2447  dateTimeFormat->setDateIntervalFormat(dif);
   2448 
   2449  intl::AddICUCellMemory(
   2450      dateTimeFormat,
   2451      DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
   2452  return dif;
   2453 }
   2454 
   2455 /**
   2456 * PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
   2457 */
   2458 static bool PartitionDateTimeRangePattern(
   2459    JSContext* cx, const mozilla::intl::DateTimeFormat* df,
   2460    const mozilla::intl::DateIntervalFormat* dif,
   2461    mozilla::intl::AutoFormattedDateInterval& formatted, ClippedTime x,
   2462    ClippedTime y, bool* equal) {
   2463  MOZ_ASSERT(x.isValid());
   2464  MOZ_ASSERT(y.isValid());
   2465 
   2466  auto result =
   2467      dif->TryFormatDateTime(x.toDouble(), y.toDouble(), df, formatted, equal);
   2468  if (result.isErr()) {
   2469    intl::ReportInternalError(cx, result.unwrapErr());
   2470    return false;
   2471  }
   2472  return true;
   2473 }
   2474 
   2475 /**
   2476 * FormatDateTimeRange( dateTimeFormat, x, y )
   2477 */
   2478 static bool FormatDateTimeRange(JSContext* cx,
   2479                                const mozilla::intl::DateTimeFormat* df,
   2480                                const mozilla::intl::DateIntervalFormat* dif,
   2481                                ClippedTime x, ClippedTime y,
   2482                                MutableHandleValue result) {
   2483  mozilla::intl::AutoFormattedDateInterval formatted;
   2484  if (!formatted.IsValid()) {
   2485    intl::ReportInternalError(cx, formatted.GetError());
   2486    return false;
   2487  }
   2488 
   2489  bool equal;
   2490  if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) {
   2491    return false;
   2492  }
   2493 
   2494  // PartitionDateTimeRangePattern, step 12.
   2495  if (equal) {
   2496    return intl_FormatDateTime(cx, df, x, result);
   2497  }
   2498 
   2499  auto spanResult = formatted.ToSpan();
   2500  if (spanResult.isErr()) {
   2501    intl::ReportInternalError(cx, spanResult.unwrapErr());
   2502    return false;
   2503  }
   2504  JSString* resultStr = NewStringCopy<CanGC>(cx, spanResult.unwrap());
   2505  if (!resultStr) {
   2506    return false;
   2507  }
   2508 
   2509  result.setString(resultStr);
   2510  return true;
   2511 }
   2512 
   2513 /**
   2514 * FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
   2515 */
   2516 static bool FormatDateTimeRangeToParts(
   2517    JSContext* cx, const mozilla::intl::DateTimeFormat* df,
   2518    const mozilla::intl::DateIntervalFormat* dif, ClippedTime x, ClippedTime y,
   2519    MutableHandleValue result) {
   2520  mozilla::intl::AutoFormattedDateInterval formatted;
   2521  if (!formatted.IsValid()) {
   2522    intl::ReportInternalError(cx, formatted.GetError());
   2523    return false;
   2524  }
   2525 
   2526  bool equal;
   2527  if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, x, y, &equal)) {
   2528    return false;
   2529  }
   2530 
   2531  // PartitionDateTimeRangePattern, step 12.
   2532  if (equal) {
   2533    return intl_FormatToPartsDateTime(cx, df, x, /* hasNoSource */ false,
   2534                                      result);
   2535  }
   2536 
   2537  mozilla::intl::DateTimePartVector parts;
   2538  auto r = dif->TryFormattedToParts(formatted, parts);
   2539  if (r.isErr()) {
   2540    intl::ReportInternalError(cx, r.unwrapErr());
   2541    return false;
   2542  }
   2543 
   2544  auto spanResult = formatted.ToSpan();
   2545  if (spanResult.isErr()) {
   2546    intl::ReportInternalError(cx, spanResult.unwrapErr());
   2547    return false;
   2548  }
   2549  return CreateDateTimePartArray(cx, spanResult.unwrap(),
   2550                                 /* hasNoSource */ false, parts, result);
   2551 }
   2552 
   2553 bool js::intl_FormatDateTimeRange(JSContext* cx, unsigned argc, Value* vp) {
   2554  CallArgs args = CallArgsFromVp(argc, vp);
   2555  MOZ_ASSERT(args.length() == 4);
   2556  MOZ_ASSERT(args[0].isObject());
   2557  MOZ_ASSERT(!args[1].isUndefined());
   2558  MOZ_ASSERT(!args[2].isUndefined());
   2559  MOZ_ASSERT(args[3].isBoolean());
   2560 
   2561  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
   2562  dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
   2563 
   2564  bool formatToParts = args[3].toBoolean();
   2565  const char* method = formatToParts ? "formatRangeToParts" : "formatRange";
   2566 
   2567  Rooted<Value> start(cx, args[1]);
   2568  auto startKind = ToDateTimeFormattable(start);
   2569  if (startKind == DateTimeValueKind::Number) {
   2570    if (!ToNumber(cx, &start)) {
   2571      return false;
   2572    }
   2573  }
   2574  MOZ_ASSERT(start.isNumber() || start.isObject());
   2575 
   2576  Rooted<Value> end(cx, args[2]);
   2577  auto endKind = ToDateTimeFormattable(end);
   2578  if (endKind == DateTimeValueKind::Number) {
   2579    if (!ToNumber(cx, &end)) {
   2580      return false;
   2581    }
   2582  }
   2583  MOZ_ASSERT(end.isNumber() || end.isObject());
   2584 
   2585  if (startKind != endKind) {
   2586    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   2587                              JSMSG_NOT_EXPECTED_TYPE, method,
   2588                              DateTimeValueKindToString(startKind),
   2589                              DateTimeValueKindToString(endKind));
   2590    return false;
   2591  }
   2592 
   2593  // PartitionDateTimeRangePattern, steps 1-2.
   2594  JS::ClippedTime x;
   2595  if (!HandleDateTimeValue(cx, method, dateTimeFormat, start, &x)) {
   2596    return false;
   2597  }
   2598  MOZ_ASSERT(x.isValid());
   2599 
   2600  // PartitionDateTimeRangePattern, steps 3-4.
   2601  JS::ClippedTime y;
   2602  if (!HandleDateTimeValue(cx, method, dateTimeFormat, end, &y)) {
   2603    return false;
   2604  }
   2605  MOZ_ASSERT(y.isValid());
   2606 
   2607  mozilla::intl::DateTimeFormat* df =
   2608      GetOrCreateDateTimeFormat(cx, dateTimeFormat, startKind);
   2609  if (!df) {
   2610    return false;
   2611  }
   2612 
   2613  mozilla::intl::DateIntervalFormat* dif =
   2614      GetOrCreateDateIntervalFormat(cx, dateTimeFormat, *df, startKind);
   2615  if (!dif) {
   2616    return false;
   2617  }
   2618 
   2619  // Use the DateIntervalFormat to actually format the time range.
   2620  return formatToParts
   2621             ? FormatDateTimeRangeToParts(cx, df, dif, x, y, args.rval())
   2622             : FormatDateTimeRange(cx, df, dif, x, y, args.rval());
   2623 }
   2624 
   2625 /**
   2626 * Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )
   2627 */
   2628 static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
   2629                                              Value* vp) {
   2630  CallArgs args = CallArgsFromVp(argc, vp);
   2631 
   2632  // Steps 1-3.
   2633  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DateTimeFormat,
   2634                                   args.get(0), args.get(1));
   2635  if (!array) {
   2636    return false;
   2637  }
   2638  args.rval().setObject(*array);
   2639  return true;
   2640 }
   2641 
   2642 bool js::intl::TemporalObjectToLocaleString(
   2643    JSContext* cx, const CallArgs& args, DateTimeFormatKind formatKind,
   2644    Handle<Value> toLocaleStringTimeZone) {
   2645  MOZ_ASSERT(args.thisv().isObject());
   2646 
   2647  auto kind = ToDateTimeFormattable(args.thisv());
   2648  MOZ_ASSERT(kind != DateTimeValueKind::Number);
   2649  MOZ_ASSERT_IF(kind != DateTimeValueKind::TemporalZonedDateTime,
   2650                toLocaleStringTimeZone.isUndefined());
   2651  MOZ_ASSERT_IF(kind == DateTimeValueKind::TemporalZonedDateTime,
   2652                toLocaleStringTimeZone.isString());
   2653 
   2654  HandleValue locales = args.get(0);
   2655  HandleValue options = args.get(1);
   2656 
   2657  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
   2658  if (kind != DateTimeValueKind::TemporalZonedDateTime) {
   2659    dateTimeFormat =
   2660        GetOrCreateDateTimeFormat(cx, locales, options, formatKind);
   2661  } else {
   2662    // Cache doesn't yet support Temporal.ZonedDateTime.
   2663    dateTimeFormat = ::CreateDateTimeFormat(cx, locales, options,
   2664                                            toLocaleStringTimeZone, formatKind);
   2665  }
   2666  if (!dateTimeFormat) {
   2667    return false;
   2668  }
   2669 
   2670  JS::ClippedTime x;
   2671  if (kind == DateTimeValueKind::TemporalZonedDateTime) {
   2672    Rooted<ZonedDateTimeObject*> zonedDateTime(
   2673        cx, &args.thisv().toObject().as<ZonedDateTimeObject>());
   2674    if (!HandleDateTimeTemporalZonedDateTime(cx, dateTimeFormat, zonedDateTime,
   2675                                             &x)) {
   2676      return false;
   2677    }
   2678  } else {
   2679    if (!HandleDateTimeValue(cx, "toLocaleString", dateTimeFormat, args.thisv(),
   2680                             &x)) {
   2681      return false;
   2682    }
   2683  }
   2684  MOZ_ASSERT(x.isValid());
   2685 
   2686  auto* df = GetOrCreateDateTimeFormat(cx, dateTimeFormat, kind);
   2687  if (!df) {
   2688    return false;
   2689  }
   2690 
   2691  return intl_FormatDateTime(cx, df, x, args.rval());
   2692 }