Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/builtin/intl/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 111 kB image not shown  

Quelle  DateTimeFormat.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/* Intl.DateTimeFormat implementation. */

#include "builtin/intl/DateTimeFormat.h"

#include "mozilla/Assertions.h"
#include "mozilla/intl/Calendar.h"
#include "mozilla/intl/DateIntervalFormat.h"
#include "mozilla/intl/DateTimeFormat.h"
#include "mozilla/intl/DateTimePart.h"
#include "mozilla/intl/Locale.h"
#include "mozilla/intl/TimeZone.h"
#include "mozilla/Maybe.h"
#include "mozilla/Span.h"
#include "mozilla/UsingEnum.h"

#include "builtin/Array.h"
#include "builtin/Date.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/FormatBuffer.h"
#include "builtin/intl/LanguageTag.h"
#include "builtin/intl/LocaleNegotiation.h"
#include "builtin/intl/Packed.h"
#include "builtin/intl/ParameterNegotiation.h"
#include "builtin/intl/SharedIntlData.h"
#include "builtin/temporal/Calendar.h"
#include "builtin/temporal/Instant.h"
#include "builtin/temporal/PlainDate.h"
#include "builtin/temporal/PlainDateTime.h"
#include "builtin/temporal/PlainMonthDay.h"
#include "builtin/temporal/PlainTime.h"
#include "builtin/temporal/PlainYearMonth.h"
#include "builtin/temporal/Temporal.h"
#include "builtin/temporal/TemporalParser.h"
#include "builtin/temporal/TimeZone.h"
#include "builtin/temporal/ZonedDateTime.h"
#include "gc/GCContext.h"
#include "js/Date.h"
#include "js/experimental/Intl.h"     // JS::AddMozDateTimeFormatConstructor
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/GCAPI.h"
#include "js/PropertyAndElement.h"  // JS_DefineFunctions, JS_DefineProperties
#include "js/PropertySpec.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "vm/DateTime.h"
#include "vm/GlobalObject.h"
#include "vm/JSContext.h"
#include "vm/PlainObject.h"  // js::PlainObject
#include "vm/Runtime.h"
#include "vm/Warnings.h"

#include "vm/GeckoProfiler-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"

using namespace js;
using namespace js::intl;
using namespace js::temporal;

using JS::ClippedTime;

const JSClassOps DateTimeFormatObject::classOps_ = {
    .finalize = DateTimeFormatObject::finalize,
};

const JSClass DateTimeFormatObject::class_ = {
    "Intl.DateTimeFormat",
    JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) |
        JSCLASS_HAS_CACHED_PROTO(JSProto_DateTimeFormat) |
        JSCLASS_BACKGROUND_FINALIZE,
    &DateTimeFormatObject::classOps_,
    &DateTimeFormatObject::classSpec_,
};

const JSClass& DateTimeFormatObject::protoClass_ = PlainObject::class_;

static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
                                              Value* vp);

static bool dateTimeFormat_format(JSContext* cx, unsigned argc, Value* vp);

static bool dateTimeFormat_formatToParts(JSContext* cx, unsigned argc,
                                         Value* vp);

static bool dateTimeFormat_formatRange(JSContext* cx, unsigned argc, Value* vp);

static bool dateTimeFormat_formatRangeToParts(JSContext* cx, unsigned argc,
                                              Value* vp);

static bool dateTimeFormat_resolvedOptions(JSContext* cx, unsigned argc,
                                           Value* vp);

static bool dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  args.rval().setString(cx->names().DateTimeFormat);
  return true;
}

static const JSFunctionSpec dateTimeFormat_static_methods[] = {
    JS_FN("supportedLocalesOf", dateTimeFormat_supportedLocalesOf, 10),
    JS_FS_END,
};

static const JSFunctionSpec dateTimeFormat_methods[] = {
    JS_FN("resolvedOptions", dateTimeFormat_resolvedOptions, 00),
    JS_FN("formatToParts", dateTimeFormat_formatToParts, 10),
    JS_FN("formatRange", dateTimeFormat_formatRange, 20),
    JS_FN("formatRangeToParts", dateTimeFormat_formatRangeToParts, 20),
    JS_FN("toSource", dateTimeFormat_toSource, 00),
    JS_FS_END,
};

static const JSPropertySpec dateTimeFormat_properties[] = {
    JS_PSG("format", dateTimeFormat_format, 0),
    JS_STRING_SYM_PS(toStringTag, "Intl.DateTimeFormat", JSPROP_READONLY),
    JS_PS_END,
};

static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp);

const ClassSpec DateTimeFormatObject::classSpec_ = {
    GenericCreateConstructor<DateTimeFormat, 0, gc::AllocKind::FUNCTION>,
    GenericCreatePrototype<DateTimeFormatObject>,
    dateTimeFormat_static_methods,
    nullptr,
    dateTimeFormat_methods,
    dateTimeFormat_properties,
    nullptr,
    ClassSpec::DontDefineConstructor,
};

struct js::intl::DateTimeFormatOptions {
  enum class Required : int8_t { Any, Date, Time };
  Required required = Required::Any;

  enum class Defaults : int8_t { Date, Time, All };
  Defaults defaults = Defaults::Date;

  using HourCycle = mozilla::intl::DateTimeFormat::HourCycle;
  mozilla::Maybe<HourCycle> hourCycle{};

  mozilla::Maybe<bool> hour12{};

  using DateStyle = mozilla::intl::DateTimeFormat::Style;
  mozilla::Maybe<DateStyle> dateStyle{};

  using TimeStyle = mozilla::intl::DateTimeFormat::Style;
  mozilla::Maybe<TimeStyle> timeStyle{};

  // Components of date and time formats
  //
  // https://tc39.es/ecma402/#table-datetimeformat-components

  using Weekday = mozilla::intl::DateTimeFormat::Text;
  mozilla::Maybe<Weekday> weekday{};

  using Era = mozilla::intl::DateTimeFormat::Text;
  mozilla::Maybe<Era> era{};

  using Year = mozilla::intl::DateTimeFormat::Numeric;
  mozilla::Maybe<Year> year{};

  using Month = mozilla::intl::DateTimeFormat::Month;
  mozilla::Maybe<Month> month{};

  using Day = mozilla::intl::DateTimeFormat::Numeric;
  mozilla::Maybe<Day> day{};

  using DayPeriod = mozilla::intl::DateTimeFormat::Text;
  mozilla::Maybe<DayPeriod> dayPeriod{};

  using Hour = mozilla::intl::DateTimeFormat::Numeric;
  mozilla::Maybe<Hour> hour{};

  using Minute = mozilla::intl::DateTimeFormat::Numeric;
  mozilla::Maybe<Minute> minute{};

  using Second = mozilla::intl::DateTimeFormat::Numeric;
  mozilla::Maybe<Second> second{};

  mozilla::Maybe<int8_t> fractionalSecondDigits{};

  using TimeZoneName = mozilla::intl::DateTimeFormat::TimeZoneName;
  mozilla::Maybe<TimeZoneName> timeZoneName{};
};

struct PackedDateTimeFormatOptions {
  using RawValue = uint64_t;

  using RequiredField =
      packed::EnumField<RawValue, DateTimeFormatOptions::Required::Any,
                        DateTimeFormatOptions::Required::Time>;

  using DefaultsField =
      packed::EnumField<RequiredField, DateTimeFormatOptions::Defaults::Date,
                        DateTimeFormatOptions::Defaults::All>;

  using HourCycleField =
      packed::OptionalEnumField<DefaultsField,
                                DateTimeFormatOptions::HourCycle::H11,
                                DateTimeFormatOptions::HourCycle::H24>;

  using Hour12Field = packed::OptionalBooleanField<HourCycleField>;

  using DateStyleField =
      packed::OptionalEnumField<Hour12Field,
                                DateTimeFormatOptions::DateStyle::Full,
                                DateTimeFormatOptions::DateStyle::Short>;

  using TimeStyleField =
      packed::OptionalEnumField<DateStyleField,
                                DateTimeFormatOptions::TimeStyle::Full,
                                DateTimeFormatOptions::TimeStyle::Short>;

  using WeekdayField =
      packed::OptionalEnumField<TimeStyleField,
                                DateTimeFormatOptions::Weekday::Long,
                                DateTimeFormatOptions::Weekday::Narrow>;

  using EraField =
      packed::OptionalEnumField<WeekdayField, DateTimeFormatOptions::Era::Long,
                                DateTimeFormatOptions::Era::Narrow>;

  using YearField =
      packed::OptionalEnumField<EraField, DateTimeFormatOptions::Year::Numeric,
                                DateTimeFormatOptions::Year::TwoDigit>;

  using MonthField =
      packed::OptionalEnumField<YearField,
                                DateTimeFormatOptions::Month::Numeric,
                                DateTimeFormatOptions::Month::Narrow>;

  using DayField =
      packed::OptionalEnumField<MonthField, DateTimeFormatOptions::Day::Numeric,
                                DateTimeFormatOptions::Day::TwoDigit>;

  using DayPeriodField =
      packed::OptionalEnumField<DayField,
                                DateTimeFormatOptions::DayPeriod::Long,
                                DateTimeFormatOptions::DayPeriod::Narrow>;

  using HourField =
      packed::OptionalEnumField<DayPeriodField,
                                DateTimeFormatOptions::Hour::Numeric,
                                DateTimeFormatOptions::Hour::TwoDigit>;

  using MinuteField =
      packed::OptionalEnumField<HourField,
                                DateTimeFormatOptions::Minute::Numeric,
                                DateTimeFormatOptions::Minute::TwoDigit>;

  using SecondField =
      packed::OptionalEnumField<MinuteField,
                                DateTimeFormatOptions::Second::Numeric,
                                DateTimeFormatOptions::Second::TwoDigit>;

  using FractionalSecondDigitsField =
      packed::RangeField<SecondField, int8_t, 03>;

  using TimeZoneNameField = packed::OptionalEnumField<
      FractionalSecondDigitsField, DateTimeFormatOptions::TimeZoneName::Long,
      DateTimeFormatOptions::TimeZoneName::LongGeneric>;

  using PackedValue = packed::PackedValue<TimeZoneNameField>;

  static auto pack(const DateTimeFormatOptions& options) {
    RawValue rawValue =
        RequiredField::pack(options.required) |
        DefaultsField::pack(options.defaults) |
        HourCycleField::pack(options.hourCycle) |
        Hour12Field::pack(options.hour12) |
        DateStyleField::pack(options.dateStyle) |
        TimeStyleField::pack(options.timeStyle) |
        WeekdayField::pack(options.weekday) | EraField::pack(options.era) |
        YearField::pack(options.year) | MonthField::pack(options.month) |
        DayField::pack(options.day) | DayPeriodField::pack(options.dayPeriod) |
        HourField::pack(options.hour) | MinuteField::pack(options.minute) |
        SecondField::pack(options.second) |
        FractionalSecondDigitsField::pack(
            options.fractionalSecondDigits.valueOr(0)) |
        TimeZoneNameField::pack(options.timeZoneName);
    return PackedValue::toValue(rawValue);
  }

  static auto unpack(JS::Value value) {
    auto maybeFractionalDigits = [](int8_t digits) -> mozilla::Maybe<int8_t> {
      return digits > 0 ? mozilla::Some(digits) : mozilla::Nothing();
    };

    RawValue rawValue = PackedValue::fromValue(value);
    return DateTimeFormatOptions{
        .required = RequiredField::unpack(rawValue),
        .defaults = DefaultsField::unpack(rawValue),
        .hourCycle = HourCycleField::unpack(rawValue),
        .hour12 = Hour12Field::unpack(rawValue),
        .dateStyle = DateStyleField::unpack(rawValue),
        .timeStyle = TimeStyleField::unpack(rawValue),
        .weekday = WeekdayField::unpack(rawValue),
        .era = EraField::unpack(rawValue),
        .year = YearField::unpack(rawValue),
        .month = MonthField::unpack(rawValue),
        .day = DayField::unpack(rawValue),
        .dayPeriod = DayPeriodField::unpack(rawValue),
        .hour = HourField::unpack(rawValue),
        .minute = MinuteField::unpack(rawValue),
        .second = SecondField::unpack(rawValue),
        .fractionalSecondDigits = maybeFractionalDigits(
            FractionalSecondDigitsField::unpack(rawValue)),
        .timeZoneName = TimeZoneNameField::unpack(rawValue),
    };
  }
};

DateTimeFormatOptions js::intl::DateTimeFormatObject::getOptions() const {
  const auto& slot = getFixedSlot(OPTIONS_SLOT);
  if (slot.isUndefined()) {
    return {};
  }
  return PackedDateTimeFormatOptions::unpack(slot);
}

void js::intl::DateTimeFormatObject::setOptions(
    const DateTimeFormatOptions& options) {
  setFixedSlot(OPTIONS_SLOT, PackedDateTimeFormatOptions::pack(options));
}

static constexpr std::string_view HourCycleToString(
    DateTimeFormatOptions::HourCycle hourCycle) {
  MOZ_USING_ENUM(DateTimeFormatOptions::HourCycle, H11, H12, H23, H24);
  switch (hourCycle) {
    case H11:
      return "h11";
    case H12:
      return "h12";
    case H23:
      return "h23";
    case H24:
      return "h24";
  }
  MOZ_CRASH("invalid date time format hour cycle");
}

template <typename DateOrTimeStyle>
static constexpr std::string_view DateOrTimeStyleToString(
    DateOrTimeStyle dateOrTimeStyle) {
  switch (dateOrTimeStyle) {
    case DateOrTimeStyle::Full:
      return "full";
    case DateOrTimeStyle::Long:
      return "long";
    case DateOrTimeStyle::Medium:
      return "medium";
    case DateOrTimeStyle::Short:
      return "short";
  }
  MOZ_CRASH("invalid date time format date or time style");
}

static constexpr std::string_view DateStyleToString(
    DateTimeFormatOptions::DateStyle dateStyle) {
  return DateOrTimeStyleToString(dateStyle);
}

static constexpr std::string_view TimeStyleToString(
    DateTimeFormatOptions::TimeStyle timeStyle) {
  return DateOrTimeStyleToString(timeStyle);
}

template <typename TextComponent>
static constexpr std::string_view TextComponentToString(
    TextComponent textComponent) {
  switch (textComponent) {
    case TextComponent::Narrow:
      return "narrow";
    case TextComponent::Short:
      return "short";
    case TextComponent::Long:
      return "long";
  }
  MOZ_CRASH("invalid date time format text component");
}

