Bug 1719696 - Use a components bag for Intl.DateTimeFormat implementation; r=anba
authorGreg Tatum <gtatum@mozilla.com>
Mon, 13 Sep 2021 20:53:00 +0000
changeset 591823 b23f891ed375c6cd9974fbc4e3de8a0b285dcb3d
parent 591822 0b5d9fdd57a2fc4207dd8aff99e58fd73c3150f3
child 591824 268ea13a74e762a01bd0fe85049b7e2390ec9d43
push id149645
push usergtatum@mozilla.com
push dateMon, 13 Sep 2021 20:56:14 +0000
treeherderautoland@f785d3c64243 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba
bugs1719696
milestone94.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1719696 - Use a components bag for Intl.DateTimeFormat implementation; r=anba This patch completes the move of pattern manipulation for Intl.DateTimeFormat and places it in the unified API. If and when we switch to ICU4X, the pattern manipulation code can be removed, as ICU4X will handle that. Differential Revision: https://phabricator.services.mozilla.com/D123823
js/src/builtin/intl/DateTimeFormat.cpp
js/src/builtin/intl/DateTimeFormat.h
js/src/builtin/intl/DateTimeFormat.js
js/src/vm/CommonPropertyNames.h
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -55,17 +55,16 @@ using JS::ClippedTime;
 using JS::TimeClip;
 
 using js::intl::CallICU;
 using js::intl::DateTimeFormatOptions;
 using js::intl::FormatBuffer;
 using js::intl::IcuLocale;
 using js::intl::INITIAL_CHAR_BUFFER_SIZE;
 using js::intl::SharedIntlData;
-using js::intl::StringsAreEqual;
 
 const JSClassOps DateTimeFormatObject::classOps_ = {
     nullptr,                         // addProperty
     nullptr,                         // delProperty
     nullptr,                         // enumerate
     nullptr,                         // newEnumerate
     nullptr,                         // resolve
     nullptr,                         // mayResolve
@@ -508,238 +507,20 @@ enum class HourCycle {
 
   // 24 hour cycle, from 0 to 23.
   H23,
 
   // 24 hour cycle, from 1 to 24.
   H24
 };
 
-/**
- * Return the hour cycle for the given option string.
- */
-static HourCycle HourCycleFromOption(JSLinearString* str) {
-  if (StringEqualsLiteral(str, "h11")) {
-    return HourCycle::H11;
-  }
-  if (StringEqualsLiteral(str, "h12")) {
-    return HourCycle::H12;
-  }
-  if (StringEqualsLiteral(str, "h23")) {
-    return HourCycle::H23;
-  }
-  MOZ_ASSERT(StringEqualsLiteral(str, "h24"));
-  return HourCycle::H24;
-}
-
-bool js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) {
-  CallArgs args = CallArgsFromVp(argc, vp);
-  MOZ_ASSERT(args.length() == 3);
-  MOZ_ASSERT(args[0].isString());
-  MOZ_ASSERT(args[1].isString());
-  MOZ_ASSERT(args[2].isString() || args[2].isUndefined());
-
-  UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
-  if (!locale) {
-    return false;
-  }
-
-  AutoStableStringChars skeleton(cx);
-  if (!skeleton.initTwoByte(cx, args[1].toString())) {
-    return false;
-  }
-
-  mozilla::Maybe<HourCycle> hourCycle;
-  if (args[2].isString()) {
-    JSLinearString* hourCycleStr = args[2].toString()->ensureLinear(cx);
-    if (!hourCycleStr) {
-      return false;
-    }
-
-    hourCycle.emplace(HourCycleFromOption(hourCycleStr));
-  }
-
-  mozilla::Range<const char16_t> skelChars = skeleton.twoByteRange();
-
-  SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
-  mozilla::intl::DateTimePatternGenerator* gen =
-      sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
-  if (!gen) {
-    return false;
-  }
-
-  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
-  auto options = PatternMatchOptions(skelChars);
-  auto result = gen->GetBestPattern(skelChars, pattern, options);
-  if (result.isErr()) {
-    intl::ReportInternalError(cx, result.unwrapErr());
-    return false;
-  }
-
-  // If the hourCycle option was set, adjust the resolved pattern to use the
-  // requested hour cycle representation.
-  if (hourCycle) {
-    ReplaceHourSymbol(pattern, hourCycle.value());
-  }
-
-  JSString* str = pattern.toString();
-  if (!str) {
-    return false;
-  }
-  args.rval().setString(str);
-  return true;
-}
-
-bool js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) {
-  CallArgs args = CallArgsFromVp(argc, vp);
-  MOZ_ASSERT(args.length() == 6);
-  MOZ_ASSERT(args[0].isString());
-  MOZ_ASSERT(args[1].isString() || args[1].isUndefined());
-  MOZ_ASSERT(args[2].isString() || args[2].isUndefined());
-  MOZ_ASSERT(args[3].isString());
-  MOZ_ASSERT(args[4].isBoolean() || args[4].isUndefined());
-  MOZ_ASSERT(args[5].isString() || args[5].isUndefined());
-
-  UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
-  if (!locale) {
-    return false;
-  }
-
-  auto toDateFormatStyle = [](JSLinearString* str) {
-    if (StringEqualsLiteral(str, "full")) {
-      return mozilla::intl::DateTimeStyle::Full;
-    }
-    if (StringEqualsLiteral(str, "long")) {
-      return mozilla::intl::DateTimeStyle::Long;
-    }
-    if (StringEqualsLiteral(str, "medium")) {
-      return mozilla::intl::DateTimeStyle::Medium;
-    }
-    MOZ_ASSERT(StringEqualsLiteral(str, "short"));
-    return mozilla::intl::DateTimeStyle::Short;
-  };
-
-  auto dateStyle = mozilla::intl::DateTimeStyle::None;
-  if (args[1].isString()) {
-    JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx);
-    if (!dateStyleStr) {
-      return false;
-    }
-
-    dateStyle = toDateFormatStyle(dateStyleStr);
-  }
-
-  auto timeStyle = mozilla::intl::DateTimeStyle::None;
-  if (args[2].isString()) {
-    JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx);
-    if (!timeStyleStr) {
-      return false;
-    }
-
-    timeStyle = toDateFormatStyle(timeStyleStr);
-  }
-
-  AutoStableStringChars timeZone(cx);
-  if (!timeZone.initTwoByte(cx, args[3].toString())) {
-    return false;
-  }
-
-  mozilla::Maybe<bool> hour12;
-  if (args[4].isBoolean()) {
-    hour12.emplace(args[4].toBoolean());
-  }
-
-  mozilla::Maybe<HourCycle> hourCycle;
-  if (args[5].isString()) {
-    JSLinearString* hourCycleStr = args[5].toString()->ensureLinear(cx);
-    if (!hourCycleStr) {
-      return false;
-    }
-
-    hourCycle.emplace(HourCycleFromOption(hourCycleStr));
-  }
-
-  mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
-
-  auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromStyle(
-      mozilla::MakeStringSpan(IcuLocale(locale.get())), timeStyle, dateStyle,
-      mozilla::Some(timeZoneChars));
-  if (dfResult.isErr()) {
-    intl::ReportInternalError(cx);
-    return false;
-  }
-
-  auto df = dfResult.unwrap();
-
-  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
-  auto patternResult = df->GetPattern(pattern);
-  if (patternResult.isErr()) {
-    intl::ReportInternalError(cx, patternResult.unwrapErr());
-    return false;
-  }
-
-  // If a specific hour cycle was requested and this hour cycle doesn't match
-  // the hour cycle used in the resolved pattern, find an equivalent pattern
-  // with the correct hour cycle.
-  if (timeStyle != mozilla::intl::DateTimeStyle::None &&
-      (hour12 || hourCycle)) {
-    if (auto hcPattern = HourCycleFromPattern<char16_t>(pattern)) {
-      bool wantHour12 = hour12 ? hour12.value() : IsHour12(hourCycle.value());
-      if (wantHour12 != IsHour12(hcPattern.value())) {
-        if (!FindPatternWithHourCycle(cx, locale.get(), pattern, wantHour12)) {
-          return false;
-        }
-      }
-    }
-  }
-
-  // If the hourCycle option was set, adjust the resolved pattern to use the
-  // requested hour cycle representation.
-  if (hourCycle) {
-    ReplaceHourSymbol(pattern, hourCycle.value());
-  }
-
-  JSString* str = pattern.toString();
-  if (!str) {
-    return false;
-  }
-  args.rval().setString(str);
-  return true;
-}
-
-bool js::intl_skeletonForPattern(JSContext* cx, unsigned argc, Value* vp) {
-  CallArgs args = CallArgsFromVp(argc, vp);
-  MOZ_ASSERT(args.length() == 1);
-  MOZ_ASSERT(args[0].isString());
-
-  AutoStableStringChars pattern(cx);
-  if (!pattern.initTwoByte(cx, args[0].toString())) {
-    return false;
-  }
-
-  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx);
-  auto result = mozilla::intl::DateTimePatternGenerator::GetSkeleton(
-      pattern.twoByteRange(), skeleton);
-  if (result.isErr()) {
-    intl::ReportInternalError(cx, result.unwrapErr());
-    return false;
-  }
-
-  JSString* str = skeleton.toString();
-  if (!str) {
-    return false;
-  }
-
-  args.rval().setString(str);
-  return true;
-}
-
 static UniqueChars DateTimeFormatLocale(
     JSContext* cx, HandleObject internals,
-    mozilla::Maybe<HourCycle> hourCycle = mozilla::Nothing()) {
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hourCycle =
+        mozilla::Nothing()) {
   RootedValue value(cx);
   if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
     return nullptr;
   }
 
   // ICU expects calendar, numberingSystem, and hourCycle as Unicode locale
   // extensions on locale.
 