template <typename NumericComponent>
static constexpr std::string_view NumericComponentToString(
    NumericComponent numericComponent) {
  switch (numericComponent) {
    case NumericComponent::TwoDigit:
      return "2-digit";
    case NumericComponent::Numeric:
      return "numeric";
  }
  MOZ_CRASH("invalid date time format numeric component");
}

static constexpr std::string_view WeekdayToString(
    DateTimeFormatOptions::Weekday weekday) {
  return TextComponentToString(weekday);
}

static constexpr std::string_view EraToString(DateTimeFormatOptions::Era era) {
  return TextComponentToString(era);
}

static constexpr std::string_view YearToString(
    DateTimeFormatOptions::Year year) {
  return NumericComponentToString(year);
}

static constexpr std::string_view MonthToString(
    DateTimeFormatOptions::Month month) {
  MOZ_USING_ENUM(DateTimeFormatOptions::Month, TwoDigit, Numeric, Narrow, Short,
                 Long);
  switch (month) {
    case TwoDigit:
      return "2-digit";
    case Numeric:
      return "numeric";
    case Narrow:
      return "narrow";
    case Short:
      return "short";
    case Long:
      return "long";
  }
  MOZ_CRASH("invalid date time format month");
}

static constexpr std::string_view DayToString(DateTimeFormatOptions::Day day) {
  return NumericComponentToString(day);
}

static constexpr std::string_view DayPeriodToString(
    DateTimeFormatOptions::DayPeriod dayPeriod) {
  return TextComponentToString(dayPeriod);
}

static constexpr std::string_view HourToString(
    DateTimeFormatOptions::Hour hour) {
  return NumericComponentToString(hour);
}

static constexpr std::string_view MinuteToString(
    DateTimeFormatOptions::Minute minute) {
  return NumericComponentToString(minute);
}

static constexpr std::string_view SecondToString(
    DateTimeFormatOptions::Second second) {
  return NumericComponentToString(second);
}

static constexpr std::string_view TimeZoneNameToString(
    DateTimeFormatOptions::TimeZoneName timeZoneName) {
  MOZ_USING_ENUM(DateTimeFormatOptions::TimeZoneName, ShortLong, ShortOffset,
                 LongOffset, ShortGeneric, LongGeneric);
  switch (timeZoneName) {
    case Short:
      return "short";
    case Long:
      return "long";
    case ShortOffset:
      return "shortOffset";
    case LongOffset:
      return "longOffset";
    case ShortGeneric:
      return "shortGeneric";
    case LongGeneric:
      return "longGeneric";
  }
  MOZ_CRASH("invalid date time format time zone name");
}

enum class FormatMatcher { Basic, BestFit };

static constexpr std::string_view FormatMatcherToString(
    FormatMatcher formatMatcher) {
  MOZ_USING_ENUM(FormatMatcher, Basic, BestFit);
  switch (formatMatcher) {
    case Basic:
      return "basic";
    case BestFit:
      return "best fit";
  }
  MOZ_CRASH("invalid format matcher");
}

enum class DateTimeFormatConstructorKind {
  Standard,
  EnableMozExtensions,
};

/**
 * CreateDateTimeFormat ( newTarget, locales, options, required, defaults [ ,
 * toLocaleStringTimeZone ] )
 */

static bool CreateDateTimeFormat(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    Handle<JS::Value> locales, Handle<JS::Value> optionsValue,
    DateTimeFormatOptions::Required required,
    DateTimeFormatOptions::Defaults defaults,
    Handle<JSLinearString*> toLocaleStringTimeZone = nullptr,
    DateTimeFormatConstructorKind dtfKind =
        DateTimeFormatConstructorKind::Standard) {
  // Step 1. (Performed in caller)

  // Steps 2-4. (Inlined ResolveOptions)

  // ResolveOptions, step 1.
  auto* requestedLocales = CanonicalizeLocaleList(cx, locales);
  if (!requestedLocales) {
    return false;
  }
  dateTimeFormat->setRequestedLocales(requestedLocales);

  auto dtfOptions = DateTimeFormatOptions{
      .required = required,
      .defaults = defaults,
  };

  if (!optionsValue.isUndefined()) {
    // ResolveOptions, steps 2-3.
    Rooted<JSObject*> options(cx, JS::ToObject(cx, optionsValue));
    if (!options) {
      return false;
    }

    // ResolveOptions, step 4.
    LocaleMatcher matcher;
    if (!GetLocaleMatcherOption(cx, options, &matcher)) {
      return false;
    }

    // ResolveOptions, step 5.
    //
    // This implementation only supports the "lookup" locale matcher, therefore
    // the "localeMatcher" option doesn't need to be stored.

    // ResolveOptions, step 6.
    Rooted<JSLinearString*> calendar(cx);
    if (!GetUnicodeExtensionOption(cx, options, UnicodeExtensionKey::Calendar,
                                   &calendar)) {
      return false;
    }
    if (calendar) {
      dateTimeFormat->setCalendar(calendar);
    }

    Rooted<JSLinearString*> numberingSystem(cx);
    if (!GetUnicodeExtensionOption(cx, options,
                                   UnicodeExtensionKey::NumberingSystem,
                                   &numberingSystem)) {
      return false;
    }
    if (numberingSystem) {
      dateTimeFormat->setNumberingSystem(numberingSystem);
    }

    if (!GetBooleanOption(cx, options, cx->names().hour12,
                          &dtfOptions.hour12)) {
      return false;
    }

    static constexpr auto hourCycles =
        MapOptions<HourCycleToString>(DateTimeFormatOptions::HourCycle::H11,
                                      DateTimeFormatOptions::HourCycle::H12,
                                      DateTimeFormatOptions::HourCycle::H23,
                                      DateTimeFormatOptions::HourCycle::H24);
    if (!GetStringOption(cx, options, cx->names().hourCycle, hourCycles,
                         &dtfOptions.hourCycle)) {
      return false;
    }

    // ResolveOptions, step 7.
    if (dtfOptions.hour12.isSome()) {
      // The "hourCycle" option is ignored if "hour12" is also present.
      dtfOptions.hourCycle = mozilla::Nothing();
    }

    // ResolveOptions, step 8. (Performed in ResolveLocale)

    // ResolveOptions, step 9. (Return)

    // Step 5. (Not applicable when ResolveOptions is inlined.)

    // Step 6-14. (Performed in ResolveLocale)

    // Step 15.
    Rooted<JS::Value> timeZoneOption(cx);
    if (!GetProperty(cx, options, options, cx->names().timeZone,
                     &timeZoneOption)) {
      return false;
    }

    // Steps 16-19.
    JSLinearString* timeZone = nullptr;
    if (timeZoneOption.isUndefined()) {
      // Step 16.
      if (toLocaleStringTimeZone) {
        timeZone = toLocaleStringTimeZone;
      } else {
        timeZone = cx->global()->globalIntlData().defaultTimeZone(cx);
        if (!timeZone) {
          return false;
        }
      }

      // Steps 18-19.  (Not applicable in our implementation.)
    } else {
      // Step 17.
      if (toLocaleStringTimeZone) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_INVALID_DATETIME_OPTION, "timeZone",
                                  "Temporal.ZonedDateTime.toLocaleString");
        return false;
      }

      Rooted<JSString*> timeZoneStr(cx, JS::ToString(cx, timeZoneOption));
      if (!timeZoneStr) {
        return false;
      }

      // Steps 18-19.
      timeZone = temporal::ToValidCanonicalTimeZoneIdentifier(cx, timeZoneStr);
      if (!timeZone) {
        return false;
      }
    }

    // Step 20.
    dateTimeFormat->setTimeZone(timeZone);

    if (dtfKind == DateTimeFormatConstructorKind::EnableMozExtensions) {
      Rooted<JS::Value> patternOption(cx);
      if (!GetProperty(cx, options, options, cx->names().pattern,
                       &patternOption)) {
        return false;
      }

      if (!patternOption.isUndefined()) {
        auto* pattern = JS::ToString(cx, patternOption);
        if (!pattern) {
          return false;
        }
        dateTimeFormat->setPattern(pattern);
      }
    }

    // Steps 21-22. (Not applicable in our implementation.)

    // Step 23. (Moved below)

    // Step 24.
    static constexpr auto weekdays =
        MapOptions<WeekdayToString>(DateTimeFormatOptions::Weekday::Narrow,
                                    DateTimeFormatOptions::Weekday::Short,
                                    DateTimeFormatOptions::Weekday::Long);
    if (!GetStringOption(cx, options, cx->names().weekday, weekdays,
                         &dtfOptions.weekday)) {
      return false;
    }

    static constexpr auto eras = MapOptions<EraToString>(
        DateTimeFormatOptions::Era::Narrow, DateTimeFormatOptions::Era::Short,
        DateTimeFormatOptions::Era::Long);
    if (!GetStringOption(cx, options, cx->names().era, eras, &dtfOptions.era)) {
      return false;
    }

    static constexpr auto years =
        MapOptions<YearToString>(DateTimeFormatOptions::Year::TwoDigit,
                                 DateTimeFormatOptions::Year::Numeric);
    if (!GetStringOption(cx, options, cx->names().year, years,
                         &dtfOptions.year)) {
      return false;
    }

    static constexpr auto months =
        MapOptions<MonthToString>(DateTimeFormatOptions::Month::TwoDigit,
                                  DateTimeFormatOptions::Month::Numeric,
                                  DateTimeFormatOptions::Month::Narrow,
                                  DateTimeFormatOptions::Month::Short,
                                  DateTimeFormatOptions::Month::Long);
    if (!GetStringOption(cx, options, cx->names().month, months,
                         &dtfOptions.month)) {
      return false;
    }

    static constexpr auto days =
        MapOptions<DayToString>(DateTimeFormatOptions::Day::TwoDigit,
                                DateTimeFormatOptions::Day::Numeric);
    if (!GetStringOption(cx, options, cx->names().day, days, &dtfOptions.day)) {
      return false;
    }

    static constexpr auto dayPeriods =
        MapOptions<DayPeriodToString>(DateTimeFormatOptions::DayPeriod::Narrow,
                                      DateTimeFormatOptions::DayPeriod::Short,
                                      DateTimeFormatOptions::DayPeriod::Long);
    if (!GetStringOption(cx, options, cx->names().dayPeriod, dayPeriods,
                         &dtfOptions.dayPeriod)) {
      return false;
    }

    static constexpr auto hours =
        MapOptions<HourToString>(DateTimeFormatOptions::Hour::TwoDigit,
                                 DateTimeFormatOptions::Hour::Numeric);
    if (!GetStringOption(cx, options, cx->names().hour, hours,
                         &dtfOptions.hour)) {
      return false;
    }

    static constexpr auto minutes =
        MapOptions<MinuteToString>(DateTimeFormatOptions::Minute::TwoDigit,
                                   DateTimeFormatOptions::Minute::Numeric);
    if (!GetStringOption(cx, options, cx->names().minute, minutes,
                         &dtfOptions.minute)) {
      return false;
    }

    static constexpr auto seconds =
        MapOptions<SecondToString>(DateTimeFormatOptions::Second::TwoDigit,
                                   DateTimeFormatOptions::Second::Numeric);
    if (!GetStringOption(cx, options, cx->names().second, seconds,
                         &dtfOptions.second)) {
      return false;
    }

    mozilla::Maybe<int32_t> fractionalSecondDigits{};
    if (!GetNumberOption(cx, options, cx->names().fractionalSecondDigits, 13,
                         &fractionalSecondDigits)) {
      return false;
    }
    dtfOptions.fractionalSecondDigits = fractionalSecondDigits;

    static constexpr auto timeZoneNames = MapOptions<TimeZoneNameToString>(
        DateTimeFormatOptions::TimeZoneName::Short,
        DateTimeFormatOptions::TimeZoneName::Long,
        DateTimeFormatOptions::TimeZoneName::ShortOffset,
        DateTimeFormatOptions::TimeZoneName::LongOffset,
        DateTimeFormatOptions::TimeZoneName::ShortGeneric,
        DateTimeFormatOptions::TimeZoneName::LongGeneric);
    if (!GetStringOption(cx, options, cx->names().timeZoneName, timeZoneNames,
                         &dtfOptions.timeZoneName)) {
      return false;
    }

    // Step 25.
    //
    // This implementation only supports the "best fit" format matcher,
    // therefore the "formatMatcher" option doesn't need to be stored.
    //
    // See bug 852837.
    static constexpr auto formatMatchers = MapOptions<FormatMatcherToString>(
        FormatMatcher::Basic, FormatMatcher::BestFit);
    FormatMatcher formatMatcher;
    if (!GetStringOption(cx, options, cx->names().formatMatcher, formatMatchers,
                         FormatMatcher::BestFit, &formatMatcher)) {
      return false;
    }

    // Steps 26-27.
    static constexpr auto dateStyles =
        MapOptions<DateStyleToString>(DateTimeFormatOptions::DateStyle::Full,
                                      DateTimeFormatOptions::DateStyle::Long,
                                      DateTimeFormatOptions::DateStyle::Medium,
                                      DateTimeFormatOptions::DateStyle::Short);
    if (!GetStringOption(cx, options, cx->names().dateStyle, dateStyles,
                         &dtfOptions.dateStyle)) {
      return false;
    }

    // Steps 28-29.
    static constexpr auto timeStyles =
        MapOptions<TimeStyleToString>(DateTimeFormatOptions::TimeStyle::Full,
                                      DateTimeFormatOptions::TimeStyle::Long,
                                      DateTimeFormatOptions::TimeStyle::Medium,
                                      DateTimeFormatOptions::TimeStyle::Short);
    if (!GetStringOption(cx, options, cx->names().timeStyle, timeStyles,
                         &dtfOptions.timeStyle)) {
      return false;
    }

    // Step 30.
    if (dtfOptions.dateStyle.isSome() || dtfOptions.timeStyle.isSome()) {
      // Step 23.
      const char* explicitFormatComponent = ([&]() -> const char* {
        if (dtfOptions.weekday.isSome()) {
          return "weekday";
        }
        if (dtfOptions.era.isSome()) {
          return "era";
        }
        if (dtfOptions.year.isSome()) {
          return "year";
        }
        if (dtfOptions.month.isSome()) {
          return "month";
        }
        if (dtfOptions.day.isSome()) {
          return "day";
        }
        if (dtfOptions.dayPeriod.isSome()) {
          return "dayPeriod";
        }
        if (dtfOptions.hour.isSome()) {
          return "hour";
        }
        if (dtfOptions.minute.isSome()) {
          return "minute";
        }
        if (dtfOptions.second.isSome()) {
          return "second";
        }
        if (dtfOptions.fractionalSecondDigits.isSome()) {
          return "fractionalSecondDigits";
        }
        if (dtfOptions.timeZoneName.isSome()) {
          return "timeZoneName";
        }
        return nullptr;
      })();

      // Step 30.a.
      if (explicitFormatComponent) {
        JS_ReportErrorNumberASCII(
            cx, GetErrorMessage, nullptr, JSMSG_INVALID_DATETIME_OPTION,
            explicitFormatComponent,
            dtfOptions.dateStyle.isSome() ? "dateStyle" : "timeStyle");
        return false;
      }

      // Step 30.b.
      if (required == DateTimeFormatOptions::Required::Date &&
          dtfOptions.timeStyle.isSome()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_INVALID_DATETIME_STYLE, "timeStyle",
                                  "date");
        return false;
      }

      // Step 30.c.
      if (required == DateTimeFormatOptions::Required::Time &&
          dtfOptions.dateStyle.isSome()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_INVALID_DATETIME_STYLE, "dateStyle",
                                  "time");
        return false;
      }
    }

    // Steps 31-33. (See ResolveDateTimeFormat)
  } else {
    // Absent |options| object only requires to store the current time zone.

    // Step 16.
    JSLinearString* timeZone;
    if (toLocaleStringTimeZone) {
      timeZone = toLocaleStringTimeZone;
    } else {
      timeZone = cx->global()->globalIntlData().defaultTimeZone(cx);
      if (!timeZone) {
        return false;
      }
    }

    // Step 20.
    dateTimeFormat->setTimeZone(timeZone);
  }
  dateTimeFormat->setOptions(dtfOptions);

  // Step 34.
  return true;
}