@@ -786,26 +567,26 @@ static UniqueChars DateTimeFormatLocale(
     if (!keywords.emplaceBack("nu", numberingSystem)) {
       return nullptr;
     }
   }
 
   if (hourCycle) {
     JSAtom* hourCycleStr;
     switch (*hourCycle) {
-      case HourCycle::H11:
+      case mozilla::intl::DateTimeFormat::HourCycle::H11:
         hourCycleStr = cx->names().h11;
         break;
-      case HourCycle::H12:
+      case mozilla::intl::DateTimeFormat::HourCycle::H12:
         hourCycleStr = cx->names().h12;
         break;
-      case HourCycle::H23:
+      case mozilla::intl::DateTimeFormat::HourCycle::H23:
         hourCycleStr = cx->names().h23;
         break;
-      case HourCycle::H24:
+      case mozilla::intl::DateTimeFormat::HourCycle::H24:
         hourCycleStr = cx->names().h24;
         break;
     }
 
     if (!keywords.emplaceBack("hc", hourCycleStr)) {
       return nullptr;
     }
   }
@@ -816,16 +597,218 @@ static UniqueChars DateTimeFormatLocale(
   // should be ignored.
   if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords)) {
     return nullptr;
   }
 
   return tag.toStringZ(cx);
 }
 
+static bool AssignTextComponent(
+    JSContext* cx, HandleObject internals, HandlePropertyName property,
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::Text>* text) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, property, &value)) {
+    return false;
+  }
+
+  if (value.isString()) {
+    JSLinearString* string = value.toString()->ensureLinear(cx);
+    if (!string) {
+      return false;
+    }
+    if (StringEqualsLiteral(string, "narrow")) {
+      *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Narrow);
+    } else if (StringEqualsLiteral(string, "short")) {
+      *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Short);
+    } else {
+      MOZ_ASSERT(StringEqualsLiteral(string, "long"));
+      *text = mozilla::Some(mozilla::intl::DateTimeFormat::Text::Long);
+    }
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
+static bool AssignNumericComponent(
+    JSContext* cx, HandleObject internals, HandlePropertyName property,
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::Numeric>* numeric) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, property, &value)) {
+    return false;
+  }
+
+  if (value.isString()) {
+    JSLinearString* string = value.toString()->ensureLinear(cx);
+    if (!string) {
+      return false;
+    }
+    if (StringEqualsLiteral(string, "numeric")) {
+      *numeric = mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::Numeric);
+    } else {
+      MOZ_ASSERT(StringEqualsLiteral(string, "2-digit"));
+      *numeric =
+          mozilla::Some(mozilla::intl::DateTimeFormat::Numeric::TwoDigit);
+    }
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
+static bool AssignMonthComponent(
+    JSContext* cx, HandleObject internals, HandlePropertyName property,
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::Month>* month) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, property, &value)) {
+    return false;
+  }
+
+  if (value.isString()) {
+    JSLinearString* string = value.toString()->ensureLinear(cx);
+    if (!string) {
+      return false;
+    }
+    if (StringEqualsLiteral(string, "numeric")) {
+      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Numeric);
+    } else if (StringEqualsLiteral(string, "2-digit")) {
+      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::TwoDigit);
+    } else if (StringEqualsLiteral(string, "long")) {
+      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Long);
+    } else if (StringEqualsLiteral(string, "short")) {
+      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Short);
+    } else {
+      MOZ_ASSERT(StringEqualsLiteral(string, "narrow"));
+      *month = mozilla::Some(mozilla::intl::DateTimeFormat::Month::Narrow);
+    }
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
+static bool AssignTimeZoneNameComponent(
+    JSContext* cx, HandleObject internals, HandlePropertyName property,
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::TimeZoneName>* tzName) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, property, &value)) {
+    return false;
+  }
+
+  if (value.isString()) {
+    JSLinearString* string = value.toString()->ensureLinear(cx);
+    if (!string) {
+      return false;
+    }
+    if (StringEqualsLiteral(string, "long")) {
+      *tzName =
+          mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Long);
+    } else if (StringEqualsLiteral(string, "short")) {
+      *tzName =
+          mozilla::Some(mozilla::intl::DateTimeFormat::TimeZoneName::Short);
+    } else if (StringEqualsLiteral(string, "shortOffset")) {
+      *tzName = mozilla::Some(
+          mozilla::intl::DateTimeFormat::TimeZoneName::ShortOffset);
+    } else if (StringEqualsLiteral(string, "longOffset")) {
+      *tzName = mozilla::Some(
+          mozilla::intl::DateTimeFormat::TimeZoneName::LongOffset);
+    } else if (StringEqualsLiteral(string, "shortGeneric")) {
+      *tzName = mozilla::Some(
+          mozilla::intl::DateTimeFormat::TimeZoneName::ShortGeneric);
+    } else {
+      MOZ_ASSERT(StringEqualsLiteral(string, "longGeneric"));
+      *tzName = mozilla::Some(
+          mozilla::intl::DateTimeFormat::TimeZoneName::LongGeneric);
+    }
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
+static bool AssignHourCycleComponent(
+    JSContext* cx, HandleObject internals, HandlePropertyName property,
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle>* hourCycle) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, property, &value)) {
+    return false;
+  }
+
+  if (value.isString()) {
+    JSLinearString* string = value.toString()->ensureLinear(cx);
+    if (!string) {
+      return false;
+    }
+    if (StringEqualsLiteral(string, "h11")) {
+      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H11);
+    } else if (StringEqualsLiteral(string, "h12")) {
+      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H12);
+    } else if (StringEqualsLiteral(string, "h23")) {
+      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H23);
+    } else {
+      MOZ_ASSERT(StringEqualsLiteral(string, "h24"));
+      *hourCycle = mozilla::Some(mozilla::intl::DateTimeFormat::HourCycle::H24);
+    }
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
+static bool AssignHour12Component(JSContext* cx, HandleObject internals,
+                                  mozilla::Maybe<bool>* hour12) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, cx->names().hour12, &value)) {
+    return false;
+  }
+  if (value.isBoolean()) {
+    *hour12 = mozilla::Some(value.toBoolean());
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
+static bool AssignDateTimeLength(
+    JSContext* cx, HandleObject internals, HandlePropertyName property,
+    mozilla::Maybe<mozilla::intl::DateTimeFormat::Style>* style) {
+  RootedValue value(cx);
+  if (!GetProperty(cx, internals, internals, property, &value)) {
+    return false;
+  }
+
+  if (value.isString()) {
+    JSLinearString* string = value.toString()->ensureLinear(cx);
+    if (!string) {
+      return false;
+    }
+    if (StringEqualsLiteral(string, "full")) {
+      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Full);
+    } else if (StringEqualsLiteral(string, "long")) {
+      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
+    } else if (StringEqualsLiteral(string, "medium")) {
+      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Medium);
+    } else {
+      MOZ_ASSERT(StringEqualsLiteral(string, "short"));
+      *style = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+    }
+  } else {
+    MOZ_ASSERT(value.isUndefined());
+  }
+
+  return true;
+}
+
 /**
  * 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) {
   RootedValue value(cx);
 
@@ -848,38 +831,290 @@ static mozilla::intl::DateTimeFormat* Ne
     return nullptr;
   }
 
   mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
 
   if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) {
     return nullptr;
   }
+  bool hasPattern = value.isString();
 
-  AutoStableStringChars pattern(cx);
-  if (!pattern.initTwoByte(cx, value.toString())) {
+  if (!GetProperty(cx, internals, internals, cx->names().timeStyle, &value)) {
     return nullptr;
   }
+  bool hasStyle = value.isString();
+  if (!hasStyle) {
+    if (!GetProperty(cx, internals, internals, cx->names().dateStyle, &value)) {
+      return nullptr;
+    }
+    hasStyle = value.isString();
+  }
 
-  auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromPattern(
-      mozilla::MakeStringSpan(IcuLocale(locale.get())), pattern.twoByteRange(),
-      mozilla::Some(timeZoneChars));
-  if (dfResult.isErr()) {
-    intl::ReportInternalError(cx);
-    return nullptr;
+  mozilla::UniquePtr<mozilla::intl::DateTimeFormat> df = nullptr;
+  if (hasPattern) {
+    // This is a DateTimeFormat defined by a pattern option. This is internal
+    // to Mozilla, and not part of the ECMA-402 API.
+    if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) {
+      return nullptr;
+    }
+
+    AutoStableStringChars pattern(cx);
+    if (!pattern.initTwoByte(cx, value.toString())) {
+      return nullptr;
+    }
+
+    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromPattern(
+        mozilla::MakeStringSpan(IcuLocale(locale.get())),
+        pattern.twoByteRange(), mozilla::Some(timeZoneChars));
+    if (dfResult.isErr()) {
+      intl::ReportInternalError(cx, dfResult.unwrapErr());
+      return nullptr;
+    }
+
+    df = dfResult.unwrap();
+  } else if (hasStyle) {
+    // This is a DateTimeFormat defined by a time style or date style.
+    mozilla::intl::DateTimeFormat::StyleBag style;
+    if (!AssignDateTimeLength(cx, internals, cx->names().timeStyle,
+                              &style.time)) {
+      return nullptr;
+    }
+    if (!AssignDateTimeLength(cx, internals, cx->names().dateStyle,
+                              &style.date)) {
+      return nullptr;
+    }
+    if (!AssignHourCycleComponent(cx, internals, cx->names().hourCycle,
+                                  &style.hourCycle)) {
+      return nullptr;
+    }
+
+    if (!AssignHour12Component(cx, internals, &style.hour12)) {
+      return nullptr;
+    }
+
+    SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
+
+    mozilla::intl::DateTimePatternGenerator* gen =
+        sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
+    if (!gen) {
+      return nullptr;
+    }
+    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromStyle(
+        mozilla::MakeStringSpan(IcuLocale(locale.get())), style, gen,
+        mozilla::Some(timeZoneChars));
+    if (dfResult.isErr()) {
+      intl::ReportInternalError(cx, dfResult.unwrapErr());
+      return nullptr;
+    }
+    df = dfResult.unwrap();
+  } else {
+    // This is a DateTimeFormat defined by a components bag.
+    mozilla::intl::DateTimeFormat::ComponentsBag bag;
+
+    if (!AssignTextComponent(cx, internals, cx->names().era, &bag.era)) {
+      return nullptr;
+    }
+    if (!AssignNumericComponent(cx, internals, cx->names().year, &bag.year)) {
+      return nullptr;
+    }
+    if (!AssignMonthComponent(cx, internals, cx->names().month, &bag.month)) {
+      return nullptr;
+    }
+    if (!AssignNumericComponent(cx, internals, cx->names().day, &bag.day)) {
+      return nullptr;
+    }
+    if (!AssignTextComponent(cx, internals, cx->names().weekday,
+                             &bag.weekday)) {
+      return nullptr;
+    }
+    if (!AssignNumericComponent(cx, internals, cx->names().hour, &bag.hour)) {
+      return nullptr;
+    }
+    if (!AssignNumericComponent(cx, internals, cx->names().minute,
+                                &bag.minute)) {
+      return nullptr;
+    }
+    if (!AssignNumericComponent(cx, internals, cx->names().second,
+                                &bag.second)) {
+      return nullptr;
+    }
+    if (!AssignTimeZoneNameComponent(cx, internals, cx->names().timeZoneName,
+                                     &bag.timeZoneName)) {
+      return nullptr;
+    }
+    if (!AssignHourCycleComponent(cx, internals, cx->names().hourCycle,
+                                  &bag.hourCycle)) {
+      return nullptr;
+    }
+    if (!AssignTextComponent(cx, internals, cx->names().dayPeriod,
+                             &bag.dayPeriod)) {
+      return nullptr;
+    }
+    if (!AssignHour12Component(cx, internals, &bag.hour12)) {
+      return nullptr;
+    }
+
+    if (!GetProperty(cx, internals, internals,
+                     cx->names().fractionalSecondDigits, &value)) {
+      return nullptr;
+    }
+    if (value.isInt32()) {
+      bag.fractionalSecondDigits = mozilla::Some(value.toInt32());
+    } else {
+      MOZ_ASSERT(value.isUndefined());
+    }
+
+    SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
+    auto* dtpg = sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
+    if (!dtpg) {
+      return nullptr;
+    }
+
+    auto dfResult = mozilla::intl::DateTimeFormat::TryCreateFromComponents(
+        mozilla::MakeStringSpan(IcuLocale(locale.get())), bag, dtpg,
+        mozilla::Some(timeZoneChars));
+    if (dfResult.isErr()) {
+      intl::ReportInternalError(cx, dfResult.unwrapErr());
+      return nullptr;
+    }
+    df = dfResult.unwrap();
   }
-  auto df = dfResult.unwrap();
 
   // ECMAScript requires the Gregorian calendar to be used from the beginning
   // of ECMAScript time.
   df->SetStartTimeIfGregorian(StartOfTime);
 
   return df.release();
 }
 
+template <typename T>
+static bool SetResolvedProperty(JSContext* cx, HandleObject resolved,
+                                HandlePropertyName name,
+                                mozilla::Maybe<T> intlProp) {
+  if (!intlProp) {
+    return true;
+  }
+  JSString* str = NewStringCopyZ<CanGC>(
+      cx, mozilla::intl::DateTimeFormat::ToString(*intlProp));
+  if (!str) {
+    return false;
+  }
+  RootedValue value(cx, StringValue(str));
+  return DefineDataProperty(cx, resolved, name, value);
+}
+
+bool js::intl_resolveDateTimeFormatComponents(JSContext* cx, unsigned argc,
+                                              Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() == 3);
+  MOZ_ASSERT(args[0].isObject());
+  MOZ_ASSERT(args[1].isObject());
+  MOZ_ASSERT(args[2].isBoolean());
+
+  Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
+  dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
+
+  RootedObject resolved(cx, &args[1].toObject());
+
+  bool includeDateTimeFields = args[2].toBoolean();
+
+  // Obtain a cached mozilla::intl::DateTimeFormat object.
+  mozilla::intl::DateTimeFormat* df = dateTimeFormat->getDateFormat();
+  if (!df) {
+    df = NewDateTimeFormat(cx, dateTimeFormat);
+    if (!df) {
+      return false;
+    }
+    dateTimeFormat->setDateFormat(df);
+
+    intl::AddICUCellMemory(dateTimeFormat,
+                           DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
+  }
+
+  auto result = df->ResolveComponents();
+  if (result.isErr()) {
+    intl::ReportInternalError(cx, result.unwrapErr());
+    return false;
+  }
+
+  mozilla::intl::DateTimeFormat::ComponentsBag 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 (!SetResolvedProperty(cx, resolved, cx->names().hourCycle,
+                           components.hourCycle)) {
+    return false;
+  }
+
+  if (components.hour12) {
+    RootedValue value(cx, BooleanValue(*components.hour12));
+    if (!DefineDataProperty(cx, resolved, cx->names().hour12, value)) {
+      return false;
+    }
+  }
+
+  if (!includeDateTimeFields) {
+    args.rval().setUndefined();
+    // Do not include date time fields.
+    return true;
+  }
+
+  if (!SetResolvedProperty(cx, resolved, cx->names().weekday,
+                           components.weekday)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().era, components.era)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().year, components.year)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().month, components.month)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().day, components.day)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().dayPeriod,
+                           components.dayPeriod)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().hour, components.hour)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().minute,
+                           components.minute)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().second,
+                           components.second)) {
+    return false;
+  }
+  if (!SetResolvedProperty(cx, resolved, cx->names().timeZoneName,
+                           components.timeZoneName)) {
+    return false;
+  }
+
+  if (components.fractionalSecondDigits) {
+    RootedValue value(cx, Int32Value(*components.fractionalSecondDigits));
+    if (!DefineDataProperty(cx, resolved, cx->names().fractionalSecondDigits,
+                            value)) {
+      return false;
+    }
+  }
+
+  args.rval().setUndefined();
+  return true;
+}
+
 static bool intl_FormatDateTime(JSContext* cx,
                                 const mozilla::intl::DateTimeFormat* df,
                                 ClippedTime x, MutableHandleValue result) {
   MOZ_ASSERT(x.isValid());
 
   FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
   auto dfResult = df->TryFormat(x.toDouble(), buffer);
   if (dfResult.isErr()) {
@@ -1158,84 +1393,63 @@ bool js::intl_FormatDateTime(JSContext* 
              : intl_FormatDateTime(cx, df, x, args.rval());
 }
 
 /**
  * Returns a new UDateIntervalFormat with the locale and date-time formatting
  * options of the given DateTimeFormat.
  */
 static UDateIntervalFormat* NewUDateIntervalFormat(
-    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) {
+    JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat,
+    mozilla::intl::DateTimeFormat& mozDtf) {
   RootedValue value(cx);
-
   RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
   if (!internals) {
     return nullptr;
   }
 
-  if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) {
+  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> pattern(cx);
+  auto result = mozDtf.GetPattern(pattern);
+  if (result.isErr()) {
+    intl::ReportInternalError(cx, result.unwrapErr());
+    return nullptr;
+  }
+
+  FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> skeleton(cx);
+  auto skelResult =
+      mozilla::intl::DateTimePatternGenerator::GetSkeleton(pattern, skeleton);
+  if (skelResult.isErr()) {
+    intl::ReportInternalError(cx, skelResult.unwrapErr());
     return nullptr;
   }
 
   // Determine the hour cycle used in the resolved pattern. This is needed to
   // workaround <https://unicode-org.atlassian.net/browse/ICU-21154> and
   // <https://unicode-org.atlassian.net/browse/ICU-21155>.
-  mozilla::Maybe<HourCycle> hcPattern;
-  {
-    JSLinearString* pattern = value.toString()->ensureLinear(cx);
-    if (!pattern) {
-      return nullptr;
-    }
-
-    JS::AutoCheckCannotGC nogc;
-    if (pattern->hasLatin1Chars()) {
-      hcPattern = HourCycleFromPattern<Latin1Char>(pattern->latin1Range(nogc));
-    } else {
-      hcPattern = HourCycleFromPattern<char16_t>(pattern->twoByteRange(nogc));
-    }
-  }
+  mozilla::Maybe<mozilla::intl::DateTimeFormat::HourCycle> hcPattern =
+      mozilla::intl::DateTimeFormat::HourCycleFromPattern(pattern);
 
   UniqueChars locale = DateTimeFormatLocale(cx, internals, hcPattern);
   if (!locale) {
     return nullptr;
   }
 
   if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) {
     return nullptr;
   }
 
   AutoStableStringChars timeZone(cx);
   if (!timeZone.initTwoByte(cx, value.toString())) {
     return nullptr;
   }
   mozilla::Span<const char16_t> timeZoneChars = timeZone.twoByteRange();
 