/**
 * 12.2.1 Intl.DateTimeFormat([ locales [, options]])
 *
 * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
 */

static bool DateTimeFormat(JSContext* cx, const CallArgs& args,
                           DateTimeFormatOptions::Required required,
                           DateTimeFormatOptions::Defaults defaults,
                           DateTimeFormatConstructorKind dtfKind) {
  AutoJSConstructorProfilerEntry pseudoFrame(cx, "Intl.DateTimeFormat");

  // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).

  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
  JSProtoKey protoKey = dtfKind == DateTimeFormatConstructorKind::Standard
                            ? JSProto_DateTimeFormat
                            : JSProto_Null;
  Rooted<JSObject*> proto(cx);
  if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) {
    return false;
  }

  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
  dateTimeFormat = NewObjectWithClassProto<DateTimeFormatObject>(cx, proto);
  if (!dateTimeFormat) {
    return false;
  }

  // Step 2.
  if (!CreateDateTimeFormat(cx, dateTimeFormat, args.get(0), args.get(1),
                            required, defaults, nullptr, dtfKind)) {
    return false;
  }

  // Step 3.
  if (dtfKind == DateTimeFormatConstructorKind::Standard) {
    return ChainLegacyIntlFormat(cx, JSProto_DateTimeFormat, args,
                                 dateTimeFormat);
  }

  // Step 4.
  args.rval().setObject(*dateTimeFormat);
  return true;
}

static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  auto required = DateTimeFormatOptions::Required::Any;
  auto defaults = DateTimeFormatOptions::Defaults::Date;
  return DateTimeFormat(cx, args, required, defaults,
                        DateTimeFormatConstructorKind::Standard);
}

static bool MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // Don't allow to call mozIntl.DateTimeFormat as a function. That way we
  // don't need to worry how to handle the legacy initialization semantics
  // when applied on mozIntl.DateTimeFormat.
  if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat")) {
    return false;
  }

  auto required = DateTimeFormatOptions::Required::Any;
  auto defaults = DateTimeFormatOptions::Defaults::Date;
  return DateTimeFormat(cx, args, required, defaults,
                        DateTimeFormatConstructorKind::EnableMozExtensions);
}

static auto ToRequired(DateTimeFormatKind kind) {
  MOZ_USING_ENUM(DateTimeFormatOptions::Required, Any, Date, Time);
  switch (kind) {
    case DateTimeFormatKind::All:
      return Any;
    case DateTimeFormatKind::Date:
      return Date;
    case DateTimeFormatKind::Time:
      return Time;
  }
  MOZ_CRASH("invalid date time format kind");
}

static auto ToDefaults(DateTimeFormatKind kind) {
  MOZ_USING_ENUM(DateTimeFormatOptions::Defaults, All, Date, Time);
  switch (kind) {
    case DateTimeFormatKind::All:
      return All;
    case DateTimeFormatKind::Date:
      return Date;
    case DateTimeFormatKind::Time:
      return Time;
  }
  MOZ_CRASH("invalid date time format kind");
}

static DateTimeFormatObject* CreateDateTimeFormat(
    JSContext* cx, Handle<Value> locales, Handle<Value> options,
    Handle<JSLinearString*> toLocaleStringTimeZone, DateTimeFormatKind kind) {
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, NewBuiltinClassInstance<DateTimeFormatObject>(cx));
  if (!dateTimeFormat) {
    return nullptr;
  }

  auto required = ToRequired(kind);
  auto defaults = ToDefaults(kind);

  if (!CreateDateTimeFormat(cx, dateTimeFormat, locales, options, required,
                            defaults, toLocaleStringTimeZone,
                            DateTimeFormatConstructorKind::Standard)) {
    return nullptr;
  }

  return dateTimeFormat;
}

DateTimeFormatObject* js::intl::CreateDateTimeFormat(JSContext* cx,
                                                     Handle<Value> locales,
                                                     Handle<Value> options,
                                                     DateTimeFormatKind kind) {
  return CreateDateTimeFormat(cx, locales, options, nullptr, kind);
}

DateTimeFormatObject* js::intl::GetOrCreateDateTimeFormat(
    JSContext* cx, Handle<Value> locales, Handle<Value> options,
    DateTimeFormatKind kind) {
  // Try to use a cached instance when |locales| is either undefined or a
  // string, and |options| is undefined.
  if ((locales.isUndefined() || locales.isString()) && options.isUndefined()) {
    Rooted<JSLinearString*> locale(cx);
    if (locales.isString()) {
      locale = locales.toString()->ensureLinear(cx);
      if (!locale) {
        return nullptr;
      }
    }
    return cx->global()->globalIntlData().getOrCreateDateTimeFormat(cx, kind,
                                                                    locale);
  }

  // Create a new Intl.DateTimeFormat instance.
  return CreateDateTimeFormat(cx, locales, options, nullptr, kind);
}

void js::intl::DateTimeFormatObject::finalize(JS::GCContext* gcx,
                                              JSObject* obj) {
  auto* dateTimeFormat = &obj->as<DateTimeFormatObject>();
  auto* df = dateTimeFormat->getDateFormat();
  auto* dif = dateTimeFormat->getDateIntervalFormat();

  if (df) {
    RemoveICUCellMemory(gcx, obj,
                        DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
    delete df;
  }

  if (dif) {
    RemoveICUCellMemory(
        gcx, obj, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
    delete dif;
  }
}

bool JS::AddMozDateTimeFormatConstructor(JSContext* cx,
                                         JS::Handle<JSObject*> intl) {
  Rooted<JSObject*> ctor(
      cx, GlobalObject::createConstructor(cx, MozDateTimeFormat,
                                          cx->names().DateTimeFormat, 0));
  if (!ctor) {
    return false;
  }

  Rooted<JSObject*> proto(
      cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()));
  if (!proto) {
    return false;
  }

  if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
    return false;
  }

  // 12.3.2
  if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) {
    return false;
  }

  // 12.4.4 and 12.4.5
  if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) {
    return false;
  }

  // 12.4.2 and 12.4.3
  if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) {
    return false;
  }

  Rooted<JS::Value> ctorValue(cx, ObjectValue(*ctor));
  return DefineDataProperty(cx, intl, cx->names().DateTimeFormat, ctorValue, 0);
}

/**
 * Resolve the actual locale to finish initialization of the DateTimeFormat.
 */

static bool ResolveLocale(JSContext* cx,
                          Handle<DateTimeFormatObject*> dateTimeFormat) {
  // Return if the locale was already resolved.
  if (dateTimeFormat->isLocaleResolved()) {
    return true;
  }
  auto dtfOptions = dateTimeFormat->getOptions();

  Rooted<ArrayObject*> requestedLocales(
      cx, &dateTimeFormat->getRequestedLocales()->as<ArrayObject>());

  // %Intl.DateTimeFormat%.[[RelevantExtensionKeys]] is « "ca", "hc", "nu" ».
  mozilla::EnumSet<UnicodeExtensionKey> relevantExtensionKeys{
      UnicodeExtensionKey::Calendar,
      UnicodeExtensionKey::HourCycle,
      UnicodeExtensionKey::NumberingSystem,
  };

  // Initialize locale options from constructor arguments.
  Rooted<LocaleOptions> localeOptions(cx);
  if (auto* ca = dateTimeFormat->getCalendar()) {
    localeOptions.setUnicodeExtension(UnicodeExtensionKey::Calendar, ca);
  }
  if (dtfOptions.hour12.isSome()) {
    // Explicitly opt-out of hourCycle if the hour12 option is present, because
    // the latter takes precedence over hourCycle.
    localeOptions.setUnicodeExtension(UnicodeExtensionKey::HourCycle, nullptr);
  } else {
    if (auto hourCycle = dtfOptions.hourCycle) {
      MOZ_USING_ENUM(DateTimeFormatOptions::HourCycle, H11, H12, H23, H24);

      JSLinearString* hc;
      switch (*hourCycle) {
        case H11:
          hc = cx->names().h11;
          break;
        case H12:
          hc = cx->names().h12;
          break;
        case H23:
          hc = cx->names().h23;
          break;
        case H24:
          hc = cx->names().h24;
          break;
      }
      localeOptions.setUnicodeExtension(UnicodeExtensionKey::HourCycle, hc);
    }
  }
  if (auto* nu = dateTimeFormat->getNumberingSystem()) {
    localeOptions.setUnicodeExtension(UnicodeExtensionKey::NumberingSystem, nu);
  }

  // Use the default locale data.
  auto localeData = LocaleData::Default;

  // Resolve the actual locale.
  Rooted<ResolvedLocale> resolved(cx);
  if (!ResolveLocale(cx, AvailableLocaleKind::DateTimeFormat, requestedLocales,
                     localeOptions, relevantExtensionKeys, localeData,
                     &resolved)) {
    return false;
  }

  // Finish initialization by setting the actual locale and extensions.

  // Changes from "Intl era and monthCode" proposal.
  //
  // https://tc39.es/proposal-intl-era-monthcode/#sec-createdatetimeformat
  if (auto ca = resolved.extension(UnicodeExtensionKey::Calendar)) {
    if (StringEqualsLiteral(ca, "islamic")) {
      if (!WarnNumberASCII(cx, JSMSG_ISLAMIC_FALLBACK)) {
        return false;
      }

      // Fallback to "islamic-tbla" calendar.
      auto* str = NewStringCopyZ<CanGC>(cx, "islamic-tbla");
      if (!str) {
        return false;
      }
      dateTimeFormat->setCalendar(str);
    } else {
      dateTimeFormat->setCalendar(ca);
    }
  } else {
    dateTimeFormat->setCalendar(cx->names().default_);
  }

  auto hc = resolved.extension(UnicodeExtensionKey::HourCycle);
  if (hc) {
    MOZ_ASSERT(dtfOptions.hour12.isNothing());
    MOZ_USING_ENUM(DateTimeFormatOptions::HourCycle, H11, H12, H23, H24);
    if (StringEqualsLiteral(hc, "h11")) {
      dtfOptions.hourCycle = mozilla::Some(H11);
    } else if (StringEqualsLiteral(hc, "h12")) {
      dtfOptions.hourCycle = mozilla::Some(H12);
    } else if (StringEqualsLiteral(hc, "h23")) {
      dtfOptions.hourCycle = mozilla::Some(H23);
    } else {
      MOZ_ASSERT(StringEqualsLiteral(hc, "h24"));
      dtfOptions.hourCycle = mozilla::Some(H24);
    }
  } else {
    // The first element of [[LocaleData]].[[<locale>]].[[hc]] is |null|, so
    // hour-cycle is left unset if no explicit option was present.
  }

  if (auto nu = resolved.extension(UnicodeExtensionKey::NumberingSystem)) {
    dateTimeFormat->setNumberingSystem(nu);
  } else {
    dateTimeFormat->setNumberingSystem(cx->names().default_);
  }

  auto* locale = resolved.toLocale(cx);
  if (!locale) {
    return false;
  }
  dateTimeFormat->setLocale(locale);

  // Set the resolved options.
  dateTimeFormat->setOptions(dtfOptions);

  MOZ_ASSERT(dateTimeFormat->isLocaleResolved(),
             "locale successfully resolved");
  return true;
}

static JSLinearString* ResolveCalendar(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) {
  MOZ_ASSERT(dateTimeFormat->isLocaleResolved());

  auto* calendar = dateTimeFormat->getCalendar();
  if (calendar == cx->names().default_) {
    calendar = DefaultCalendar(cx, dateTimeFormat->getLocale());
    if (!calendar) {
      return nullptr;
    }
    dateTimeFormat->setCalendar(calendar);
  }
  return calendar;
}

static JSLinearString* ResolveNumberingSystem(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) {
  MOZ_ASSERT(dateTimeFormat->isLocaleResolved());

  auto* numberingSystem = dateTimeFormat->getNumberingSystem();
  if (numberingSystem == cx->names().default_) {
    numberingSystem = DefaultNumberingSystem(cx, dateTimeFormat->getLocale());
    if (!numberingSystem) {
      return nullptr;
    }
    dateTimeFormat->setNumberingSystem(numberingSystem);
  }
  return numberingSystem;
}

enum class HourCycle {
  // 12 hour cycle, from 0 to 11.
  H11,

  // 12 hour cycle, from 1 to 12.
  H12,

  // 24 hour cycle, from 0 to 23.
  H23,

  // 24 hour cycle, from 1 to 24.
  H24
};