-  if (!GetProperty(cx, internals, internals, cx->names().skeleton, &value)) {
-    return nullptr;
-  }
-
-  AutoStableStringChars skeleton(cx);
-  if (!skeleton.initTwoByte(cx, value.toString())) {
-    return nullptr;
-  }
-  mozilla::Span<const char16_t> skeletonChars = skeleton.twoByteRange();
-
-  Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> newSkeleton(cx);
-  if (hcPattern) {
-    if (!newSkeleton.append(skeletonChars.data(), skeletonChars.size())) {
-      return nullptr;
-    }
-
-    ReplaceHourSymbol(newSkeleton, *hcPattern);
-    skeletonChars = newSkeleton;
-  }
-
   UErrorCode status = U_ZERO_ERROR;
   UDateIntervalFormat* dif = udtitvfmt_open(
-      IcuLocale(locale.get()), skeletonChars.data(), skeletonChars.size(),
+      IcuLocale(locale.get()), skeleton.data(), skeleton.length(),
       timeZoneChars.data(), timeZoneChars.size(), &status);
   if (U_FAILURE(status)) {
     intl::ReportInternalError(cx);
     return nullptr;
   }
 
   return dif;
 }
@@ -1632,17 +1846,17 @@ bool js::intl_FormatDateTimeRange(JSCont
 
     intl::AddICUCellMemory(dateTimeFormat,
                            DateTimeFormatObject::UDateFormatEstimatedMemoryUse);
   }
 
   // Obtain a cached UDateIntervalFormat object.
   UDateIntervalFormat* dif = dateTimeFormat->getDateIntervalFormat();
   if (!dif) {
-    dif = NewUDateIntervalFormat(cx, dateTimeFormat);
+    dif = NewUDateIntervalFormat(cx, dateTimeFormat, *df);
     if (!dif) {
       return false;
     }
     dateTimeFormat->setDateIntervalFormat(dif);
 
     intl::AddICUCellMemory(
         dateTimeFormat,
         DateTimeFormatObject::UDateIntervalFormatEstimatedMemoryUse);
--- a/js/src/builtin/intl/DateTimeFormat.h
+++ b/js/src/builtin/intl/DateTimeFormat.h
@@ -148,70 +148,16 @@ class DateTimeFormatObject : public Nati
  * intl_defaultTimeZone(). Otherwise return false.
  *
  * Usage: isIcuDefaultTimeZone = intl_isDefaultTimeZone(icuDefaultTimeZone)
  */
 [[nodiscard]] extern bool intl_isDefaultTimeZone(JSContext* cx, unsigned argc,
                                                  JS::Value* vp);
 
 /**
- * Return a pattern in the date-time format pattern language of Unicode
- * Technical Standard 35, Unicode Locale Data Markup Language, for the
- * best-fit date-time format pattern corresponding to skeleton for the
- * given locale.
- *
- * Usage: pattern = intl_patternForSkeleton(locale, skeleton, hourCycle)
- */
-[[nodiscard]] extern bool intl_patternForSkeleton(JSContext* cx, unsigned argc,
-                                                  JS::Value* vp);
-
-/**
- * Return a pattern in the date-time format pattern language of Unicode
- * Technical Standard 35, Unicode Locale Data Markup Language, for the
- * best-fit date-time style for the given locale.
- * The function takes six arguments:
- *
- *   locale
- *     BCP47 compliant locale string
- *   dateStyle
- *     A string with values: full or long or medium or short, or `undefined`
- *   timeStyle
- *     A string with values: full or long or medium or short, or `undefined`
- *   timeZone
- *     IANA time zone name
- *   hour12
- *     A boolean to request hour12 representation, or `undefined`
- *   hourCycle
- *     A string with values: h11, h12, h23, or h24, or `undefined`
- *
- * Date and time style categories map to CLDR time/date standard
- * format patterns.
- *
- * For the definition of a pattern string, see LDML 4.8:
- * http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
- *
- * If `undefined` is passed to `dateStyle` or `timeStyle`, the respective
- * portions of the pattern will not be included in the result.
- *
- * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone,
- *                                       hour12, hourCycle)
- */
-[[nodiscard]] extern bool intl_patternForStyle(JSContext* cx, unsigned argc,
-                                               JS::Value* vp);
-
-/**
- * Return a skeleton for the pattern in the date-time format pattern language of
- * Unicode Technical Standard 35, Unicode Locale Data Markup Language.
- *
- * Usage: skeleton = intl_skeletonForPattern(pattern)
- */
-[[nodiscard]] extern bool intl_skeletonForPattern(JSContext* cx, unsigned argc,
-                                                  JS::Value* vp);
-
-/**
  * Returns a String value representing x (which must be a Number value)
  * according to the effective locale and the formatting options of the
  * given DateTimeFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.3.2.
  *
  * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x, formatToParts)
  */
@@ -225,11 +171,20 @@ class DateTimeFormatObject : public Nati
  *
  * Spec: Intl.DateTimeFormat.prototype.formatRange proposal
  *
  * Usage: formatted = intl_FormatDateTimeRange(dateTimeFmt, x, y, formatToParts)
  */
 [[nodiscard]] extern bool intl_FormatDateTimeRange(JSContext* cx, unsigned argc,
                                                    JS::Value* vp);
 
+/**
+ * Extracts the resolved components from a DateTimeFormat and applies them to
+ * the object for resolved components.
+ *
+ * Usage: intl_resolveDateTimeFormatComponents(dateTimeFormat, resolved)
+ */
+[[nodiscard]] extern bool intl_resolveDateTimeFormatComponents(JSContext* cx,
+                                                               unsigned argc,
+                                                               JS::Value* vp);
 }  // namespace js
 
 #endif /* builtin_intl_DateTimeFormat_h */