static UniqueChars DateTimeFormatLocale(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hourCycle =
        mozilla::Nothing()) {
  MOZ_ASSERT(dateTimeFormat->isLocaleResolved());

  // ICU expects calendar, numberingSystem, and hourCycle as Unicode locale
  // extensions on locale.
  //
  // We don't add any Unicode extension keywords when the default values can be
  // used, because ICU optimizes for this case.

  JS::RootedVector<UnicodeExtensionKeyword> keywords(cx);

  auto* calendar = dateTimeFormat->getCalendar();
  if (calendar != cx->names().default_) {
    if (!keywords.emplaceBack("ca", calendar)) {
      return nullptr;
    }
  }

  auto* numberingSystem = dateTimeFormat->getNumberingSystem();
  if (numberingSystem != cx->names().default_) {
    if (!keywords.emplaceBack("nu", numberingSystem)) {
      return nullptr;
    }
  }

  if (hourCycle) {
    MOZ_USING_ENUM(mozilla::intl::DateTimeFormat::HourCycle, H11, H12, H23,
                   H24);

    JSAtom* hourCycleStr;
    switch (*hourCycle) {
      case H11:
        hourCycleStr = cx->names().h11;
        break;
      case H12:
        hourCycleStr = cx->names().h12;
        break;
      case H23:
        hourCycleStr = cx->names().h23;
        break;
      case H24:
        hourCycleStr = cx->names().h24;
        break;
    }

    if (!keywords.emplaceBack("hc", hourCycleStr)) {
      return nullptr;
    }
  }

  Rooted<JSLinearString*> localeStr(cx, dateTimeFormat->getLocale());
  return FormatLocale(cx, localeStr, keywords);
}

enum class Required { Date, Time, YearMonth, MonthDay, Any };

enum class Defaults { Date, Time, YearMonth, MonthDay, ZonedDateTime, All };

enum class Inherit { All, Relevant };

struct DateTimeFormatArgs {
  Required required;
  Defaults defaults;
  Inherit inherit;
};

/**
 * Get the "required" argument passed to CreateDateTimeFormat.
 */

static auto GetRequired(DateTimeFormatOptions::Required required) {
  MOZ_USING_ENUM(Required, Date, Time, Any);
  switch (required) {
    case DateTimeFormatOptions::Required::Date:
      return Date;
    case DateTimeFormatOptions::Required::Time:
      return Time;
    case DateTimeFormatOptions::Required::Any:
      return Any;
  }
  MOZ_CRASH("invalid date time format required");
}

/**
 * Get the "defaults" argument passed to CreateDateTimeFormat.
 */

static auto GetDefaults(DateTimeFormatOptions::Defaults defaults) {
  MOZ_USING_ENUM(Defaults, Date, Time, All);
  switch (defaults) {
    case DateTimeFormatOptions::Defaults::Date:
      return Date;
    case DateTimeFormatOptions::Defaults::Time:
      return Time;
    case DateTimeFormatOptions::Defaults::All:
      return All;
  }
  MOZ_CRASH("invalid date time format defaults");
}

/**
 * Compute the (required, defaults, inherit) arguments passed to
 * GetDateTimeFormat.
 */

static DateTimeFormatArgs GetDateTimeFormatArgs(
    const DateTimeFormatOptions& options, DateTimeValueKind kind) {
  switch (kind) {
    case DateTimeValueKind::Number:
      return {GetRequired(options.required), GetDefaults(options.defaults),
              Inherit::All};
    case DateTimeValueKind::TemporalDate:
      return {Required::Date, Defaults::Date, Inherit::Relevant};
    case DateTimeValueKind::TemporalTime:
      return {Required::Time, Defaults::Time, Inherit::Relevant};
    case DateTimeValueKind::TemporalDateTime:
      return {Required::Any, Defaults::All, Inherit::Relevant};
    case DateTimeValueKind::TemporalYearMonth:
      return {Required::YearMonth, Defaults::YearMonth, Inherit::Relevant};
    case DateTimeValueKind::TemporalMonthDay:
      return {Required::MonthDay, Defaults::MonthDay, Inherit::Relevant};
    case DateTimeValueKind::TemporalZonedDateTime:
      return {Required::Any, Defaults::ZonedDateTime, Inherit::All};
    case DateTimeValueKind::TemporalInstant:
      return {Required::Any, Defaults::All, Inherit::All};
  }
  MOZ_CRASH("invalid date-time value kind");
}

enum class DateTimeField {
  Weekday,
  Era,
  Year,
  Month,
  Day,
  DayPeriod,
  Hour,
  Minute,
  Second,
  FractionalSecondDigits,
};

/**
 * GetDateTimeFormat ( formats, matcher, options, required, defaults, inherit )
 *
 * https://tc39.es/proposal-temporal/#sec-getdatetimeformat
 */

static mozilla::Maybe<mozilla::intl::DateTimeFormat::ComponentsBag>
GetDateTimeFormat(const mozilla::intl::DateTimeFormat::ComponentsBag& options,
                  Required required, Defaults defaults, Inherit inherit) {
  // Steps 1-5.
  mozilla::EnumSet<DateTimeField> requiredOptions;
  switch (required) {
    case Required::Date:
      requiredOptions = {
          DateTimeField::Weekday,
          DateTimeField::Year,
          DateTimeField::Month,
          DateTimeField::Day,
      };
      break;
    case Required::Time:
      requiredOptions = {
          DateTimeField::DayPeriod,
          DateTimeField::Hour,
          DateTimeField::Minute,
          DateTimeField::Second,
          DateTimeField::FractionalSecondDigits,
      };
      break;
    case Required::YearMonth:
      requiredOptions = {
          DateTimeField::Year,
          DateTimeField::Month,
      };
      break;
    case Required::MonthDay:
      requiredOptions = {
          DateTimeField::Month,
          DateTimeField::Day,
      };
      break;
    case Required::Any:
      requiredOptions = {
          DateTimeField::Weekday,
          DateTimeField::Year,
          DateTimeField::Month,
          DateTimeField::Day,
          DateTimeField::DayPeriod,
          DateTimeField::Hour,
          DateTimeField::Minute,
          DateTimeField::Second,
          DateTimeField::FractionalSecondDigits,
      };
      break;
  }
  MOZ_ASSERT(!requiredOptions.contains(DateTimeField::Era),
             "standalone era not supported");

  // Steps 6-10.
  mozilla::EnumSet<DateTimeField> defaultOptions;
  switch (defaults) {
    case Defaults::Date:
      defaultOptions = {
          DateTimeField::Year,
          DateTimeField::Month,
          DateTimeField::Day,
      };
      break;
    case Defaults::Time:
      defaultOptions = {
          DateTimeField::Hour,
          DateTimeField::Minute,
          DateTimeField::Second,
      };
      break;
    case Defaults::YearMonth:
      defaultOptions = {
          DateTimeField::Year,
          DateTimeField::Month,
      };
      break;
    case Defaults::MonthDay:
      defaultOptions = {
          DateTimeField::Month,
          DateTimeField::Day,
      };
      break;
    case Defaults::ZonedDateTime:
    case Defaults::All:
      defaultOptions = {
          DateTimeField::Year, DateTimeField::Month,  DateTimeField::Day,
          DateTimeField::Hour, DateTimeField::Minute, DateTimeField::Second,
      };
      break;
  }
  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::Weekday));
  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::Era));
  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::DayPeriod));
  MOZ_ASSERT(!defaultOptions.contains(DateTimeField::FractionalSecondDigits));

  // Steps 11-12.
  mozilla::intl::DateTimeFormat::ComponentsBag formatOptions;
  if (inherit == Inherit::All) {
    // Step 11.a.
    formatOptions = options;
  } else {
    // Step 12.a. (Implicit)

    // Step 12.b.
    switch (required) {
      case Required::Date:
      case Required::YearMonth:
      case Required::Any:
        formatOptions.era = options.era;
        break;
      case Required::Time:
      case Required::MonthDay:
        // |era| option not applicable for these types.
        break;
    }

    // Step 12.c.
    switch (required) {
      case Required::Time:
      case Required::Any:
        formatOptions.hourCycle = options.hourCycle;
        formatOptions.hour12 = options.hour12;
        break;
      case Required::Date:
      case Required::YearMonth:
      case Required::MonthDay:
        // |hourCycle| and |hour12| options not applicable for these types.
        break;
    }
  }

  // Steps 13-14.
  bool anyPresent = options.weekday || options.year || options.month ||
                    options.day || options.dayPeriod || options.hour ||
                    options.minute || options.second ||
                    options.fractionalSecondDigits;

  // Step 15.
  bool needDefaults = true;

  // Step 16. (Loop unrolled)
  if (requiredOptions.contains(DateTimeField::Weekday) && options.weekday) {
    formatOptions.weekday = options.weekday;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::Year) && options.year) {
    formatOptions.year = options.year;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::Month) && options.month) {
    formatOptions.month = options.month;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::Day) && options.day) {
    formatOptions.day = options.day;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::DayPeriod) && options.dayPeriod) {
    formatOptions.dayPeriod = options.dayPeriod;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::Hour) && options.hour) {
    formatOptions.hour = options.hour;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::Minute) && options.minute) {
    formatOptions.minute = options.minute;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::Second) && options.second) {
    formatOptions.second = options.second;
    needDefaults = false;
  }
  if (requiredOptions.contains(DateTimeField::FractionalSecondDigits) &&
      options.fractionalSecondDigits) {
    formatOptions.fractionalSecondDigits = options.fractionalSecondDigits;
    needDefaults = false;
  }

  // Step 17.
  if (needDefaults) {
    // Step 17.a.
    if (anyPresent && inherit == Inherit::Relevant) {
      return mozilla::Nothing();
    }

    // Step 17.b. (Loop unrolled)
    auto numericOption =
        mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::Numeric);
    if (defaultOptions.contains(DateTimeField::Year)) {
      formatOptions.year = numericOption;
    }
    if (defaultOptions.contains(DateTimeField::Month)) {
      formatOptions.month =
          mozilla::Some(mozilla::intl::DateTimeFormat::Month::Numeric);
    }
    if (defaultOptions.contains(DateTimeField::Day)) {
      formatOptions.day = numericOption;
    }
    if (defaultOptions.contains(DateTimeField::Hour)) {
      formatOptions.hour = numericOption;
    }
    if (defaultOptions.contains(DateTimeField::Minute)) {
      formatOptions.minute = numericOption;
    }
    if (defaultOptions.contains(DateTimeField::Second)) {
      formatOptions.second = std::move(numericOption);
    }

    // Step 17.c.
    if (defaults == Defaults::ZonedDateTime && !formatOptions.timeZoneName) {
      formatOptions.timeZoneName =
          mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Short);
    }
  }

  // Steps 18-20. (Performed in caller).

  return mozilla::Some(formatOptions);
}

/**
 * AdjustDateTimeStyleFormat ( formats, baseFormat, matcher, allowedOptions )
 *
 * https://tc39.es/proposal-temporal/#sec-adjustdatetimestyleformat
 */

static mozilla::Maybe<mozilla::intl::DateTimeFormat::ComponentsBag>
AdjustDateTimeStyleFormat(
    const mozilla::intl::DateTimeFormat::ComponentsBag& baseFormat,
    mozilla::EnumSet<DateTimeField> allowedOptions) {
  // Step 1.
  bool anyConflictingFields = false;

  // Step 5. (Reordered)
  mozilla::intl::DateTimeFormat::ComponentsBag formatOptions;

  // Steps 2 and 6. (Loops unrolled)
  if (baseFormat.era) {
    if (allowedOptions.contains(DateTimeField::Era)) {
      formatOptions.era = baseFormat.era;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.weekday) {
    if (allowedOptions.contains(DateTimeField::Weekday)) {
      formatOptions.weekday = baseFormat.weekday;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.year) {
    if (allowedOptions.contains(DateTimeField::Year)) {
      formatOptions.year = baseFormat.year;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.month) {
    if (allowedOptions.contains(DateTimeField::Month)) {
      formatOptions.month = baseFormat.month;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.day) {
    if (allowedOptions.contains(DateTimeField::Day)) {
      formatOptions.day = baseFormat.day;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.dayPeriod) {
    if (allowedOptions.contains(DateTimeField::DayPeriod)) {
      formatOptions.dayPeriod = baseFormat.dayPeriod;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.hour) {
    if (allowedOptions.contains(DateTimeField::Hour)) {
      formatOptions.hour = baseFormat.hour;
      formatOptions.hourCycle = baseFormat.hourCycle;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.minute) {
    if (allowedOptions.contains(DateTimeField::Minute)) {
      formatOptions.minute = baseFormat.minute;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.second) {
    if (allowedOptions.contains(DateTimeField::Second)) {
      formatOptions.second = baseFormat.second;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.fractionalSecondDigits) {
    if (allowedOptions.contains(DateTimeField::FractionalSecondDigits)) {
      formatOptions.fractionalSecondDigits = baseFormat.fractionalSecondDigits;
    } else {
      anyConflictingFields = true;
    }
  }
  if (baseFormat.timeZoneName) {
    anyConflictingFields = true;
  }

  // Steps 3-4.
  if (!anyConflictingFields) {
    return mozilla::Nothing();
  }

  // Steps 5-6. (Moved above)

  // Steps 7-9. (Performed in caller)

  return mozilla::Some(formatOptions);
}

static const char* DateTimeValueKindToString(DateTimeValueKind kind) {
  switch (kind) {
    case DateTimeValueKind::Number:
      return "number";
    case DateTimeValueKind::TemporalDate:
      return "Temporal.PlainDate";
    case DateTimeValueKind::TemporalTime:
      return "Temporal.PlainTime";
    case DateTimeValueKind::TemporalDateTime:
      return "Temporal.PlainDateTime";
    case DateTimeValueKind::TemporalYearMonth:
      return "Temporal.PlainYearMonth";
    case DateTimeValueKind::TemporalMonthDay:
      return "Temporal.PlainMonthDay";
    case DateTimeValueKind::TemporalZonedDateTime:
      return "Temporal.ZonedDateTime";
    case DateTimeValueKind::TemporalInstant:
      return "Temporal.Instant";
  }
  MOZ_CRASH("invalid date-time value kind");
}

class TimeZoneOffsetString {
  static constexpr std::u16string_view GMT = u"GMT";

  // Time zone offset string format is "±hh:mm".
  static constexpr size_t offsetLength = 6;

  // ICU custom time zones are in the format "GMT±hh:mm".
  char16_t timeZone_[GMT.size() + offsetLength] = {};

  TimeZoneOffsetString() = default;

 public:
  TimeZoneOffsetString(const TimeZoneOffsetString& other) { *this = other; }

  TimeZoneOffsetString& operator=(const TimeZoneOffsetString& other) {
    std::copy_n(other.timeZone_, std::size(timeZone_), timeZone_);
    return *this;
  }

  operator mozilla::Span<const char16_t>() const {
    return mozilla::Span(timeZone_);
  }

  static TimeZoneOffsetString GMTZero() {
    static constexpr std::u16string_view gmtZero = u"GMT+00:00";
    static_assert(gmtZero.length() ==
                  std::extent_v<decltype(TimeZoneOffsetString::timeZone_)>);

    TimeZoneOffsetString result{};
    gmtZero.copy(result.timeZone_, gmtZero.length());
    return result;
  }

  /**
   * |timeZone| is either a canonical IANA time zone identifier or a normalized
   * time zone offset string.
   */

  static mozilla::Maybe<TimeZoneOffsetString> from(
      const JSLinearString* timeZone) {
    MOZ_RELEASE_ASSERT(!timeZone->empty(), "time zone is a non-empty string");

    // If the time zone string starts with either "+" or "-", it is a normalized
    // time zone offset string, because (canonical) IANA time zone identifiers
    // can't start with "+" or "-".
    char16_t timeZoneSign = timeZone->latin1OrTwoByteChar(0);
    MOZ_ASSERT(timeZoneSign != 0x2212,
               "Minus sign is normalized to Ascii minus");
    if (timeZoneSign != '+' && timeZoneSign != '-') {
      return mozilla::Nothing();
    }

    // Release assert because we don't want CopyChars to write out-of-bounds.
    MOZ_RELEASE_ASSERT(timeZone->length() == offsetLength);

    // ToValidCanonicalTimeZoneIdentifier normalizes offset strings to the
    // format "±hh:mm".
    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(1)));
    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(2)));
    MOZ_ASSERT(timeZone->latin1OrTwoByteChar(3) == ':');
    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(4)));
    MOZ_ASSERT(mozilla::IsAsciiDigit(timeZone->latin1OrTwoByteChar(5)));

    // ToValidCanonicalTimeZoneIdentifier verifies the offset is at most ±23:59.
#ifdef DEBUG
    auto twoDigit = [&](size_t offset) {
      auto c1 = timeZone->latin1OrTwoByteChar(offset);
      auto c2 = timeZone->latin1OrTwoByteChar(offset + 1);
      return mozilla::AsciiAlphanumericToNumber(c1) * 10 +
             mozilla::AsciiAlphanumericToNumber(c2);
    };

    int32_t hours = twoDigit(1);
    MOZ_ASSERT(0 <= hours && hours <= 23);

    int32_t minutes = twoDigit(4);
    MOZ_ASSERT(0 <= minutes && minutes <= 59);
#endif

    TimeZoneOffsetString result{};

    // Copy the string "GMT" followed by the offset string.
    size_t copied = GMT.copy(result.timeZone_, GMT.size());
    CopyChars(result.timeZone_ + copied, *timeZone);

    return mozilla::Some(result);
  }
};

class TimeZoneChars final {
  JS::AutoStableStringChars timeZone_;
  mozilla::Maybe<TimeZoneOffsetString> timeZoneOffset_;

  mozilla::Maybe<mozilla::Span<const char16_t>> maybeSpan() const {
    if (timeZone_.isTwoByte()) {
      return mozilla::Some(timeZone_.twoByteRange());
    }
    if (timeZoneOffset_) {
      return timeZoneOffset_;
    }
    return mozilla::Nothing();
  }

 public:
  explicit TimeZoneChars(JSContext* cx) : timeZone_(cx) {}

  operator mozilla::Span<const char16_t>() const {
    return maybeSpan().valueOr(mozilla::Span<const char16_t>{});
  }

  operator mozilla::Maybe<mozilla::Span<const char16_t>>() const {
    return maybeSpan();
  }

  bool init(JSContext* cx, JSLinearString* timeZone) {
    auto timeZoneOffset = TimeZoneOffsetString::from(timeZone);
    if (timeZoneOffset) {
      timeZoneOffset_ = std::move(timeZoneOffset);
      return true;
    }
    return timeZone_.initTwoByte(cx, timeZone);
  }

  void setToOffset(const TimeZoneOffsetString& offset) {
    timeZoneOffset_ = mozilla::Some(offset);
  }
};

static bool IsPlainDateTimeValue(DateTimeValueKind kind) {
  switch (kind) {
    case DateTimeValueKind::Number:
    case DateTimeValueKind::TemporalZonedDateTime:
    case DateTimeValueKind::TemporalInstant:
      return false;
    case DateTimeValueKind::TemporalDate:
    case DateTimeValueKind::TemporalTime:
    case DateTimeValueKind::TemporalDateTime:
    case DateTimeValueKind::TemporalYearMonth:
    case DateTimeValueKind::TemporalMonthDay:
      return true;
  }
  MOZ_CRASH("invalid date time value kind");
}

/**
 * Returns a new mozilla::intl::DateTimeFormat with the locale and date-time
 * formatting options of the given DateTimeFormat.
 */

static mozilla::intl::DateTimeFormat* NewDateTimeFormat(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    DateTimeValueKind kind) {
  if (!ResolveLocale(cx, dateTimeFormat)) {
    return nullptr;
  }
  auto dtfOptions = dateTimeFormat->getOptions();

  auto locale = DateTimeFormatLocale(cx, dateTimeFormat);
  if (!locale) {
    return nullptr;
  }

  TimeZoneChars timeZone(cx);
  if (IsPlainDateTimeValue(kind)) {
    timeZone.setToOffset(TimeZoneOffsetString::GMTZero());
  } else {
    if (!timeZone.init(cx, dateTimeFormat->getTimeZone())) {
      return nullptr;
    }
  }

  // This is a DateTimeFormat defined by a pattern option. This is internal
  // to Mozilla, and not part of the ECMA-402 API.
  if (auto* patternString = dateTimeFormat->getPattern()) {
    JS::AutoStableStringChars pattern(cx);
    if (!pattern.initTwoByte(cx, patternString)) {
      return nullptr;
    }

    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromPattern(
        mozilla::MakeStringSpan(locale.get()), pattern.twoByteRange(),
        timeZone);
    if (dfResult.isErr()) {
      ReportInternalError(cx, dfResult.unwrapErr());
      return nullptr;
    }
    return dfResult.unwrap().release();
  }

  // This is a DateTimeFormat defined by a time style or date style.
  if (dtfOptions.dateStyle.isSome() || dtfOptions.timeStyle.isSome()) {
    switch (kind) {
      case DateTimeValueKind::TemporalDate:
      case DateTimeValueKind::TemporalYearMonth:
      case DateTimeValueKind::TemporalMonthDay: {
        if (dtfOptions.dateStyle.isNothing()) {
          JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                    JSMSG_INVALID_FORMAT_OPTIONS,
                                    DateTimeValueKindToString(kind));
          return nullptr;
        }
        break;
      }

      case DateTimeValueKind::TemporalTime: {
        if (dtfOptions.timeStyle.isNothing()) {
          JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                    JSMSG_INVALID_FORMAT_OPTIONS,
                                    DateTimeValueKindToString(kind));
          return nullptr;
        }
        break;
      }

      case DateTimeValueKind::Number:
      case DateTimeValueKind::TemporalDateTime:
      case DateTimeValueKind::TemporalZonedDateTime:
      case DateTimeValueKind::TemporalInstant:
        break;
    }

    mozilla::intl::DateTimeFormat::StyleBag style = {
        .date = dtfOptions.dateStyle,
        .time = dtfOptions.timeStyle,
        .hourCycle = dtfOptions.hourCycle,
        .hour12 = dtfOptions.hour12,
    };

    auto& sharedIntlData = cx->runtime()->sharedIntlData.ref();
    auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
    if (!dtpg) {
      return nullptr;
    }

    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromStyle(
        mozilla::MakeStringSpan(locale.get()), style, dtpg, timeZone);
    if (dfResult.isErr()) {
      ReportInternalError(cx, dfResult.unwrapErr());
      return nullptr;
    }
    auto df = dfResult.unwrap();

    mozilla::EnumSet<DateTimeField> allowedOptions;
    switch (kind) {
      case DateTimeValueKind::TemporalDate:
        allowedOptions = {
            DateTimeField::Weekday, DateTimeField::Era, DateTimeField::Year,
            DateTimeField::Month,   DateTimeField::Day,
        };
        break;
      case DateTimeValueKind::TemporalTime:
        allowedOptions = {
            DateTimeField::DayPeriod,
            DateTimeField::Hour,
            DateTimeField::Minute,
            DateTimeField::Second,
            DateTimeField::FractionalSecondDigits,
        };
        break;
      case DateTimeValueKind::TemporalDateTime:
        allowedOptions = {
            DateTimeField::Weekday, DateTimeField::Era,
            DateTimeField::Year,    DateTimeField::Month,
            DateTimeField::Day,     DateTimeField::DayPeriod,
            DateTimeField::Hour,    DateTimeField::Minute,
            DateTimeField::Second,  DateTimeField::FractionalSecondDigits,
        };
        break;
      case DateTimeValueKind::TemporalYearMonth:
        allowedOptions = {
            DateTimeField::Era,
            DateTimeField::Year,
            DateTimeField::Month,
        };
        break;
      case DateTimeValueKind::TemporalMonthDay:
        allowedOptions = {
            DateTimeField::Month,
            DateTimeField::Day,
        };
        break;

      case DateTimeValueKind::Number:
      case DateTimeValueKind::TemporalZonedDateTime:
      case DateTimeValueKind::TemporalInstant:
        break;
    }

    if (allowedOptions.isEmpty()) {
      return df.release();
    }

    auto baseFormatResult = df->ResolveComponents();
    if (baseFormatResult.isErr()) {
      ReportInternalError(cx, baseFormatResult.unwrapErr());
      return nullptr;
    }
    auto baseFormat = baseFormatResult.unwrap();

    auto adjusted = AdjustDateTimeStyleFormat(baseFormat, allowedOptions);
    if (adjusted.isNothing()) {
      return df.release();
    }

    auto dfAdjustedResult =
        mozilla::intl::DateTimeFormat::TryCreateFromComponents(
            mozilla::MakeStringSpan(locale.get()), *adjusted, dtpg, timeZone);
    if (dfAdjustedResult.isErr()) {
      ReportInternalError(cx, dfAdjustedResult.unwrapErr());
      return nullptr;
    }
    return dfAdjustedResult.unwrap().release();
  }

  // This is a DateTimeFormat defined by a components bag.
  mozilla::intl::DateTimeFormat::ComponentsBag bag = {
      .era = dtfOptions.era,
      .year = dtfOptions.year,
      .month = dtfOptions.month,
      .day = dtfOptions.day,
      .weekday = dtfOptions.weekday,
      .hour = dtfOptions.hour,
      .minute = dtfOptions.minute,
      .second = dtfOptions.second,
      .timeZoneName = dtfOptions.timeZoneName,
      .hour12 = dtfOptions.hour12,
      .hourCycle = dtfOptions.hourCycle,
      .dayPeriod = dtfOptions.dayPeriod,
      .fractionalSecondDigits = dtfOptions.fractionalSecondDigits,
  };

  auto [required, defaults, inherit] = GetDateTimeFormatArgs(dtfOptions, kind);

  auto resolvedBag = GetDateTimeFormat(bag, required, defaults, inherit);
  if (!resolvedBag) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_INVALID_FORMAT_OPTIONS,
                              DateTimeValueKindToString(kind));
    return nullptr;
  }
  bag = *resolvedBag;

  auto& sharedIntlData = cx->runtime()->sharedIntlData.ref();
  auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
  if (!dtpg) {
    return nullptr;
  }

  auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromComponents(
      mozilla::MakeStringSpan(locale.get()), bag, dtpg, timeZone);
  if (dfResult.isErr()) {
    ReportInternalError(cx, dfResult.unwrapErr());
    return nullptr;
  }
  return dfResult.unwrap().release();
}

void js::intl::DateTimeFormatObject::maybeClearCache(DateTimeValueKind kind) {
  if (getDateTimeValueKind() == kind) {
    return;
  }
  setDateTimeValueKind(kind);

  if (auto* df = getDateFormat()) {
    RemoveICUCellMemory(this,
                        DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
    delete df;

    setDateFormat(nullptr);
  }

  if (auto* dif = getDateIntervalFormat()) {
    RemoveICUCellMemory(
        this, DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
    delete dif;

    setDateIntervalFormat(nullptr);
  }
}

static mozilla::intl::DateTimeFormat* GetOrCreateDateTimeFormat(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    DateTimeValueKind kind) {
  // Clear previously created formatters if their type doesn't match.
  dateTimeFormat->maybeClearCache(kind);

  // Obtain a cached mozilla::intl::DateTimeFormat object.
  if (auto* df = dateTimeFormat->getDateFormat()) {
    return df;
  }

  auto* df = NewDateTimeFormat(cx, dateTimeFormat, kind);
  if (!df) {
    return nullptr;
  }
  dateTimeFormat->setDateFormat(df);

  AddICUCellMemory(dateTimeFormat,
                   DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
  return df;
}

static auto OptionToString(mozilla::intl::DateTimeFormat::HourCycle hourCycle) {
  return HourCycleToString(hourCycle);
}

static auto OptionToString(mozilla::intl::DateTimeFormat::Text text) {
  return TextComponentToString(text);
}

static auto OptionToString(mozilla::intl::DateTimeFormat::Numeric numeric) {
  return NumericComponentToString(numeric);
}

static auto OptionToString(mozilla::intl::DateTimeFormat::Month month) {
  return MonthToString(month);
}

static auto OptionToString(
    mozilla::intl::DateTimeFormat::TimeZoneName timeZoneName) {
  return TimeZoneNameToString(timeZoneName);
}

template <typename T>
static bool SetOptionsProperty(JSContext* cx,
                               MutableHandle<IdValueVector> options,
                               Handle<PropertyName*> name,
                               mozilla::Maybe<T> intlProp) {
  if (!intlProp) {
    return true;
  }
  auto* str = NewStringCopy<CanGC>(cx, OptionToString(*intlProp));
  if (!str) {
    return false;
  }
  return options.emplaceBack(NameToId(name), StringValue(str));
}

enum class IncludeDateTimeFields : bool { No, Yes };

/**
 * Extracts the resolved components from a DateTimeFormat and applies them to
 * the object for resolved components.
 */

static bool ResolveDateTimeFormatComponents(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    MutableHandle<IdValueVector> options,
    IncludeDateTimeFields includeDateTimeFields) {
  auto* df =
      GetOrCreateDateTimeFormat(cx, dateTimeFormat, DateTimeValueKind::Number);
  if (!df) {
    return false;
  }

  auto result = df->ResolveComponents();
  if (result.isErr()) {
    ReportInternalError(cx, result.unwrapErr());
    return false;
  }

  auto components = result.unwrap();

  // Map the resolved mozilla::intl::DateTimeFormat::ComponentsBag to the
  // options object as returned by DateTimeFormat.prototype.resolvedOptions.
  //
  // Resolved options must match the ordering as defined in:
  // https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.resolvedoptions

  if (!SetOptionsProperty(cx, options, cx->names().hourCycle,
                          components.hourCycle)) {
    return false;
  }

  if (components.hour12) {
    if (!options.emplaceBack(NameToId(cx->names().hour12),
                             BooleanValue(*components.hour12))) {
      return false;
    }
  }

  if (includeDateTimeFields == IncludeDateTimeFields::No) {
    // Do not include date time fields.
    return true;
  }

  if (!SetOptionsProperty(cx, options, cx->names().weekday,
                          components.weekday)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().era, components.era)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().year, components.year)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().month, components.month)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().day, components.day)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().dayPeriod,
                          components.dayPeriod)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().hour, components.hour)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().minute, components.minute)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().second, components.second)) {
    return false;
  }
  if (!SetOptionsProperty(cx, options, cx->names().timeZoneName,
                          components.timeZoneName)) {
    return false;
  }

  if (components.fractionalSecondDigits) {
    if (!options.emplaceBack(NameToId(cx->names().fractionalSecondDigits),
                             Int32Value(*components.fractionalSecondDigits))) {
      return false;
    }
  }

  return true;
}