--- a/js/src/builtin/intl/DateTimeFormat.js
+++ b/js/src/builtin/intl/DateTimeFormat.js
@@ -66,65 +66,54 @@ function resolveDateTimeFormatInternals(
                           DateTimeFormat.relevantExtensionKeys,
                           localeData);
 
     // Steps 12-13, 15.
     internalProps.locale = r.locale;
     internalProps.calendar = r.ca;
     internalProps.numberingSystem = r.nu;
 
-    // Compute formatting options.
-    // Step 16.
-    var dataLocale = r.dataLocale;
-
-    // Allow the calendar field to modify the pattern selection choice.
-    dataLocale = addUnicodeExtension(dataLocale, "-u-ca-" + r.ca);
-
     // Step 20.
     internalProps.timeZone = lazyDateTimeFormatData.timeZone;
 
     // Step 21.
     var formatOpt = lazyDateTimeFormatData.formatOpt;
 
     // Step 14.
     // Copy the hourCycle setting, if present, to the format options. But
     // only do this if no hour12 option is present, because the latter takes
     // precedence over hourCycle.
     if (r.hc !== null && formatOpt.hour12 === undefined)
         formatOpt.hourCycle = r.hc;
 
-    // Steps 26-30, more or less - see comment after this function.
-    var skeleton;
-    var pattern;
+    // Steps 26-31, more or less - see comment after this function.
     if (lazyDateTimeFormatData.patternOption !== undefined) {
-        pattern = lazyDateTimeFormatData.patternOption;
-        skeleton = intl_skeletonForPattern(pattern);
-
-        internalProps.patternOption = lazyDateTimeFormatData.patternOption;
+        internalProps.pattern = lazyDateTimeFormatData.patternOption;
     } else if (lazyDateTimeFormatData.dateStyle !== undefined ||
                lazyDateTimeFormatData.timeStyle !== undefined) {
-        pattern = intl_patternForStyle(dataLocale,
-                                       lazyDateTimeFormatData.dateStyle,
-                                       lazyDateTimeFormatData.timeStyle,
-                                       lazyDateTimeFormatData.timeZone,
-                                       formatOpt.hour12,
-                                       formatOpt.hourCycle);
-        skeleton = intl_skeletonForPattern(pattern);
-
+        internalProps.hourCycle = formatOpt.hourCycle;
+        internalProps.hour12 = formatOpt.hour12;
         internalProps.dateStyle = lazyDateTimeFormatData.dateStyle;
         internalProps.timeStyle = lazyDateTimeFormatData.timeStyle;
     } else {
-        skeleton = toICUSkeleton(formatOpt);
-        pattern = toBestICUPattern(dataLocale, skeleton, formatOpt);
+        internalProps.hourCycle = formatOpt.hourCycle;
+        internalProps.hour12 = formatOpt.hour12;
+        internalProps.weekday = formatOpt.weekday;
+        internalProps.era = formatOpt.era;
+        internalProps.year = formatOpt.year;
+        internalProps.month = formatOpt.month;
+        internalProps.day = formatOpt.day;
+        internalProps.dayPeriod = formatOpt.dayPeriod;
+        internalProps.hour = formatOpt.hour;
+        internalProps.minute = formatOpt.minute;
+        internalProps.second = formatOpt.second;
+        internalProps.fractionalSecondDigits = formatOpt.fractionalSecondDigits;
+        internalProps.timeZoneName = formatOpt.timeZoneName;
     }
 