/**
 * ToDateTimeFormattable ( value )
 *
 * https://tc39.es/proposal-temporal/#sec-todatetimeformattable
 */

static auto ToDateTimeFormattable(const Value& value) {
  MOZ_ASSERT(!value.isUndefined());

  // Step 1. (Inlined IsTemporalObject)
  if (value.isObject()) {
    auto* obj = CheckedUnwrapStatic(&value.toObject());
    if (obj) {
      if (obj->is<PlainDateObject>()) {
        return DateTimeValueKind::TemporalDate;
      }
      if (obj->is<PlainDateTimeObject>()) {
        return DateTimeValueKind::TemporalDateTime;
      }
      if (obj->is<PlainTimeObject>()) {
        return DateTimeValueKind::TemporalTime;
      }
      if (obj->is<PlainYearMonthObject>()) {
        return DateTimeValueKind::TemporalYearMonth;
      }
      if (obj->is<PlainMonthDayObject>()) {
        return DateTimeValueKind::TemporalMonthDay;
      }
      if (obj->is<ZonedDateTimeObject>()) {
        return DateTimeValueKind::TemporalZonedDateTime;
      }
      if (obj->is<InstantObject>()) {
        return DateTimeValueKind::TemporalInstant;
      }
      return DateTimeValueKind::Number;
    }
  }

  // Step 2. (ToNumber performed in caller)
  return DateTimeValueKind::Number;
}

/**
 * Ensure the calendar value is resolved.
 */

static bool ResolveCalendarValue(JSContext* cx,
                                 Handle<DateTimeFormatObject*> dateTimeFormat) {
  if (dateTimeFormat->getCalendarValue()) {
    return true;
  }

  // Ensure the calendar option is resolved.
  if (!ResolveLocale(cx, dateTimeFormat)) {
    return false;
  }

  Rooted<JSString*> calendarString(cx, ResolveCalendar(cx, dateTimeFormat));
  if (!calendarString) {
    return false;
  }

  Rooted<CalendarValue> calendar(cx);
  if (!CanonicalizeCalendar(cx, calendarString, &calendar)) {
    return false;
  }
  dateTimeFormat->setCalendarValue(calendar);
  return true;
}

/**
 * Throws an error if `dateTimeFormat`'s calendar is not equal to `calendarId`.
 */

static bool ThrowIfCalendarNotEqual(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    CalendarId calendarId) {
  if (!ResolveCalendarValue(cx, dateTimeFormat)) {
    return false;
  }
  auto calendar = dateTimeFormat->getCalendarValue();

  if (calendarId != calendar.identifier()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
                              CalendarIdentifier(calendarId).data(),
                              CalendarIdentifier(calendar).data());
    return false;
  }
  return true;
}

struct EpochMilliseconds {
  double milliseconds = 0;

  EpochMilliseconds() = default;

  explicit EpochMilliseconds(EpochNanoseconds epochNs)
      : milliseconds(epochNs.floorToMilliseconds()) {}

  explicit EpochMilliseconds(JS::ClippedTime time)
      : milliseconds(time.toDouble()) {
    MOZ_ASSERT(time.isValid());
  }

  double toDouble() const { return milliseconds; }
};

/**
 * HandleDateTimeTemporalDate ( dateTimeFormat, temporalDate )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldate
 */

static bool HandleDateTimeTemporalDate(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    Handle<PlainDateObject*> unwrappedTemporalDate, EpochMilliseconds* result) {
  auto isoDate = unwrappedTemporalDate->date();
  auto calendarId = unwrappedTemporalDate->calendar().identifier();

  // Step 1.
  if (calendarId != CalendarId::ISO8601) {
    if (!ThrowIfCalendarNotEqual(cx, dateTimeFormat, calendarId)) {
      return false;
    }
  }

  // Step 2.
  auto isoDateTime = ISODateTime{isoDate, {1200}};

  // Step 3.
  auto epochNs = GetUTCEpochNanoseconds(isoDateTime);

  // Steps 4-5. (Performed in NewDateTimeFormat)

  // Step 6.
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * HandleDateTimeTemporalYearMonth ( dateTimeFormat, temporalYearMonth )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalyearmonth
 */

static bool HandleDateTimeTemporalYearMonth(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    Handle<PlainYearMonthObject*> unwrappedTemporalYearMonth,
    EpochMilliseconds* result) {
  auto isoDate = unwrappedTemporalYearMonth->date();
  auto calendarId = unwrappedTemporalYearMonth->calendar().identifier();

  // Step 1.
  if (!ThrowIfCalendarNotEqual(cx, dateTimeFormat, calendarId)) {
    return false;
  }

  // Step 2.
  auto isoDateTime = ISODateTime{isoDate, {1200}};

  // Step 3.
  auto epochNs = GetUTCEpochNanoseconds(isoDateTime);

  // Steps 4-5. (Performed in NewDateTimeFormat)

  // Step 6.
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * HandleDateTimeTemporalMonthDay ( dateTimeFormat, temporalMonthDay )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalmonthday
 */

static bool HandleDateTimeTemporalMonthDay(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    Handle<PlainMonthDayObject*> unwrappedTemporalMonthDay,
    EpochMilliseconds* result) {
  auto isoDate = unwrappedTemporalMonthDay->date();
  auto calendarId = unwrappedTemporalMonthDay->calendar().identifier();

  // Step 1.
  if (!ThrowIfCalendarNotEqual(cx, dateTimeFormat, calendarId)) {
    return false;
  }

  // Step 2.
  auto isoDateTime = ISODateTime{isoDate, {1200}};

  // Step 3.
  auto epochNs = GetUTCEpochNanoseconds(isoDateTime);

  // Steps 4-5. (Performed in NewDateTimeFormat)

  // Step 6.
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * HandleDateTimeTemporalTime ( dateTimeFormat, temporalTime )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaltime
 */

static bool HandleDateTimeTemporalTime(PlainTimeObject* unwrappedTemporalTime,
                                       EpochMilliseconds* result) {
  auto time = unwrappedTemporalTime->time();

  // Steps 1-2.
  auto isoDateTime = ISODateTime{{197011}, time};

  // Step 3.
  auto epochNs = GetUTCEpochNanoseconds(isoDateTime);

  // Steps 4-5. (Performed in NewDateTimeFormat)

  // Step 6.
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * HandleDateTimeTemporalDateTime ( dateTimeFormat, dateTime )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldatetime
 */

static bool HandleDateTimeTemporalDateTime(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    Handle<PlainDateTimeObject*> unwrappedDateTime, EpochMilliseconds* result) {
  auto isoDateTime = unwrappedDateTime->dateTime();
  auto calendarId = unwrappedDateTime->calendar().identifier();

  // Step 1.
  if (calendarId != CalendarId::ISO8601) {
    if (!ThrowIfCalendarNotEqual(cx, dateTimeFormat, calendarId)) {
      return false;
    }
  }

  // Step 2.
  auto epochNs = GetUTCEpochNanoseconds(isoDateTime);

  // Step 3. (Performed in NewDateTimeFormat)

  // Step 4.
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * HandleDateTimeTemporalInstant ( dateTimeFormat, instant )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalinstant
 */

static bool HandleDateTimeTemporalInstant(InstantObject* unwrappedInstant,
                                          EpochMilliseconds* result) {
  // Step 1. (Performed in NewDateTimeFormat)

  // Step 2.
  auto epochNs = unwrappedInstant->epochNanoseconds();
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
 */

static bool HandleDateTimeTemporalZonedDateTime(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    Handle<ZonedDateTimeObject*> unwrappedZonedDateTime,
    EpochMilliseconds* result) {
  auto epochNs = unwrappedZonedDateTime->epochNanoseconds();
  auto calendarId = unwrappedZonedDateTime->calendar().identifier();

  // Step 4.
  if (calendarId != CalendarId::ISO8601) {
    if (!ThrowIfCalendarNotEqual(cx, dateTimeFormat, calendarId)) {
      return false;
    }
  }

  // Step 5.
  *result = EpochMilliseconds{epochNs};
  return true;
}

/**
 * HandleDateTimeOthers ( dateTimeFormat, x )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimeothers
 */

static bool HandleDateTimeOthers(JSContext* cx, const char* method, double x,
                                 EpochMilliseconds* result) {
  // Step 1.
  auto clipped = JS::TimeClip(x);

  // Step 2.
  if (!clipped.isValid()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_DATE_NOT_FINITE, "DateTimeFormat", method);
    return false;
  }

  // Step 4. (Performed in NewDateTimeFormat)

  // Steps 3 and 5.
  *result = EpochMilliseconds{clipped};
  return true;
}

/**
 * HandleDateTimeValue ( dateTimeFormat, x )
 *
 * https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue
 */

static bool HandleDateTimeValue(JSContext* cx, const char* method,
                                Handle<DateTimeFormatObject*> dateTimeFormat,
                                JSObject* x, EpochMilliseconds* result) {
  // Step 1.
  Rooted<JSObject*> unwrapped(cx, CheckedUnwrapStatic(x));
  if (!unwrapped) {
    ReportAccessDenied(cx);
    return false;
  }

  // Step 1.a.
  if (unwrapped->is<PlainDateObject>()) {
    return HandleDateTimeTemporalDate(cx, dateTimeFormat,
                                      unwrapped.as<PlainDateObject>(), result);
  }

  // Step 1.b.
  if (unwrapped->is<PlainYearMonthObject>()) {
    return HandleDateTimeTemporalYearMonth(
        cx, dateTimeFormat, unwrapped.as<PlainYearMonthObject>(), result);
  }

  // Step 1.c.
  if (unwrapped->is<PlainMonthDayObject>()) {
    return HandleDateTimeTemporalMonthDay(
        cx, dateTimeFormat, unwrapped.as<PlainMonthDayObject>(), result);
  }

  // Step 1.d.
  if (unwrapped->is<PlainTimeObject>()) {
    return HandleDateTimeTemporalTime(&unwrapped->as<PlainTimeObject>(),
                                      result);
  }

  // Step 1.e.
  if (unwrapped->is<PlainDateTimeObject>()) {
    return HandleDateTimeTemporalDateTime(
        cx, dateTimeFormat, unwrapped.as<PlainDateTimeObject>(), result);
  }

  // Step 1.f.
  if (unwrapped->is<InstantObject>()) {
    return HandleDateTimeTemporalInstant(&unwrapped->as<InstantObject>(),
                                         result);
  }

  // Step 1.g.
  MOZ_ASSERT(unwrapped->is<ZonedDateTimeObject>());

  // Step 1.h.
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
                            "object", unwrapped->getClass()->name);
  return false;
}

struct DateTimeValue {
  EpochMilliseconds time;
  DateTimeValueKind kind{};
};

/**
 * DateTime Format Functions, steps 3-4 and 5 (partial).
 * Intl.DateTimeFormat.prototype.formatToParts, steps 3-4 and 5 (partial).
 */

static bool ToDateTimeValue(JSContext* cx, const char* method,
                            Handle<DateTimeFormatObject*> dateTimeFormat,
                            Handle<JS::Value> date, DateTimeValue* result) {
  // DateTime Format Functions, step 3.
  // Intl.DateTimeFormat.prototype.formatToParts, step 3.
  if (date.isUndefined()) {
    result->time = EpochMilliseconds{DateNow(cx)};
    result->kind = DateTimeValueKind::Number;
    return true;
  }

  // DateTime Format Functions, step 4.
  // Intl.DTF.prototype.formatToParts, step 4.
  auto kind = ToDateTimeFormattable(date);
  result->kind = kind;

  // DateTime Format Functions, step 5. (Call to FormatDateTime)
  // - FormatDateTime, step 1. (Call to PartitionDateTimePattern)
  // -- PartitionDateTimePattern, step 1. (Call to HandleDateTimeValue)
  // --- HandleDateTimeValue, step 1.
  //
  // Intl.DTF.prototype.formatToParts, step 5. (Call to FormatDateTimeToParts)
  // - FormatDateTimeToParts, step 1. (Call to PartitionDateTimePattern)
  // -- PartitionDateTimePattern, step 1. (Call to HandleDateTimeValue)
  // --- HandleDateTimeValue, step 1.
  if (kind != DateTimeValueKind::Number) {
    MOZ_ASSERT(date.isObject());
    return HandleDateTimeValue(cx, method, dateTimeFormat, &date.toObject(),
                               &result->time);
  }

  // ToDateTimeFormattable, step 2. (Call to ToNumber)
  double num;
  if (!JS::ToNumber(cx, date, &num)) {
    return false;
  }

  // DateTime Format Functions, step 5. (Call to FormatDateTime)
  // - FormatDateTime, step 1. (Call to PartitionDateTimePattern)
  // -- PartitionDateTimePattern, step 1. (Call to HandleDateTimeValue)
  // --- HandleDateTimeValue, step 2. (Call to HandleDateTimeOthers)
  //
  // Intl.DTF.prototype.formatToParts, step 5. (Call to FormatDateTimeToParts)
  // - FormatDateTimeToParts, step 1. (Call to PartitionDateTimePattern)
  // -- PartitionDateTimePattern, step 1. (Call to HandleDateTimeValue)
  // --- HandleDateTimeValue, step 2. (Call to HandleDateTimeOthers)
  return HandleDateTimeOthers(cx, method, num, &result->time);
}

/**
 * FormatDateTime ( dateTimeFormat, x )
 * PartitionDateTimePattern ( dateTimeFormat, x )
 *
 * Returns a String value representing x according to the effective locale and
 * the formatting options of the given DateTimeFormat.
 */

static bool FormatDateTime(JSContext* cx,
                           const mozilla::intl::DateTimeFormat* df,
                           EpochMilliseconds x,
                           MutableHandle<JS::Value> result) {
  // FormatDateTime, step 1. (Inlined call to PartitionDateTimePattern)

  // PartitionDateTimePattern, step 1. (Performed in caller)

  // PartitionDateTimePattern, steps 2-7.
  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
  auto dfResult = df->TryFormat(x.toDouble(), buffer);
  if (dfResult.isErr()) {
    ReportInternalError(cx, dfResult.unwrapErr());
    return false;
  }

  // FormatDateTime, steps 2-4.
  auto* str = buffer.toString(cx);
  if (!str) {
    return false;
  }
  result.setString(str);
  return true;
}

static bool FormatDateTime(JSContext* cx,
                           Handle<DateTimeFormatObject*> dateTimeFormat,
                           const DateTimeValue& date,
                           MutableHandle<JS::Value> result) {
  auto* df = GetOrCreateDateTimeFormat(cx, dateTimeFormat, date.kind);
  if (!df) {
    return false;
  }
  return FormatDateTime(cx, df, date.time, result);
}

static JSString* DateTimePartTypeToString(
    JSContext* cx, mozilla::intl::DateTimePartType type) {
  switch (type) {
    case mozilla::intl::DateTimePartType::Literal:
      return cx->names().literal;
    case mozilla::intl::DateTimePartType::Era:
      return cx->names().era;
    case mozilla::intl::DateTimePartType::Year:
      return cx->names().year;
    case mozilla::intl::DateTimePartType::YearName:
      return cx->names().yearName;
    case mozilla::intl::DateTimePartType::RelatedYear:
      return cx->names().relatedYear;
    case mozilla::intl::DateTimePartType::Month:
      return cx->names().month;
    case mozilla::intl::DateTimePartType::Day:
      return cx->names().day;
    case mozilla::intl::DateTimePartType::Hour:
      return cx->names().hour;
    case mozilla::intl::DateTimePartType::Minute:
      return cx->names().minute;
    case mozilla::intl::DateTimePartType::Second:
      return cx->names().second;
    case mozilla::intl::DateTimePartType::Weekday:
      return cx->names().weekday;
    case mozilla::intl::DateTimePartType::DayPeriod:
      return cx->names().dayPeriod;
    case mozilla::intl::DateTimePartType::TimeZoneName:
      return cx->names().timeZoneName;
    case mozilla::intl::DateTimePartType::FractionalSecondDigits:
      return cx->names().fractionalSecond;
    case mozilla::intl::DateTimePartType::Unknown:
      return cx->names().unknown;
  }

  MOZ_CRASH(
      "unenumerated, undocumented format field returned "
      "by iterator");
}

static JSString* DateTimePartSourceToString(
    JSContext* cx, mozilla::intl::DateTimePartSource source) {
  switch (source) {
    case mozilla::intl::DateTimePartSource::Shared:
      return cx->names().shared;
    case mozilla::intl::DateTimePartSource::StartRange:
      return cx->names().startRange;
    case mozilla::intl::DateTimePartSource::EndRange:
      return cx->names().endRange;
  }

  MOZ_CRASH(
      "unenumerated, undocumented format field returned "
      "by iterator");
}

enum class DateTimeSource : bool { No, Yes };

/**
 * FormatDateTimeToParts ( dateTimeFormat, x )
 * FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
 *
 * Create the part object for FormatDateTime{Range}ToParts.
 */

static PlainObject* CreateDateTimePart(JSContext* cx,
                                       const mozilla::intl::DateTimePart& part,
                                       Handle<JSString*> value,
                                       DateTimeSource dateTimeSource) {
  Rooted<IdValueVector> properties(cx, cx);

  auto* type = DateTimePartTypeToString(cx, part.mType);
  if (!properties.emplaceBack(NameToId(cx->names().type), StringValue(type))) {
    return nullptr;
  }

  if (!properties.emplaceBack(NameToId(cx->names().value),
                              StringValue(value))) {
    return nullptr;
  }

  if (dateTimeSource == DateTimeSource::Yes) {
    auto* source = DateTimePartSourceToString(cx, part.mSource);
    if (!properties.emplaceBack(NameToId(cx->names().source),
                                StringValue(source))) {
      return nullptr;
    }
  }

  return NewPlainObjectWithUniqueNames(cx, properties);
}

/**
 * FormatDateTimeToParts ( dateTimeFormat, x )
 * FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
 *
 * Create the parts array for FormatDateTime{Range}ToParts.
 */

static bool CreateDateTimePartArray(
    JSContext* cx, mozilla::Span<const char16_t> formattedSpan,
    DateTimeSource dateTimeSource,
    const mozilla::intl::DateTimePartVector& parts,
    MutableHandle<JS::Value> result) {
  Rooted<JSString*> overallResult(cx, NewStringCopy<CanGC>(cx, formattedSpan));
  if (!overallResult) {
    return false;
  }

  Rooted<ArrayObject*> partsArray(
      cx, NewDenseFullyAllocatedArray(cx, parts.length()));
  if (!partsArray) {
    return false;
  }
  partsArray->ensureDenseInitializedLength(0, parts.length());

  if (overallResult->empty()) {
    // An empty string contains no parts, so avoid extra work below.
    result.setObject(*partsArray);
    return true;
  }

  Rooted<JSString*> value(cx);

  size_t index = 0;
  size_t beginIndex = 0;
  for (const auto& part : parts) {
    MOZ_ASSERT(part.mEndIndex > beginIndex);
    value = NewDependentString(cx, overallResult, beginIndex,
                               part.mEndIndex - beginIndex);
    if (!value) {
      return false;
    }
    beginIndex = part.mEndIndex;

    auto* obj = CreateDateTimePart(cx, part, value, dateTimeSource);
    if (!obj) {
      return false;
    }
    partsArray->initDenseElement(index++, ObjectValue(*obj));
  }
  MOZ_ASSERT(index == parts.length());
  MOZ_ASSERT(beginIndex == formattedSpan.size());

  result.setObject(*partsArray);
  return true;
}

/**
 * FormatDateTimeToParts ( dateTimeFormat, x )
 */

static bool FormatDateTimeToParts(JSContext* cx,
                                  const mozilla::intl::DateTimeFormat* df,
                                  EpochMilliseconds x,
                                  DateTimeSource dateTimeSource,
                                  MutableHandle<JS::Value> result) {
  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
  mozilla::intl::DateTimePartVector parts;
  auto r = df->TryFormatToParts(x.toDouble(), buffer, parts);
  if (r.isErr()) {
    ReportInternalError(cx, r.unwrapErr());
    return false;
  }

  return CreateDateTimePartArray(cx, buffer, dateTimeSource, parts, result);
}

/**
 * FormatDateTimeToParts ( dateTimeFormat, x )
 */

static bool FormatDateTimeToParts(JSContext* cx,
                                  Handle<DateTimeFormatObject*> dateTimeFormat,
                                  const DateTimeValue& date,
                                  MutableHandle<JS::Value> result) {
  auto* df = GetOrCreateDateTimeFormat(cx, dateTimeFormat, date.kind);
  if (!df) {
    return false;
  }
  return FormatDateTimeToParts(cx, df, date.time, DateTimeSource::No, result);
}

struct DateTimeRangeValue {
  EpochMilliseconds start;
  EpochMilliseconds end;
  DateTimeValueKind kind{};
};

/**
 * Intl.DateTimeFormat.prototype.formatRange, steps 3-5 and 6 (partial).
 * Intl.DateTimeFormat.prototype.formatRangeToParts, steps 3-5 and 6 (partial).
 */

static bool ToDateTimeRangeValue(JSContext* cx, const char* method,
                                 Handle<DateTimeFormatObject*> dateTimeFormat,
                                 Handle<JS::Value> start, Handle<JS::Value> end,
                                 DateTimeRangeValue* result) {
  // Intl.DateTimeFormat.prototype.formatRange, step 3.
  // Intl.DateTimeFormat.prototype.formatRangeToParts, step 3.
  if (start.isUndefined() || end.isUndefined()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_UNDEFINED_DATE,
                              start.isUndefined() ? "start" : "end", method);
    return false;
  }

  // Intl.DateTimeFormat.prototype.formatRange, step 4.
  // Intl.DateTimeFormat.prototype.formatRangeToParts, step 4.
  auto startKind = ToDateTimeFormattable(start);

  // Intl.DateTimeFormat.prototype.formatRange, step 5.
  // Intl.DateTimeFormat.prototype.formatRangeToParts, step 5.
  auto endKind = ToDateTimeFormattable(end);

  // ToDateTimeFormattable, step 2. (Call to ToNumber)
  double startNum;
  if (startKind == DateTimeValueKind::Number) {
    if (!JS::ToNumber(cx, start, &startNum)) {
      return false;
    }
  }

  // ToDateTimeFormattable, step 2. (Call to ToNumber)
  double endNum;
  if (endKind == DateTimeValueKind::Number) {
    if (!JS::ToNumber(cx, end, &endNum)) {
      return false;
    }
  }

  // clang-format off
  //
  // Intl.DTF.prototype.formatRange, step 6. (Call to FormatDateTimeRange)
  // - FormatDateTimeRange, step 1. (Call to PartitionDateTimeRangePattern)
  //
  // Intl.DTF.prototype.formatRangeToParts, step 6. (Call to FormatDateTimeRangeToParts)
  // - FormatDateTimeRangeToParts, step 1. (Call to PartitionDateTimeRangePattern)
  //
  // clang-format on

  // PartitionDateTimeRangePattern, step 1.
  if (startKind != endKind) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_EXPECTED_TYPE, method,
                              DateTimeValueKindToString(startKind),
                              DateTimeValueKindToString(endKind));
    return false;
  }
  result->kind = startKind;

  // PartitionDateTimeRangePattern, steps 2-3. (Call to HandleDateTimeValue)

  // HandleDateTimeValue, step 1.
  if (startKind != DateTimeValueKind::Number) {
    MOZ_ASSERT(start.isObject());
    MOZ_ASSERT(end.isObject());
    return HandleDateTimeValue(cx, method, dateTimeFormat, &start.toObject(),
                               &result->start) &&
           HandleDateTimeValue(cx, method, dateTimeFormat, &end.toObject(),
                               &result->end);
  }

  // HandleDateTimeValue, step 2.
  return HandleDateTimeOthers(cx, method, startNum, &result->start) &&
         HandleDateTimeOthers(cx, method, endNum, &result->end);
}

bool js::intl::FormatDateTime(JSContext* cx,
                              Handle<DateTimeFormatObject*> dateTimeFormat,
                              double millis, MutableHandle<Value> result) {
  auto x = JS::TimeClip(millis);
  MOZ_ASSERT(x.isValid());

  auto epochMillis = EpochMilliseconds{x};
  auto dateTime = DateTimeValue{epochMillis, DateTimeValueKind::Number};
  return FormatDateTime(cx, dateTimeFormat, dateTime, result);
}

/**
 * Returns a new DateIntervalFormat with the locale and date-time formatting
 * options of the given DateTimeFormat.
 */

static mozilla::intl::DateIntervalFormat* NewDateIntervalFormat(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    mozilla::intl::DateTimeFormat& mozDtf, DateTimeValueKind kind) {
  if (!ResolveLocale(cx, dateTimeFormat)) {
    return nullptr;
  }

  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
  auto result = mozDtf.GetPattern(pattern);
  if (result.isErr()) {
    ReportInternalError(cx, result.unwrapErr());
    return nullptr;
  }

  // Determine the hour cycle used in the resolved pattern.
  auto hcPattern = mozilla::intl::DateTimeFormat::HourCycleFromPattern(pattern);

  auto locale = DateTimeFormatLocale(cx, dateTimeFormat, hcPattern);
  if (!locale) {
    return nullptr;
  }

  TimeZoneChars timeZone(cx);
  if (IsPlainDateTimeValue(kind)) {
    timeZone.setToOffset(TimeZoneOffsetString::GMTZero());
  } else {
    if (!timeZone.init(cx, dateTimeFormat->getTimeZone())) {
      return nullptr;
    }
  }

  FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> skeleton(cx);
  auto skelResult = mozDtf.GetOriginalSkeleton(skeleton);
  if (skelResult.isErr()) {
    ReportInternalError(cx, skelResult.unwrapErr());
    return nullptr;
  }

  auto dif = mozilla::intl::DateIntervalFormat::TryCreate(
      mozilla::MakeStringSpan(locale.get()), skeleton, timeZone);
  if (dif.isErr()) {
    ReportInternalError(cx, dif.unwrapErr());
    return nullptr;
  }
  return dif.unwrap().release();
}

static mozilla::intl::DateIntervalFormat* GetOrCreateDateIntervalFormat(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    mozilla::intl::DateTimeFormat& mozDtf, DateTimeValueKind kind) {
  dateTimeFormat->maybeClearCache(kind);

  // Obtain a cached DateIntervalFormat object.
  if (auto* dif = dateTimeFormat->getDateIntervalFormat()) {
    return dif;
  }

  auto* dif = NewDateIntervalFormat(cx, dateTimeFormat, mozDtf, kind);
  if (!dif) {
    return nullptr;
  }
  dateTimeFormat->setDateIntervalFormat(dif);

  AddICUCellMemory(dateTimeFormat,
                   DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
  return dif;
}

/**
 * PartitionDateTimeRangePattern ( dateTimeFormat, x, y )
 */

static bool PartitionDateTimeRangePattern(
    JSContext* cx, const mozilla::intl::DateTimeFormat* df,
    const mozilla::intl::DateIntervalFormat* dif,
    mozilla::intl::AutoFormattedDateInterval& formatted, EpochMilliseconds x,
    EpochMilliseconds y, bool* equal) {
  auto result =
      dif->TryFormatDateTime(x.toDouble(), y.toDouble(), df, formatted, equal);
  if (result.isErr()) {
    ReportInternalError(cx, result.unwrapErr());
    return false;
  }
  return true;
}

/**
 * FormatDateTimeRange( dateTimeFormat, x, y )
 *
 * Returns a String value representing the range between x and y according to
 * the effective locale and the formatting options of the given DateTimeFormat.
 */

static bool FormatDateTimeRange(JSContext* cx,
                                Handle<DateTimeFormatObject*> dateTimeFormat,
                                const DateTimeRangeValue& values,
                                MutableHandle<JS::Value> result) {
  auto* df = GetOrCreateDateTimeFormat(cx, dateTimeFormat, values.kind);
  if (!df) {
    return false;
  }
  auto* dif =
      GetOrCreateDateIntervalFormat(cx, dateTimeFormat, *df, values.kind);
  if (!dif) {
    return false;
  }

  mozilla::intl::AutoFormattedDateInterval formatted;
  if (!formatted.IsValid()) {
    ReportInternalError(cx, formatted.GetError());
    return false;
  }

  bool equal;
  if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, values.start,
                                     values.end, &equal)) {
    return false;
  }

  // PartitionDateTimeRangePattern, step 12.
  if (equal) {
    return FormatDateTime(cx, df, values.start, result);
  }

  auto spanResult = formatted.ToSpan();
  if (spanResult.isErr()) {
    ReportInternalError(cx, spanResult.unwrapErr());
    return false;
  }

  auto* resultStr = NewStringCopy<CanGC>(cx, spanResult.unwrap());
  if (!resultStr) {
    return false;
  }
  result.setString(resultStr);
  return true;
}