-    // Step 31.
-    internalProps.skeleton = skeleton;
-    internalProps.pattern = pattern;
-
     // The caller is responsible for associating |internalProps| with the right
     // object using |setInternalProperties|.
     return internalProps;
 }
 
 /**
  * Returns an object containing the DateTimeFormat internal properties of |obj|.
  */
@@ -452,178 +441,16 @@ function InitializeDateTimeFormat(dateTi
 
         return thisValue;
     }
 
     // 12.2.1, step 6.
     return dateTimeFormat;
 }
 
-/* eslint-disable complexity */
-/**
- * Returns an ICU skeleton string representing the specified options.
- */
-function toICUSkeleton(options) {
-    // Create an ICU skeleton representing the specified options. See
-    // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
-    var skeleton = "";
-    switch (options.weekday) {
-    case "narrow":
-        skeleton += "EEEEE";
-        break;
-    case "short":
-        skeleton += "E";
-        break;
-    case "long":
-        skeleton += "EEEE";
-    }
-    switch (options.era) {
-    case "narrow":
-        skeleton += "GGGGG";
-        break;
-    case "short":
-        skeleton += "G";
-        break;
-    case "long":
-        skeleton += "GGGG";
-        break;
-    }
-    switch (options.year) {
-    case "2-digit":
-        skeleton += "yy";
-        break;
-    case "numeric":
-        skeleton += "y";
-        break;
-    }
-    switch (options.month) {
-    case "2-digit":
-        skeleton += "MM";
-        break;
-    case "numeric":
-        skeleton += "M";
-        break;
-    case "narrow":
-        skeleton += "MMMMM";
-        break;
-    case "short":
-        skeleton += "MMM";
-        break;
-    case "long":
-        skeleton += "MMMM";
-        break;
-    }
-    switch (options.day) {
-    case "2-digit":
-        skeleton += "dd";
-        break;
-    case "numeric":
-        skeleton += "d";
-        break;
-    }
-    // If hour12 and hourCycle are both present, hour12 takes precedence.
-    var hourSkeletonChar = "j";
-    if (options.hour12 !== undefined) {
-        if (options.hour12)
-            hourSkeletonChar = "h";
-        else
-            hourSkeletonChar = "H";
-    } else {
-        switch (options.hourCycle) {
-        case "h11":
-        case "h12":
-            hourSkeletonChar = "h";
-            break;
-        case "h23":
-        case "h24":
-            hourSkeletonChar = "H";
-            break;
-        }
-    }
-    switch (options.hour) {
-    case "2-digit":
-        skeleton += hourSkeletonChar + hourSkeletonChar;
-        break;
-    case "numeric":
-        skeleton += hourSkeletonChar;
-        break;
-    }
-    // ICU requires that "B" is set after the "j" hour skeleton symbol.
-    // https://unicode-org.atlassian.net/browse/ICU-20731
-    switch (options.dayPeriod) {
-    case "narrow":
-        skeleton += "BBBBB";
-        break;
-    case "short":
-        skeleton += "B";
-        break;
-    case "long":
-        skeleton += "BBBB";
-        break;
-    }
-    switch (options.minute) {
-    case "2-digit":
-        skeleton += "mm";
-        break;
-    case "numeric":
-        skeleton += "m";
-        break;
-    }
-    switch (options.second) {
-    case "2-digit":
-        skeleton += "ss";
-        break;
-    case "numeric":
-        skeleton += "s";
-        break;
-    }
-    switch (options.fractionalSecondDigits) {
-    case 1:
-        skeleton += "S";
-        break;
-    case 2:
-        skeleton += "SS";
-        break;
-    case 3:
-        skeleton += "SSS";
-        break;
-    }
-    switch (options.timeZoneName) {
-    case "short":
-        skeleton += "z";
-        break;
-    case "long":
-        skeleton += "zzzz";
-        break;
-    case "shortOffset":
-        skeleton += "O";
-        break;
-    case "longOffset":
-        skeleton += "OOOO";
-        break;
-    case "shortGeneric":
-        skeleton += "v";
-        break;
-    case "longGeneric":
-        skeleton += "vvvv";
-        break;
-    }
-    return skeleton;
-}
-/* eslint-enable complexity */
-
-/**
- * Returns an ICU pattern string for the given locale and representing the
- * specified skeleton as closely as possible given available locale data.
- */
-function toBestICUPattern(locale, skeleton, options) {
-    // Let ICU convert the ICU skeleton to an ICU pattern for the given locale.
-    return intl_patternForSkeleton(locale, skeleton, options.hourCycle);
-}
-
 /**
  * Returns a new options object that includes the provided options (if any)
  * and fills in default components if required components are not defined.
  * Required can be "date", "time", or "any".
  * Defaults can be "date", "time", or "all".
  *
  * Spec: ECMAScript Internationalization API Specification, 12.1.1.
  */