/**
 * FormatDateTimeRangeToParts ( dateTimeFormat, x, y )
 */

static bool FormatDateTimeRangeToParts(
    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
    const DateTimeRangeValue& values, MutableHandle<JS::Value> result) {
  auto* df = GetOrCreateDateTimeFormat(cx, dateTimeFormat, values.kind);
  if (!df) {
    return false;
  }
  auto* dif =
      GetOrCreateDateIntervalFormat(cx, dateTimeFormat, *df, values.kind);
  if (!dif) {
    return false;
  }

  mozilla::intl::AutoFormattedDateInterval formatted;
  if (!formatted.IsValid()) {
    ReportInternalError(cx, formatted.GetError());
    return false;
  }

  bool equal;
  if (!PartitionDateTimeRangePattern(cx, df, dif, formatted, values.start,
                                     values.end, &equal)) {
    return false;
  }

  // PartitionDateTimeRangePattern, step 12.
  if (equal) {
    return FormatDateTimeToParts(cx, df, values.start, DateTimeSource::Yes,
                                 result);
  }

  mozilla::intl::DateTimePartVector parts;
  auto r = dif->TryFormattedToParts(formatted, parts);
  if (r.isErr()) {
    ReportInternalError(cx, r.unwrapErr());
    return false;
  }

  auto spanResult = formatted.ToSpan();
  if (spanResult.isErr()) {
    ReportInternalError(cx, spanResult.unwrapErr());
    return false;
  }
  return CreateDateTimePartArray(cx, spanResult.unwrap(), DateTimeSource::Yes,
                                 parts, result);
}

static bool IsDateTimeFormat(Handle<JS::Value> v) {
  return v.isObject() && v.toObject().is<DateTimeFormatObject>();
}

/**
 * UnwrapDateTimeFormat ( dtf )
 */

static bool UnwrapDateTimeFormat(JSContext* cx, MutableHandle<JS::Value> dtf) {
  // Step 1. (Error handling moved to caller)
  if (!dtf.isObject()) {
    return true;
  }

  auto* obj = &dtf.toObject();
  if (obj->canUnwrapAs<DateTimeFormatObject>()) {
    return true;
  }

  Rooted<JSObject*> format(cx, obj);
  return UnwrapLegacyIntlFormat(cx, JSProto_DateTimeFormat, format, dtf);
}

static constexpr uint32_t DateTimeFormatFunction_DateTimeFormat = 0;

/**
 * DateTime Format Functions
 */

static bool DateTimeFormatFunction(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // Steps 1-2.
  auto* format = &args.callee().as<JSFunction>();
  auto dtfValue =
      format->getExtendedSlot(DateTimeFormatFunction_DateTimeFormat);
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, &dtfValue.toObject().as<DateTimeFormatObject>());

  // Steps 3-4.
  DateTimeValue x;
  if (!ToDateTimeValue(cx, "format", dateTimeFormat, args.get(0), &x)) {
    return false;
  }

  // Step 5.
  return FormatDateTime(cx, dateTimeFormat, x, args.rval());
}

/**
 * get Intl.DateTimeFormat.prototype.format
 */

static bool dateTimeFormat_format(JSContext* cx, const CallArgs& args) {
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, &args.thisv().toObject().as<DateTimeFormatObject>());

  // Step 4.
  auto* boundFormat = dateTimeFormat->getBoundFormat();
  if (!boundFormat) {
    Handle<PropertyName*> funName = cx->names().empty_;
    auto* fn =
        NewNativeFunction(cx, DateTimeFormatFunction, 1, funName,
                          gc::AllocKind::FUNCTION_EXTENDED, GenericObject);
    if (!fn) {
      return false;
    }
    fn->initExtendedSlot(DateTimeFormatFunction_DateTimeFormat,
                         ObjectValue(*dateTimeFormat));

    dateTimeFormat->setBoundFormat(fn);
    boundFormat = fn;
  }

  // Step 5.
  args.rval().setObject(*boundFormat);
  return true;
}

/**
 * get Intl.DateTimeFormat.prototype.format
 */

static bool dateTimeFormat_format(JSContext* cx, unsigned argc, Value* vp) {
  // Steps 1-3.
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!UnwrapDateTimeFormat(cx, args.mutableThisv())) {
    return false;
  }
  return CallNonGenericMethod<IsDateTimeFormat, dateTimeFormat_format>(cx,
                                                                       args);
}

/**
 * Intl.DateTimeFormat.prototype.formatToParts ( date )
 */

static bool dateTimeFormat_formatToParts(JSContext* cx, const CallArgs& args) {
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, &args.thisv().toObject().as<DateTimeFormatObject>());

  // Steps 3-4.
  DateTimeValue x;
  if (!ToDateTimeValue(cx, "formatRange", dateTimeFormat, args.get(0), &x)) {
    return false;
  }

  // Step 5.
  return FormatDateTimeToParts(cx, dateTimeFormat, x, args.rval());
}

/**
 * Intl.DateTimeFormat.prototype.formatToParts ( date )
 */

static bool dateTimeFormat_formatToParts(JSContext* cx, unsigned argc,
                                         Value* vp) {
  // Steps 1-2.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsDateTimeFormat, dateTimeFormat_formatToParts>(
      cx, args);
}

/**
 * Intl.DateTimeFormat.prototype.formatRange ( startDate, endDate )
 */

static bool dateTimeFormat_formatRange(JSContext* cx, const CallArgs& args) {
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, &args.thisv().toObject().as<DateTimeFormatObject>());

  // Steps 3-5.
  DateTimeRangeValue values;
  if (!ToDateTimeRangeValue(cx, "formatRange", dateTimeFormat, args.get(0),
                            args.get(1), &values)) {
    return false;
  }

  // Step 6.
  return FormatDateTimeRange(cx, dateTimeFormat, values, args.rval());
}

/**
 * Intl.DateTimeFormat.prototype.formatRange ( startDate, endDate )
 */

static bool dateTimeFormat_formatRange(JSContext* cx, unsigned argc,
                                       Value* vp) {
  // Steps 1-2.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsDateTimeFormat, dateTimeFormat_formatRange>(
      cx, args);
}

/**
 * Intl.DateTimeFormat.prototype.formatRangeToParts ( date )
 */

static bool dateTimeFormat_formatRangeToParts(JSContext* cx,
                                              const CallArgs& args) {
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, &args.thisv().toObject().as<DateTimeFormatObject>());

  // Steps 3-5.
  DateTimeRangeValue values;
  if (!ToDateTimeRangeValue(cx, "formatRangeToParts", dateTimeFormat,
                            args.get(0), args.get(1), &values)) {
    return false;
  }

  // Step 6.
  return FormatDateTimeRangeToParts(cx, dateTimeFormat, values, args.rval());
}

/**
 * Intl.DateTimeFormat.prototype.formatRangeToParts ( date )
 */

static bool dateTimeFormat_formatRangeToParts(JSContext* cx, unsigned argc,
                                              Value* vp) {
  // Steps 1-2.
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsDateTimeFormat,
                              dateTimeFormat_formatRangeToParts>(cx, args);
}

/**
 * Intl.DateTimeFormat.prototype.resolvedOptions ( )
 */

static bool dateTimeFormat_resolvedOptions(JSContext* cx,
                                           const CallArgs& args) {
  Rooted<DateTimeFormatObject*> dateTimeFormat(
      cx, &args.thisv().toObject().as<DateTimeFormatObject>());

  if (!ResolveLocale(cx, dateTimeFormat)) {
    return false;
  }
  auto dtfOptions = dateTimeFormat->getOptions();

  // Step 3.
  Rooted<IdValueVector> options(cx, cx);

  // Step 4.
  if (!options.emplaceBack(NameToId(cx->names().locale),
                           StringValue(dateTimeFormat->getLocale()))) {
    return false;
  }

  auto* calendar = ResolveCalendar(cx, dateTimeFormat);
  if (!calendar) {
    return false;
  }
  if (!options.emplaceBack(NameToId(cx->names().calendar),
                           StringValue(calendar))) {
    return false;
  }

  auto* numberingSystem = ResolveNumberingSystem(cx, dateTimeFormat);
  if (!numberingSystem) {
    return false;
  }
  if (!options.emplaceBack(NameToId(cx->names().numberingSystem),
                           StringValue(numberingSystem))) {
    return false;
  }

  if (!options.emplaceBack(NameToId(cx->names().timeZone),
                           StringValue(dateTimeFormat->getTimeZone()))) {
    return false;
  }

  // The raw pattern option is only internal to Mozilla, and not part of the
  // ECMA-402 API.
  if (auto* pattern = dateTimeFormat->getPattern()) {
    if (!options.emplaceBack(NameToId(cx->names().pattern),
                             StringValue(pattern))) {
      return false;
    }
  }

  bool hasDateStyle = dtfOptions.dateStyle.isSome();
  bool hasTimeStyle = dtfOptions.timeStyle.isSome();

  if (hasDateStyle || hasTimeStyle) {
    if (hasTimeStyle) {
      // timeStyle (unlike dateStyle) requires resolving the pattern to ensure
      // "hourCycle" and "hour12" properties are added to |result|.
      if (!ResolveDateTimeFormatComponents(cx, dateTimeFormat, &options,
                                           IncludeDateTimeFields::No)) {
        return false;
      }
    }

    if (hasDateStyle) {
      auto* dateStyle =
          NewStringCopy<CanGC>(cx, DateStyleToString(*dtfOptions.dateStyle));
      if (!dateStyle) {
        return false;
      }
      if (!options.emplaceBack(NameToId(cx->names().dateStyle),
                               StringValue(dateStyle))) {
        return false;
      }
    }

    if (hasTimeStyle) {
      auto* timeStyle =
          NewStringCopy<CanGC>(cx, TimeStyleToString(*dtfOptions.timeStyle));
      if (!timeStyle) {
        return false;
      }
      if (!options.emplaceBack(NameToId(cx->names().timeStyle),
                               StringValue(timeStyle))) {
        return false;
      }
    }
  } else {
    if (!ResolveDateTimeFormatComponents(cx, dateTimeFormat, &options,
                                         IncludeDateTimeFields::Yes)) {
      return false;
    }
  }

  // Step 6.
  auto* result = NewPlainObjectWithUniqueNames(cx, options);
  if (!result) {
    return false;
  }
  args.rval().setObject(*result);
  return true;
}

/**
 * Intl.DateTimeFormat.prototype.resolvedOptions ( )
 */

static bool dateTimeFormat_resolvedOptions(JSContext* cx, unsigned argc,
                                           Value* vp) {
  // Steps 1-2.
  CallArgs args = CallArgsFromVp(argc, vp);
  if (!UnwrapDateTimeFormat(cx, args.mutableThisv())) {
    return false;
  }
  return CallNonGenericMethod<IsDateTimeFormat, dateTimeFormat_resolvedOptions>(
      cx, args);
}

/**
 * Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] )
 */

static bool dateTimeFormat_supportedLocalesOf(JSContext* cx, unsigned argc,
                                              Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // Steps 1-3.
  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::DateTimeFormat,
                                   args.get(0), args.get(1));
  if (!array) {
    return false;
  }
  args.rval().setObject(*array);
  return true;
}

bool js::intl::TemporalObjectToLocaleString(
    JSContext* cx, const CallArgs& args, DateTimeFormatKind formatKind,
    Handle<JSLinearString*> toLocaleStringTimeZone) {
  Rooted<JSObject*> thisValue(cx, &args.thisv().toObject());

  auto kind = ToDateTimeFormattable(args.thisv());
  MOZ_ASSERT(kind != DateTimeValueKind::Number);
  MOZ_ASSERT_IF(kind != DateTimeValueKind::TemporalZonedDateTime,
                toLocaleStringTimeZone == nullptr);
  MOZ_ASSERT_IF(kind == DateTimeValueKind::TemporalZonedDateTime,
                toLocaleStringTimeZone != nullptr);

  auto locales = args.get(0);
  auto options = args.get(1);

  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
  if (kind != DateTimeValueKind::TemporalZonedDateTime) {
    dateTimeFormat =
        GetOrCreateDateTimeFormat(cx, locales, options, formatKind);
  } else {
    // Cache doesn't yet support Temporal.ZonedDateTime.
    dateTimeFormat = ::CreateDateTimeFormat(cx, locales, options,
                                            toLocaleStringTimeZone, formatKind);
  }
  if (!dateTimeFormat) {
    return false;
  }

  EpochMilliseconds x;
  if (kind == DateTimeValueKind::TemporalZonedDateTime) {
    auto zonedDateTime = thisValue.as<ZonedDateTimeObject>();
    if (!HandleDateTimeTemporalZonedDateTime(cx, dateTimeFormat, zonedDateTime,
                                             &x)) {
      return false;
    }
  } else {
    if (!HandleDateTimeValue(cx, "toLocaleString", dateTimeFormat, thisValue,
                             &x)) {
      return false;
    }
  }

  return FormatDateTime(cx, dateTimeFormat, {x, kind}, args.rval());
}

Messung V0.5 in Prozent
C=87 H=100 G=93

¤ Dauer der Verarbeitung: 0.65 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.