@@ -918,230 +745,48 @@ function Intl_DateTimeFormat_resolvedOpt
     // Steps 1-3.
     var thisArg = UnwrapDateTimeFormat(this);
     var dtf = thisArg;
     if (!IsObject(dtf) || (dtf = intl_GuardToDateTimeFormat(dtf)) === null) {
         return callFunction(intl_CallDateTimeFormatMethodIfWrapped, thisArg,
                             "Intl_DateTimeFormat_resolvedOptions");
     }
 
+    // Ensure the internals are resolved.
     var internals = getDateTimeFormatInternals(dtf);
 
     // Steps 4-5.
     var result = {
         locale: internals.locale,
         calendar: internals.calendar,
         numberingSystem: internals.numberingSystem,
         timeZone: internals.timeZone,
     };
 
-    if (internals.patternOption !== undefined) {
+    if (internals.pattern !== undefined) {
+        // The raw pattern option is only internal to Mozilla, and not part of the
+        // ECMA-402 API.
         DefineDataProperty(result, "pattern", internals.pattern);
     }
 
     var hasDateStyle = internals.dateStyle !== undefined;
     var hasTimeStyle = internals.timeStyle !== undefined;
 
     if (hasDateStyle || hasTimeStyle) {
         if (hasTimeStyle) {
             // timeStyle (unlike dateStyle) requires resolving the pattern to
             // ensure "hourCycle" and "hour12" properties are added to |result|.
-            resolveICUPattern(internals.pattern, result, /* includeDateTimeFields = */ false);
+            intl_resolveDateTimeFormatComponents(dtf, result, /* includeDateTimeFields = */ false);
         }
         if (hasDateStyle) {
             DefineDataProperty(result, "dateStyle", internals.dateStyle);
         }
         if (hasTimeStyle) {
             DefineDataProperty(result, "timeStyle", internals.timeStyle);
         }
     } else {
-        resolveICUPattern(internals.pattern, result, /* includeDateTimeFields = */ true);
+        // Components bag or a (Mozilla-only) raw pattern.
+        intl_resolveDateTimeFormatComponents(dtf, result, /* includeDateTimeFields = */ true);
     }
 
     // Step 6.
     return result;
 }
-
-/* eslint-disable complexity */
-/**
- * Maps an ICU pattern string to a corresponding set of date-time components
- * and their values, and adds properties for these components to the result
- * object, which will be returned by the resolvedOptions method. For the
- * interpretation of ICU pattern characters, see
- * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
- */
-function resolveICUPattern(pattern, result, includeDateTimeFields) {
-    assert(IsObject(result), "resolveICUPattern");
-
-    var hourCycle, weekday, era, year, month, day, dayPeriod, hour, minute, second,
-        fractionalSecondDigits, timeZoneName;
-    var i = 0;
-    while (i < pattern.length) {
-        var c = pattern[i++];
-        if (c === "'") {
-            while (i < pattern.length && pattern[i] !== "'")
-                i++;
-            i++;
-        } else {
-            var count = 1;
-            while (i < pattern.length && pattern[i] === c) {
-                i++;
-                count++;
-            }
-
-            var value;
-            switch (c) {
-            // "text" cases
-            case "G":
-            case "E":
-            case "c":
-            case "B":
-            case "z":
-            case "O":
-            case "v":
-            case "V":
-                if (count <= 3)
-                    value = "short";
-                else if (count === 4)
-                    value = "long";
-                else
-                    value = "narrow";
-                break;
-            // "number" cases
-            case "y":
-            case "d":
-            case "h":
-            case "H":
-            case "m":
-            case "s":
-            case "k":
-            case "K":
-                if (count === 2)
-                    value = "2-digit";
-                else
-                    value = "numeric";
-                break;
-            // "text & number" cases
-            case "M":
-            case "L":
-                if (count === 1)
-                    value = "numeric";
-                else if (count === 2)
-                    value = "2-digit";
-                else if (count === 3)
-                    value = "short";
-                else if (count === 4)
-                    value = "long";
-                else
-                    value = "narrow";
-                break;
-            case "S":
-                value = count;
-                break;
-            default:
-                // skip other pattern characters and literal text
-            }
-
-            // Map ICU pattern characters back to the corresponding date-time
-            // components of DateTimeFormat. See
-            // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
-            switch (c) {
-            case "E":
-            case "c":
-                weekday = value;
-                break;
-            case "G":
-                era = value;
-                break;
-            case "y":
-                year = value;
-                break;
-            case "M":
-            case "L":
-                month = value;
-                break;
-            case "d":
-                day = value;
-                break;
-            case "B":
-                dayPeriod = value;
-                break;
-            case "h":
-                hourCycle = "h12";
-                hour = value;
-                break;
-            case "H":
-                hourCycle = "h23";
-                hour = value;
-                break;
-            case "k":
-                hourCycle = "h24";
-                hour = value;
-                break;
-            case "K":
-                hourCycle = "h11";
-                hour = value;
-                break;
-            case "m":
-                minute = value;
-                break;
-            case "s":
-                second = value;
-                break;
-            case "S":
-                fractionalSecondDigits = value;
-                break;
-            case "z":
-                timeZoneName = value;
-                break;
-            case "O":
-                timeZoneName = value + "Offset";
-                break;
-            case "v":
-            case "V":
-                timeZoneName = value + "Generic";
-                break;
-            }
-        }
-    }
-
-    if (hourCycle) {
-        DefineDataProperty(result, "hourCycle", hourCycle);
-        DefineDataProperty(result, "hour12", hourCycle === "h11" || hourCycle === "h12");
-    }
-    if (!includeDateTimeFields) {
-        return;
-    }
-    if (weekday) {
-        DefineDataProperty(result, "weekday", weekday);
-    }
-    if (era) {
-        DefineDataProperty(result, "era", era);
-    }
-    if (year) {
-        DefineDataProperty(result, "year", year);
-    }
-    if (month) {
-        DefineDataProperty(result, "month", month);
-    }
-    if (day) {
-        DefineDataProperty(result, "day", day);
-    }
-    if (dayPeriod) {
-        DefineDataProperty(result, "dayPeriod", dayPeriod);
-    }
-    if (hour) {
-        DefineDataProperty(result, "hour", hour);
-    }
-    if (minute) {
-        DefineDataProperty(result, "minute", minute);
-    }
-    if (second) {
-        DefineDataProperty(result, "second", second);
-    }
-    if (fractionalSecondDigits) {
-        DefineDataProperty(result, "fractionalSecondDigits", fractionalSecondDigits);
-    }
-    if (timeZoneName) {
-        DefineDataProperty(result, "timeZoneName", timeZoneName);
-    }
-}
-/* eslint-enable complexity */
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -101,16 +101,17 @@
   MACRO_(compact, compact, "compact")                                          \
   MACRO_(compactDisplay, compactDisplay, "compactDisplay")                     \
   MACRO_(count, count, "count")                                                \
   MACRO_(CreateResolvingFunctions, CreateResolvingFunctions,                   \
          "CreateResolvingFunctions")                                           \
   MACRO_(currency, currency, "currency")                                       \
   MACRO_(currencyDisplay, currencyDisplay, "currencyDisplay")                  \
   MACRO_(currencySign, currencySign, "currencySign")                           \
+  MACRO_(dateStyle, dateStyle, "dateStyle")                                    \
   MACRO_(day, day, "day")                                                      \
   MACRO_(dayPeriod, dayPeriod, "dayPeriod")                                    \
   MACRO_(debugger, debugger, "debugger")                                       \
   MACRO_(decimal, decimal, "decimal")                                          \
   MACRO_(decodeURI, decodeURI, "decodeURI")                                    \
   MACRO_(decodeURIComponent, decodeURIComponent, "decodeURIComponent")         \
   MACRO_(default, default_, "default")                                         \
   MACRO_(defineGetter, defineGetter, "__defineGetter__")                       \
@@ -174,16 +175,18 @@
   MACRO_(float32, float32, "float32")                                          \
   MACRO_(float64, float64, "float64")                                          \
     MACRO_(for, for_, "for")                                                   \
   MACRO_(forceInterpreter, forceInterpreter, "forceInterpreter")               \
   MACRO_(forEach, forEach, "forEach")                                          \
   MACRO_(format, format, "format")                                             \
   MACRO_(fraction, fraction, "fraction")                                       \
   MACRO_(fractionalSecond, fractionalSecond, "fractionalSecond")               \
+  MACRO_(fractionalSecondDigits, fractionalSecondDigits,                       \
+         "fractionalSecondDigits")                                             \
   MACRO_(frame, frame, "frame")                                                \
   MACRO_(from, from, "from")                                                   \
   MACRO_(fulfilled, fulfilled, "fulfilled")                                    \
   MACRO_(futexNotEqual, futexNotEqual, "not-equal")                            \
   MACRO2(futexOK, futexOK, "ok")                                               \
   MACRO_(futexTimedOut, futexTimedOut, "timed-out")                            \
   MACRO_(gcCycleNumber, gcCycleNumber, "gcCycleNumber")                        \
   MACRO_(GatherAsyncParentCompletions, GatherAsyncParentCompletions,           \
@@ -220,16 +223,17 @@
   MACRO_(Handle, Handle, "Handle")                                             \
   MACRO_(has, has, "has")                                                      \
   MACRO_(hashConstructor, hashConstructor, "#constructor")                     \
   MACRO_(hasIndices, hasIndices, "hasIndices")                                 \
   MACRO_(hasOwn, hasOwn, "hasOwn")                                             \
   MACRO_(hasOwnProperty, hasOwnProperty, "hasOwnProperty")                     \
   MACRO_(highWaterMark, highWaterMark, "highWaterMark")                        \
   MACRO_(hour, hour, "hour")                                                   \
+  MACRO_(hour12, hour12, "hour12")                                             \
   MACRO_(hourCycle, hourCycle, "hourCycle")                                    \
   MACRO2(if, if_, "if")                                                        \
   MACRO_(ignoreCase, ignoreCase, "ignoreCase")                                 \
   MACRO_(ignorePunctuation, ignorePunctuation, "ignorePunctuation")            \
   MACRO_(implements, implements, "implements")                                 \
   MACRO_(import, import, "import")                                             \
   MACRO2(in, in, "in")                                                         \
   MACRO_(includes, includes, "includes")                                       \
@@ -452,16 +456,17 @@
   MACRO_(switch, switch_, "switch")                                            \
   MACRO_(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]")        \
   MACRO_(target, target, "target")                                             \
   MACRO_(test, test, "test")                                                   \
   MACRO_(then, then, "then")                                                   \
   MACRO_(this, this_, "this")                                                  \
   MACRO_(throw, throw_, "throw")                                               \
   MACRO_(timestamp, timestamp, "timestamp")                                    \
+  MACRO_(timeStyle, timeStyle, "timeStyle")                                    \
   MACRO_(timeZone, timeZone, "timeZone")                                       \
   MACRO_(timeZoneName, timeZoneName, "timeZoneName")                           \
   MACRO_(trailingZeroDisplay, trailingZeroDisplay, "trailingZeroDisplay")      \
   MACRO_(trimEnd, trimEnd, "trimEnd")                                          \
   MACRO_(trimLeft, trimLeft, "trimLeft")                                       \
   MACRO_(trimRight, trimRight, "trimRight")                                    \
   MACRO_(trimStart, trimStart, "trimStart")                                    \
   MACRO_(toGMTString, toGMTString, "toGMTString")                              \
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2421,19 +2421,18 @@ static const JSFunctionSpec intrinsic_fu
 #  endif
     JS_FN("intl_canonicalizeTimeZone", intl_canonicalizeTimeZone, 1, 0),
     JS_FN("intl_defaultCalendar", intl_defaultCalendar, 1, 0),
     JS_FN("intl_defaultTimeZone", intl_defaultTimeZone, 0, 0),
     JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0, 0),
     JS_FN("intl_isDefaultTimeZone", intl_isDefaultTimeZone, 1, 0),
     JS_FN("intl_isUpperCaseFirst", intl_isUpperCaseFirst, 1, 0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1, 0),
-    JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 3, 0),
-    JS_FN("intl_patternForStyle", intl_patternForStyle, 6, 0),
-    JS_FN("intl_skeletonForPattern", intl_skeletonForPattern, 1, 0),
+    JS_FN("intl_resolveDateTimeFormatComponents",
+          intl_resolveDateTimeFormatComponents, 3, 0),
     JS_FN("intl_supportedLocaleOrFallback", intl_supportedLocaleOrFallback, 1,
           0),
     JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2, 0),
     JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2, 0),
 #endif  // JS_HAS_INTL_API
 
     // Standard builtins used by self-hosting.
     JS_INLINABLE_FN("std_Array", array_construct, 1, 0, Array),