Bug 1719696 - Create a components bag abstraction for mozilla::intl::DateTimeFormat; r=anba,platform-i18n-reviewers,dminor
authorGreg Tatum <gtatum@mozilla.com>
Mon, 13 Sep 2021 20:52:59 +0000
changeset 591822 0b5d9fdd57a2fc4207dd8aff99e58fd73c3150f3
parent 591821 79a3304d03df928bf0422e7bcba8554f60fe58c8
child 591823 b23f891ed375c6cd9974fbc4e3de8a0b285dcb3d
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, platform-i18n-reviewers, dminor
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 - Create a components bag abstraction for mozilla::intl::DateTimeFormat; r=anba,platform-i18n-reviewers,dminor This patch will probably not compile on its own, but requires the SpiderMonkey side as well. It aims at building a components bag interface that can be then used in SpiderMonkey to back the ECMA 402 API. Differential Revision: https://phabricator.services.mozilla.com/D123822
intl/components/gtest/TestCalendar.cpp
intl/components/gtest/TestDateTimeFormat.cpp
intl/components/src/DateTimeFormat.cpp
intl/components/src/DateTimeFormat.h
intl/components/src/DateTimePatternGenerator.h
intl/components/src/ICU4CGlue.h
intl/l10n/FluentBundle.cpp
--- a/intl/components/gtest/TestCalendar.cpp
+++ b/intl/components/gtest/TestCalendar.cpp
@@ -74,21 +74,26 @@ TEST(IntlCalendar, SystemDependentTests)
 
   // This isn't system dependent, but currently there is no way to verify the
   // results.
   calendar->SetTimeInMs(CALENDAR_DATE).unwrap();
 }
 
 TEST(IntlCalendar, CloneFrom)
 {
-  auto dtFormat =
-      DateTimeFormat::TryCreateFromStyle(
-          MakeStringSpan("en-US"), DateTimeStyle::Medium, DateTimeStyle::Medium,
-          Some(MakeStringSpan(u"America/Chicago")))
-          .unwrap();
+  DateTimeFormat::StyleBag style;
+  style.date = Some(DateTimeFormat::Style::Medium);
+  style.time = Some(DateTimeFormat::Style::Medium);
+  auto dtFormat = DateTimeFormat::TryCreateFromStyle(
+                      MakeStringSpan("en-US"), style,
+                      // It's ok to pass nullptr here, as it will cause format
+                      // operations to fail, but this test is only checking
+                      // calendar cloning.
+                      nullptr, Some(MakeStringSpan(u"America/Chicago")))
+                      .unwrap();
 
   dtFormat->CloneCalendar(CALENDAR_DATE).unwrap();
 }
 
 TEST(IntlCalendar, GetCanonicalTimeZoneID)
 {
   TestBuffer<char16_t> buffer;
 
--- a/intl/components/gtest/TestDateTimeFormat.cpp
+++ b/intl/components/gtest/TestDateTimeFormat.cpp
@@ -9,105 +9,134 @@
 #include "mozilla/Span.h"
 #include "TestBuffer.h"
 
 namespace mozilla::intl {
 
 // Firefox 1.0 release date.
 const double DATE = 1032800850000.0;
 
-static UniquePtr<DateTimeFormat> testStyle(const char* aLocale,
-                                           DateTimeStyle aDateStyle,
-                                           DateTimeStyle aTimeStyle) {
+static UniquePtr<DateTimeFormat> testStyle(
+    const char* aLocale, DateTimeFormat::StyleBag& aStyleBag) {
   // Always specify a time zone in the tests, otherwise it will use the system
   // time zone which can vary between test runs.
-  return DateTimeFormat::TryCreateFromStyle(MakeStringSpan(aLocale), aDateStyle,
-                                            aTimeStyle,
-                                            Some(MakeStringSpan(u"GMT+3")))
+  auto timeZone = Some(MakeStringSpan(u"GMT+3"));
+  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+  return DateTimeFormat::TryCreateFromStyle(MakeStringSpan(aLocale), aStyleBag,
+                                            gen.get(), timeZone)
       .unwrap();
 }
 
 TEST(IntlDateTimeFormat, Style_enUS_utf8)
 {
-  auto dtFormat =
-      testStyle("en-US", DateTimeStyle::Medium, DateTimeStyle::Medium);
+  DateTimeFormat::StyleBag style;
+  style.date = Some(DateTimeFormat::Style::Medium);
+  style.time = Some(DateTimeFormat::Style::Medium);
+
+  auto dtFormat = testStyle("en-US", style);
   TestBuffer<char> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
   ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 8:07:30 PM"));
 }
 
 TEST(IntlDateTimeFormat, Style_enUS_utf16)
 {
-  auto dtFormat =
-      testStyle("en-US", DateTimeStyle::Medium, DateTimeStyle::Medium);
+  DateTimeFormat::StyleBag style;
+  style.date = Some(DateTimeFormat::Style::Medium);
+  style.time = Some(DateTimeFormat::Style::Medium);
+
+  auto dtFormat = testStyle("en-US", style);
   TestBuffer<char16_t> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
   ASSERT_TRUE(buffer.verboseMatches(u"Sep 23, 2002, 8:07:30 PM"));
 }
 
 TEST(IntlDateTimeFormat, Style_ar_utf8)
 {
-  auto dtFormat = testStyle("ar", DateTimeStyle::Medium, DateTimeStyle::None);
+  DateTimeFormat::StyleBag style;
+  style.time = Some(DateTimeFormat::Style::Medium);
+
+  auto dtFormat = testStyle("ar", style);
   TestBuffer<char> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
   ASSERT_TRUE(buffer.verboseMatches("٨:٠٧:٣٠ م"));
 }
 
 TEST(IntlDateTimeFormat, Style_ar_utf16)
 {
-  auto dtFormat = testStyle("ar", DateTimeStyle::Medium, DateTimeStyle::None);
+  DateTimeFormat::StyleBag style;
+  style.time = Some(DateTimeFormat::Style::Medium);
+
+  auto dtFormat = testStyle("ar", style);
   TestBuffer<char16_t> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
   ASSERT_TRUE(buffer.verboseMatches(u"٨:٠٧:٣٠ م"));
 }
 
 TEST(IntlDateTimeFormat, Style_enUS_fallback_to_default_styles)
 {
-  auto dtFormat = testStyle("en-US", DateTimeStyle::None, DateTimeStyle::None);
+  DateTimeFormat::StyleBag style;
+
+  auto dtFormat = testStyle("en-US", style);
   TestBuffer<char> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
   ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 8:07:30 PM"));
 }
 
 TEST(IntlDateTimeFormat, Skeleton_enUS_utf8_in)
 {
+  UniquePtr<DateTimePatternGenerator> gen = nullptr;
+  auto dateTimePatternGenerator =
+      DateTimePatternGenerator::TryCreate("en").unwrap();
+
   UniquePtr<DateTimeFormat> dtFormat =
       DateTimeFormat::TryCreateFromSkeleton(
-          "en-US", MakeStringSpan("yMdhhmmss"), Some(MakeStringSpan("GMT+3")))
+          "en-US", MakeStringSpan("yMdhhmmss"), dateTimePatternGenerator.get(),
+          Nothing(), Some(MakeStringSpan("GMT+3")))
           .unwrap();
   TestBuffer<char> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
-  ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 8:07:30 PM"));
+  ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 08:07:30 PM"));
 }
 
 TEST(IntlDateTimeFormat, Skeleton_enUS_utf16_in)
 {
+  UniquePtr<DateTimePatternGenerator> gen = nullptr;
+  auto dateTimePatternGenerator =
+      DateTimePatternGenerator::TryCreate("en").unwrap();
+
   UniquePtr<DateTimeFormat> dtFormat =
       DateTimeFormat::TryCreateFromSkeleton(
-          "en-US", MakeStringSpan(u"yMdhhmmss"), Some(MakeStringSpan(u"GMT+3")))
+          "en-US", MakeStringSpan(u"yMdhhmmss"), dateTimePatternGenerator.get(),
+          Nothing(), Some(MakeStringSpan(u"GMT+3")))
           .unwrap();
   TestBuffer<char> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
 
-  ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 8:07:30 PM"));
+  ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 08:07:30 PM"));
 }
 
 TEST(IntlDateTimeFormat, Time_zone_IANA_identifier)
 {
-  auto dtFormat =
-      DateTimeFormat::TryCreateFromStyle(
-          MakeStringSpan("en-US"), DateTimeStyle::Medium, DateTimeStyle::Medium,
-          Some(MakeStringSpan(u"America/Chicago")))
-          .unwrap();
+  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+
+  DateTimeFormat::StyleBag style;
+  style.date = Some(DateTimeFormat::Style::Medium);
+  style.time = Some(DateTimeFormat::Style::Medium);
+
+  auto dtFormat = DateTimeFormat::TryCreateFromStyle(
+                      MakeStringSpan("en-US"), style, gen.get(),
+                      Some(MakeStringSpan(u"America/Chicago")))
+                      .unwrap();
   TestBuffer<char> buffer;
   dtFormat->TryFormat(DATE, buffer).unwrap();
   ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 12:07:30 PM"));
 }
 
 TEST(IntlDateTimePatternGenerator, GetBestPattern)
 {
   auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
@@ -122,9 +151,297 @@ TEST(IntlDateTimePatternGenerator, GetSk
   auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
   TestBuffer<char16_t> buffer;
 
   DateTimePatternGenerator::GetSkeleton(MakeStringSpan(u"M/d/y"), buffer)
       .unwrap();
   ASSERT_TRUE(buffer.verboseMatches(u"yMd"));
 }
 
+// A utility function to help test the DateTimeFormat::ComponentsBag.
+[[nodiscard]] bool FormatComponents(TestBuffer<char16_t>& aBuffer,
+                                    DateTimeFormat::ComponentsBag& aComponents,
+                                    Span<const char> aLocale = "en-US") {
+  UniquePtr<DateTimePatternGenerator> gen = nullptr;
+  auto dateTimePatternGenerator =
+      DateTimePatternGenerator::TryCreate(aLocale.data()).unwrap();
+
+  auto dtFormat = DateTimeFormat::TryCreateFromComponents(
+      aLocale, aComponents, dateTimePatternGenerator.get(),
+      Some(MakeStringSpan(u"GMT+3")));
+  if (dtFormat.isErr()) {
+    fprintf(stderr, "Could not create a DateTimeFormat\n");
+    return false;
+  }
+
+  auto result = dtFormat.unwrap()->TryFormat(DATE, aBuffer);
+  if (result.isErr()) {
+    fprintf(stderr, "Could not format a DateTimeFormat\n");
+    return false;
+  }
+
+  return true;
+}
+
+TEST(IntlDateTimeFormat, Components)
+{
+  DateTimeFormat::ComponentsBag components{};
+
+  components.year = Some(DateTimeFormat::Numeric::Numeric);
+  components.month = Some(DateTimeFormat::Month::Numeric);
+  components.day = Some(DateTimeFormat::Numeric::Numeric);
+
+  components.hour = Some(DateTimeFormat::Numeric::Numeric);
+  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+  components.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+  TestBuffer<char16_t> buffer;
+  ASSERT_TRUE(FormatComponents(buffer, components));
+  ASSERT_TRUE(buffer.verboseMatches(u"9/23/2002, 8:07:30 PM"));
+}
+
+TEST(IntlDateTimeFormat, Components_es_ES)
+{
+  DateTimeFormat::ComponentsBag components{};
+
+  components.year = Some(DateTimeFormat::Numeric::Numeric);
+  components.month = Some(DateTimeFormat::Month::Numeric);
+  components.day = Some(DateTimeFormat::Numeric::Numeric);
+
+  components.hour = Some(DateTimeFormat::Numeric::Numeric);
+  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+  components.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+  TestBuffer<char16_t> buffer;
+  ASSERT_TRUE(FormatComponents(buffer, components, "es-ES"));
+  ASSERT_TRUE(buffer.verboseMatches(u"23/9/2002 20:07:30"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsAll)
+{
+  // Use most all of the components.
+  DateTimeFormat::ComponentsBag components{};
+
+  components.era = Some(DateTimeFormat::Text::Short);
+
+  components.year = Some(DateTimeFormat::Numeric::Numeric);
+  components.month = Some(DateTimeFormat::Month::Numeric);
+  components.day = Some(DateTimeFormat::Numeric::Numeric);
+
+  components.weekday = Some(DateTimeFormat::Text::Short);
+
+  components.hour = Some(DateTimeFormat::Numeric::Numeric);
+  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+  components.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+  components.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
+  components.hourCycle = Some(DateTimeFormat::HourCycle::H24);
+  components.fractionalSecondDigits = Some(3);
+
+  TestBuffer<char16_t> buffer;
+  ASSERT_TRUE(FormatComponents(buffer, components));
+  ASSERT_TRUE(buffer.verboseMatches(u"Mon, 9 23, 2002 AD, 20:07:30.000 GMT+3"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsHour12Default)
+{
+  // Assert the behavior of the default "en-US" 12 hour time with day period.
+  DateTimeFormat::ComponentsBag components{};
+  components.hour = Some(DateTimeFormat::Numeric::Numeric);
+  components.minute = Some(DateTimeFormat::Numeric::Numeric);
+
+  TestBuffer<char16_t> buffer;
+  ASSERT_TRUE(FormatComponents(buffer, components));
+  ASSERT_TRUE(buffer.verboseMatches(u"8:07 PM"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsHour24)
+{
+  // Test the behavior of using 24 hour time to override the default of
+  // hour 12 with a day period.
+  DateTimeFormat::ComponentsBag components{};
+  components.hour = Some(DateTimeFormat::Numeric::Numeric);
+  components.minute = Some(DateTimeFormat::Numeric::Numeric);
+  components.hour12 = Some(false);
+
+  TestBuffer<char16_t> buffer;
+  ASSERT_TRUE(FormatComponents(buffer, components));
+  ASSERT_TRUE(buffer.verboseMatches(u"20:07"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsHour12DayPeriod)
+{
+  // Test the behavior of specifying a specific day period.
+  DateTimeFormat::ComponentsBag components{};
+
+  components.hour = Some(DateTimeFormat::Numeric::Numeric);
+  components.minute = Some(DateTimeFormat::Numeric::Numeric);
+  components.dayPeriod = Some(DateTimeFormat::Text::Long);
+
+  TestBuffer<char16_t> buffer;
+  ASSERT_TRUE(FormatComponents(buffer, components));
+  ASSERT_TRUE(buffer.verboseMatches(u"8:07 in the evening"));
+}
+
+const char* ToString(uint8_t b) { return "uint8_t"; }
+const char* ToString(bool b) { return b ? "true" : "false"; }
+
+template <typename T>
+const char* ToString(Maybe<T> option) {
+  if (option) {
+    if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, uint8_t>) {
+      return ToString(*option);
+    } else {
+      return DateTimeFormat::ToString(*option);
+    }
+  }
+  return "Nothing";
+}
+
+template <typename T>
+[[nodiscard]] bool VerboseEquals(T expected, T actual, const char* msg) {
+  if (expected != actual) {
+    fprintf(stderr, "%s\n  Actual: %s\nExpected: %s\n", msg, ToString(actual),
+            ToString(expected));
+    return false;
+  }
+  return true;
+}
+
+// A testing utility for getting nice errors when ComponentsBags don't match.
+[[nodiscard]] bool VerboseEquals(DateTimeFormat::ComponentsBag& expected,
+                                 DateTimeFormat::ComponentsBag& actual) {
+  // clang-format off
+  return
+      VerboseEquals(expected.era, actual.era, "Components do not match: bag.era") &&
+      VerboseEquals(expected.year, actual.year, "Components do not match: bag.year") &&
+      VerboseEquals(expected.month, actual.month, "Components do not match: bag.month") &&
+      VerboseEquals(expected.day, actual.day, "Components do not match: bag.day") &&
+      VerboseEquals(expected.weekday, actual.weekday, "Components do not match: bag.weekday") &&
+      VerboseEquals(expected.hour, actual.hour, "Components do not match: bag.hour") &&
+      VerboseEquals(expected.minute, actual.minute, "Components do not match: bag.minute") &&
+      VerboseEquals(expected.second, actual.second, "Components do not match: bag.second") &&
+      VerboseEquals(expected.timeZoneName, actual.timeZoneName, "Components do not match: bag.timeZoneName") &&
+      VerboseEquals(expected.hour12, actual.hour12, "Components do not match: bag.hour12") &&
+      VerboseEquals(expected.hourCycle, actual.hourCycle, "Components do not match: bag.hourCycle") &&
+      VerboseEquals(expected.dayPeriod, actual.dayPeriod, "Components do not match: bag.dayPeriod") &&
+      VerboseEquals(expected.fractionalSecondDigits, actual.fractionalSecondDigits, "Components do not match: bag.fractionalSecondDigits");
+  // clang-format on
+}
+
+// A utility function to help test the DateTimeFormat::ComponentsBag.
+[[nodiscard]] bool ResolveComponentsBag(
+    DateTimeFormat::ComponentsBag& aComponentsIn,
+    DateTimeFormat::ComponentsBag* aComponentsOut,
+    Span<const char> aLocale = "en-US") {
+  UniquePtr<DateTimePatternGenerator> gen = nullptr;
+  auto dateTimePatternGenerator =
+      DateTimePatternGenerator::TryCreate("en").unwrap();
+  auto dtFormat = DateTimeFormat::TryCreateFromComponents(
+      aLocale, aComponentsIn, dateTimePatternGenerator.get(),
+      Some(MakeStringSpan(u"GMT+3")));
+  if (dtFormat.isErr()) {
+    fprintf(stderr, "Could not create a DateTimeFormat\n");
+    return false;
+  }
+
+  auto result = dtFormat.unwrap()->ResolveComponents();
+  if (result.isErr()) {
+    fprintf(stderr, "Could not resolve the components\n");
+    return false;
+  }
+
+  *aComponentsOut = result.unwrap();
+  return true;
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsDate)
+{
+  DateTimeFormat::ComponentsBag input{};
+  {
+    input.year = Some(DateTimeFormat::Numeric::Numeric);
+    input.month = Some(DateTimeFormat::Month::Numeric);
+    input.day = Some(DateTimeFormat::Numeric::Numeric);
+  }
+
+  DateTimeFormat::ComponentsBag expected = input;
+
+  DateTimeFormat::ComponentsBag resolved{};
+  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+  ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsAll)
+{
+  DateTimeFormat::ComponentsBag input{};
+  {
+    input.era = Some(DateTimeFormat::Text::Short);
+
+    input.year = Some(DateTimeFormat::Numeric::Numeric);
+    input.month = Some(DateTimeFormat::Month::Numeric);
+    input.day = Some(DateTimeFormat::Numeric::Numeric);
+
+    input.weekday = Some(DateTimeFormat::Text::Short);
+
+    input.hour = Some(DateTimeFormat::Numeric::Numeric);
+    input.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+    input.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+    input.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
+    input.hourCycle = Some(DateTimeFormat::HourCycle::H24);
+    input.fractionalSecondDigits = Some(3);
+  }
+
+  DateTimeFormat::ComponentsBag expected = input;
+  {
+    expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
+    expected.hourCycle = Some(DateTimeFormat::HourCycle::H24);
+    expected.hour12 = Some(false);
+  }
+
+  DateTimeFormat::ComponentsBag resolved{};
+  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+  ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsHourDayPeriod)
+{
+  DateTimeFormat::ComponentsBag input{};
+  {
+    input.hour = Some(DateTimeFormat::Numeric::Numeric);
+    input.minute = Some(DateTimeFormat::Numeric::Numeric);
+  }
+
+  DateTimeFormat::ComponentsBag expected = input;
+  {
+    expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+    expected.hourCycle = Some(DateTimeFormat::HourCycle::H12);
+    expected.hour12 = Some(true);
+  }
+
+  DateTimeFormat::ComponentsBag resolved{};
+  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+  ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsHour12)
+{
+  DateTimeFormat::ComponentsBag input{};
+  {
+    input.hour = Some(DateTimeFormat::Numeric::Numeric);
+    input.minute = Some(DateTimeFormat::Numeric::Numeric);
+    input.hour12 = Some(false);
+  }
+
+  DateTimeFormat::ComponentsBag expected = input;
+  {
+    expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
+    expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+    expected.hourCycle = Some(DateTimeFormat::HourCycle::H23);
+    expected.hour12 = Some(false);
+  }
+
+  DateTimeFormat::ComponentsBag resolved{};
+  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+  ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
 }  // namespace mozilla::intl
--- a/intl/components/src/DateTimeFormat.cpp
+++ b/intl/components/src/DateTimeFormat.cpp
@@ -5,137 +5,137 @@
 #include "unicode/ucal.h"
 #include "unicode/udat.h"
 #include "unicode/udatpg.h"
 
 #include "ScopedICUObject.h"
 
 #include "mozilla/intl/Calendar.h"
 #include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePatternGenerator.h"
 
 namespace mozilla::intl {
 
 DateTimeFormat::~DateTimeFormat() {
   MOZ_ASSERT(mDateFormat);
   udat_close(mDateFormat);
 }
 
-static UDateFormatStyle ToUDateFormatStyle(DateTimeStyle aStyle) {
-  switch (aStyle) {
-    case DateTimeStyle::Full:
+static UDateFormatStyle ToUDateFormatStyle(
+    Maybe<DateTimeFormat::Style> aLength) {
+  if (!aLength) {
+    return UDAT_NONE;
+  }
+  switch (*aLength) {
+    case DateTimeFormat::Style::Full:
       return UDAT_FULL;
-    case DateTimeStyle::Long:
+    case DateTimeFormat::Style::Long:
       return UDAT_LONG;
-    case DateTimeStyle::Medium:
+    case DateTimeFormat::Style::Medium:
       return UDAT_MEDIUM;
-    case DateTimeStyle::Short:
+    case DateTimeFormat::Style::Short:
       return UDAT_SHORT;
-    case DateTimeStyle::None:
-      return UDAT_NONE;
   }
   MOZ_ASSERT_UNREACHABLE();
   // Do not use the default: branch so that the enum is exhaustively checked.
   return UDAT_NONE;
 }
 
 /**
  * Parse a pattern according to the format specified in
  * <https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>.
  */
 template <typename CharT>
 class PatternIterator {
-  CharT* iter_;
-  const CharT* const end_;
+  CharT* iter;
+  const CharT* const end;
 
  public:
-  explicit PatternIterator(mozilla::Span<CharT> pattern)
-      : iter_(pattern.data()), end_(pattern.data() + pattern.size()) {}
+  explicit PatternIterator(mozilla::Span<CharT> aPattern)
+      : iter(aPattern.data()), end(aPattern.data() + aPattern.size()) {}
 
   CharT* next() {
-    MOZ_ASSERT(iter_ != nullptr);
+    MOZ_ASSERT(iter != nullptr);
 
     bool inQuote = false;
-    while (iter_ < end_) {
-      CharT* cur = iter_++;
+    while (iter < end) {
+      CharT* cur = iter++;
       if (*cur == '\'') {
         inQuote = !inQuote;
       } else if (!inQuote) {
         return cur;
       }
     }
 
-    iter_ = nullptr;
+    iter = nullptr;
     return nullptr;
   }
 };
 
-/**
- * Return the hour cycle used in the input pattern or Nothing if none was found.
- */
-template <typename CharT>
-static mozilla::Maybe<HourCycle> HourCycleFromPattern(
-    mozilla::Span<const CharT> pattern) {
-  PatternIterator<const CharT> iter(pattern);
+Maybe<DateTimeFormat::HourCycle> DateTimeFormat::HourCycleFromPattern(
+    Span<const char16_t> aPattern) {
+  PatternIterator<const char16_t> iter(aPattern);
   while (const auto* ptr = iter.next()) {
     switch (*ptr) {
       case 'K':
-        return mozilla::Some(HourCycle::H11);
+        return Some(DateTimeFormat::HourCycle::H11);
       case 'h':
-        return mozilla::Some(HourCycle::H12);
+        return Some(DateTimeFormat::HourCycle::H12);
       case 'H':
-        return mozilla::Some(HourCycle::H23);
+        return Some(DateTimeFormat::HourCycle::H23);
       case 'k':
-        return mozilla::Some(HourCycle::H24);
+        return Some(DateTimeFormat::HourCycle::H24);
     }
   }
-  return mozilla::Nothing();
+  return Nothing();
 }
 
-static bool IsHour12(HourCycle hc) {
-  return hc == HourCycle::H11 || hc == HourCycle::H12;
+static bool IsHour12(DateTimeFormat::HourCycle aHourCycle) {
+  return aHourCycle == DateTimeFormat::HourCycle::H11 ||
+         aHourCycle == DateTimeFormat::HourCycle::H12;
 }
 
-static char16_t HourSymbol(HourCycle hc) {
-  switch (hc) {
-    case HourCycle::H11:
+static char16_t HourSymbol(DateTimeFormat::HourCycle aHourCycle) {
+  switch (aHourCycle) {
+    case DateTimeFormat::HourCycle::H11:
       return 'K';
-    case HourCycle::H12:
+    case DateTimeFormat::HourCycle::H12:
       return 'h';
-    case HourCycle::H23:
+    case DateTimeFormat::HourCycle::H23:
       return 'H';
-    case HourCycle::H24:
+    case DateTimeFormat::HourCycle::H24:
       return 'k';
   }
   MOZ_CRASH("unexpected hour cycle");
 }
 
 enum class PatternField { Hour, Minute, Second, Other };
 
 template <typename CharT>
-static PatternField ToPatternField(CharT ch) {
-  if (ch == 'K' || ch == 'h' || ch == 'H' || ch == 'k' || ch == 'j') {
+static PatternField ToPatternField(CharT aCh) {
+  if (aCh == 'K' || aCh == 'h' || aCh == 'H' || aCh == 'k' || aCh == 'j') {
     return PatternField::Hour;
   }
-  if (ch == 'm') {
+  if (aCh == 'm') {
     return PatternField::Minute;
   }
-  if (ch == 's') {
+  if (aCh == 's') {
     return PatternField::Second;
   }
   return PatternField::Other;
 }
 
 /**
  * Replaces all hour pattern characters in |patternOrSkeleton| to use the
  * matching hour representation for |hourCycle|.
  */
-static void ReplaceHourSymbol(mozilla::Span<char16_t> patternOrSkeleton,
-                              HourCycle hc) {
-  char16_t replacement = HourSymbol(hc);
-  PatternIterator<char16_t> iter(patternOrSkeleton);
+static void ReplaceHourSymbol(mozilla::Span<char16_t> aPatternOrSkeleton,
+                              DateTimeFormat::HourCycle aHourCycle) {
+  char16_t replacement = HourSymbol(aHourCycle);
+  PatternIterator<char16_t> iter(aPatternOrSkeleton);
   while (auto* ptr = iter.next()) {
     auto field = ToPatternField(*ptr);
     if (field == PatternField::Hour) {
       *ptr = replacement;
     }
   }
 }
 
@@ -160,56 +160,42 @@ static void ReplaceHourSymbol(mozilla::S
  * ```
  *
  * Returns the pattern "MMMM d, y 'at' h:mm:ss a z", but when going through
  * |DateTimePatternGenerator::GetSkeleton| and then
  * |DateTimePatternGenerator::GetBestPattern| to find an equivalent pattern for
  * "h23", we'll end up with the pattern "MMMM d, y, HH:mm:ss z", so the
  * combinator element " 'at' " was lost in the process.
  */
-template <size_t N>
-static bool FindPatternWithHourCycle(JSContext* cx, const char* locale,
-                                     FormatBuffer<char16_t, N>& pattern,
-                                     bool hour12) {
-  SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
-  mozilla::intl::DateTimePatternGenerator* gen =
-      sharedIntlData.getDateTimePatternGenerator(cx, locale);
-  if (!gen) {
-    return false;
-  }
-
-  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 false;
-  }
+static ICUResult FindPatternWithHourCycle(
+    DateTimePatternGenerator& aDateTimePatternGenerator,
+    DateTimeFormat::PatternVector& aPattern, bool aHour12) {
+  DateTimeFormat::PatternVector skeleton{};
+  MOZ_TRY(
+      mozilla::intl::DateTimePatternGenerator::GetSkeleton(aPattern, skeleton));
 
   // Input skeletons don't differentiate between "K" and "h" resp. "k" and "H".
-  ReplaceHourSymbol(skeleton, hour12 ? HourCycle::H12 : HourCycle::H23);
+  ReplaceHourSymbol(skeleton, aHour12 ? DateTimeFormat::HourCycle::H12
+                                      : DateTimeFormat::HourCycle::H23);
 
-  auto result = gen->GetBestPattern(skeleton, pattern);
-  if (result.isErr()) {
-    intl::ReportInternalError(cx, result.unwrapErr());
-    return false;
-  }
-  return true;
+  MOZ_TRY(aDateTimePatternGenerator.GetBestPattern(skeleton, aPattern));
+
+  return Ok();
 }
 
-static auto PatternMatchOptions(mozilla::Span<const char16_t> skeleton) {
+static auto PatternMatchOptions(mozilla::Span<const char16_t> aSkeleton) {
   // Values for hour, minute, and second are:
   // - absent: 0
   // - numeric: 1
   // - 2-digit: 2
   int32_t hour = 0;
   int32_t minute = 0;
   int32_t second = 0;
 
-  PatternIterator<const char16_t> iter(skeleton);
+  PatternIterator<const char16_t> iter(aSkeleton);
   while (const auto* ptr = iter.next()) {
     switch (ToPatternField(*ptr)) {
       case PatternField::Hour:
         MOZ_ASSERT(hour < 2);
         hour += 1;
         break;
       case PatternField::Minute:
         MOZ_ASSERT(minute < 2);
@@ -249,55 +235,297 @@ static auto PatternMatchOptions(mozilla:
   }
   if (second == 2) {
     options += PatternMatchOption::SecondField;
   }
   return options;
 }
 
 /* static */
-Result<UniquePtr<DateTimeFormat>, DateTimeFormat::StyleError>
-DateTimeFormat::TryCreateFromStyle(
-    Span<const char> aLocale, DateTimeStyle aDateStyle,
-    DateTimeStyle aTimeStyle, Maybe<Span<const char16_t>> aTimeZoneOverride) {
-  auto dateStyle = ToUDateFormatStyle(aDateStyle);
-  auto timeStyle = ToUDateFormatStyle(aTimeStyle);
+Result<UniquePtr<DateTimeFormat>, ICUError> DateTimeFormat::TryCreateFromStyle(
+    Span<const char> aLocale, const StyleBag& aStyleBag,
+    DateTimePatternGenerator* aDateTimePatternGenerator,
+    Maybe<Span<const char16_t>> aTimeZoneOverride) {
+  auto dateStyle = ToUDateFormatStyle(aStyleBag.date);
+  auto timeStyle = ToUDateFormatStyle(aStyleBag.time);
 
   if (dateStyle == UDAT_NONE && timeStyle == UDAT_NONE) {
     dateStyle = UDAT_DEFAULT;
     timeStyle = UDAT_DEFAULT;
   }
 
   // The time zone is optional.
   int32_t tzIDLength = -1;
   const UChar* tzID = nullptr;
   if (aTimeZoneOverride) {
     tzIDLength = static_cast<int32_t>(aTimeZoneOverride->size());
     tzID = aTimeZoneOverride->Elements();
   }
 
   UErrorCode status = U_ZERO_ERROR;
   UDateFormat* dateFormat =
-      udat_open(dateStyle, timeStyle, aLocale.data(), tzID, tzIDLength,
+      udat_open(timeStyle, dateStyle, aLocale.data(), tzID, tzIDLength,
                 /* pattern */ nullptr, /* pattern length */ -1, &status);
-
-  if (U_SUCCESS(status)) {
-    return UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
   }
 
-  return Err(DateTimeFormat::StyleError::DateFormatFailure);
+  auto df = UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
+
+  if (aStyleBag.time && (aStyleBag.hour12 || aStyleBag.hourCycle)) {
+    // Only adjust the style pattern for time if there is an override.
+    // Extract the pattern and adjust it for the preferred hour cycle.
+    DateTimeFormat::PatternVector pattern{};
+
+    VectorToBufferAdaptor buffer(pattern);
+    MOZ_TRY(df->GetPattern(buffer));
+
+    Maybe<DateTimeFormat::HourCycle> hcPattern = HourCycleFromPattern(pattern);
+    if (hcPattern) {
+      bool wantHour12 =
+          aStyleBag.hour12 ? *aStyleBag.hour12 : IsHour12(*aStyleBag.hourCycle);
+      if (wantHour12 == IsHour12(*hcPattern)) {
+        // Return the date-time format when its hour-cycle settings match the
+        // requested options.
+        if (aStyleBag.hour12 || *hcPattern == *aStyleBag.hourCycle) {
+          return df;
+        }
+      } else {
+        MOZ_ASSERT(aDateTimePatternGenerator);
+        MOZ_TRY(FindPatternWithHourCycle(*aDateTimePatternGenerator, pattern,
+                                         wantHour12));
+      }
+      // Replace the hourCycle, if present, in the pattern string. But only do
+      // this if no hour12 option is present, because the latter takes
+      // precedence over hourCycle.
+      if (!aStyleBag.hour12) {
+        ReplaceHourSymbol(pattern, *aStyleBag.hourCycle);
+      }
+
+      return DateTimeFormat::TryCreateFromPattern(aLocale, pattern,
+                                                  aTimeZoneOverride);
+    }
+  }
+
+  return df;
 }
 
 DateTimeFormat::DateTimeFormat(UDateFormat* aDateFormat) {
   MOZ_RELEASE_ASSERT(aDateFormat, "Expected aDateFormat to not be a nullptr.");
   mDateFormat = aDateFormat;
 }
 
+// A helper to ergonomically push a string onto a string vector.
+template <typename V, size_t N>
+static ICUResult PushString(V& aVec, const char16_t (&aString)[N]) {
+  if (!aVec.append(aString, N - 1)) {
+    return Err(ICUError::OutOfMemory);
+  }
+  return Ok();
+}
+
+// A helper to ergonomically push a char onto a string vector.
+template <typename V>
+static ICUResult PushChar(V& aVec, char16_t aCh) {
+  if (!aVec.append(aCh)) {
+    return Err(ICUError::OutOfMemory);
+  }
+  return Ok();
+}
+
+/**
+ * Returns an ICU skeleton string representing the specified options.
+ * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
+ */
+ICUResult ToICUSkeleton(const DateTimeFormat::ComponentsBag& aBag,
+                        DateTimeFormat::PatternVector& aSkeleton) {
+  // Create an ICU skeleton representing the specified aBag. See
+  if (aBag.weekday) {
+    switch (*aBag.weekday) {
+      case DateTimeFormat::Text::Narrow:
+        MOZ_TRY(PushString(aSkeleton, u"EEEEE"));
+        break;
+      case DateTimeFormat::Text::Short:
+        MOZ_TRY(PushString(aSkeleton, u"E"));
+        break;
+      case DateTimeFormat::Text::Long:
+        MOZ_TRY(PushString(aSkeleton, u"EEEE"));
+    }
+  }
+  if (aBag.era) {
+    switch (*aBag.era) {
+      case DateTimeFormat::Text::Narrow:
+        MOZ_TRY(PushString(aSkeleton, u"GGGGG"));
+        break;
+      case DateTimeFormat::Text::Short:
+        MOZ_TRY(PushString(aSkeleton, u"G"));
+        break;
+      case DateTimeFormat::Text::Long:
+        MOZ_TRY(PushString(aSkeleton, u"GGGG"));
+        break;
+    }
+  }
+  if (aBag.year) {
+    switch (*aBag.year) {
+      case DateTimeFormat::Numeric::TwoDigit:
+        MOZ_TRY(PushString(aSkeleton, u"yy"));
+        break;
+      case DateTimeFormat::Numeric::Numeric:
+        MOZ_TRY(PushString(aSkeleton, u"y"));
+        break;
+    }
+  }
+  if (aBag.month) {
+    switch (*aBag.month) {
+      case DateTimeFormat::Month::TwoDigit:
+        MOZ_TRY(PushString(aSkeleton, u"MM"));
+        break;
+      case DateTimeFormat::Month::Numeric:
+        MOZ_TRY(PushString(aSkeleton, u"M"));
+        break;
+      case DateTimeFormat::Month::Narrow:
+        MOZ_TRY(PushString(aSkeleton, u"MMMMM"));
+        break;
+      case DateTimeFormat::Month::Short:
+        MOZ_TRY(PushString(aSkeleton, u"MMM"));
+        break;
+      case DateTimeFormat::Month::Long:
+        MOZ_TRY(PushString(aSkeleton, u"MMMM"));
+        break;
+    }
+  }
+  if (aBag.day) {
+    switch (*aBag.day) {
+      case DateTimeFormat::Numeric::TwoDigit:
+        MOZ_TRY(PushString(aSkeleton, u"dd"));
+        break;
+      case DateTimeFormat::Numeric::Numeric:
+        MOZ_TRY(PushString(aSkeleton, u"d"));
+        break;
+    }
+  }
+
+  // If hour12 and hourCycle are both present, hour12 takes precedence.
+  char16_t hourSkeletonChar = 'j';
+  if (aBag.hour12) {
+    if (*aBag.hour12) {
+      hourSkeletonChar = 'h';
+    } else {
+      hourSkeletonChar = 'H';
+    }
+  } else if (aBag.hourCycle) {
+    switch (*aBag.hourCycle) {
+      case DateTimeFormat::HourCycle::H11:
+      case DateTimeFormat::HourCycle::H12:
+        hourSkeletonChar = 'h';
+        break;
+      case DateTimeFormat::HourCycle::H23:
+      case DateTimeFormat::HourCycle::H24:
+        hourSkeletonChar = 'H';
+        break;
+    }
+  }
+  if (aBag.hour) {
+    switch (*aBag.hour) {
+      case DateTimeFormat::Numeric::TwoDigit:
+        MOZ_TRY(PushChar(aSkeleton, hourSkeletonChar));
+        MOZ_TRY(PushChar(aSkeleton, hourSkeletonChar));
+        break;
+      case DateTimeFormat::Numeric::Numeric:
+        MOZ_TRY(PushChar(aSkeleton, hourSkeletonChar));
+        break;
+    }
+  }
+  // ICU requires that "B" is set after the "j" hour skeleton symbol.
+  // https://unicode-org.atlassian.net/browse/ICU-20731
+  if (aBag.dayPeriod) {
+    switch (*aBag.dayPeriod) {
+      case DateTimeFormat::Text::Narrow:
+        MOZ_TRY(PushString(aSkeleton, u"BBBBB"));
+        break;
+      case DateTimeFormat::Text::Short:
+        MOZ_TRY(PushString(aSkeleton, u"B"));
+        break;
+      case DateTimeFormat::Text::Long:
+        MOZ_TRY(PushString(aSkeleton, u"BBBB"));
+        break;
+    }
+  }
+  if (aBag.minute) {
+    switch (*aBag.minute) {
+      case DateTimeFormat::Numeric::TwoDigit:
+        MOZ_TRY(PushString(aSkeleton, u"mm"));
+        break;
+      case DateTimeFormat::Numeric::Numeric:
+        MOZ_TRY(PushString(aSkeleton, u"m"));
+        break;
+    }
+  }
+  if (aBag.second) {
+    switch (*aBag.second) {
+      case DateTimeFormat::Numeric::TwoDigit:
+        MOZ_TRY(PushString(aSkeleton, u"ss"));
+        break;
+      case DateTimeFormat::Numeric::Numeric:
+        MOZ_TRY(PushString(aSkeleton, u"s"));
+        break;
+    }
+  }
+  if (aBag.fractionalSecondDigits) {
+    switch (*aBag.fractionalSecondDigits) {
+      case 1:
+        MOZ_TRY(PushString(aSkeleton, u"S"));
+        break;
+      case 2:
+        MOZ_TRY(PushString(aSkeleton, u"SS"));
+        break;
+      default:
+        MOZ_TRY(PushString(aSkeleton, u"SSS"));
+        break;
+    }
+  }
+  if (aBag.timeZoneName) {
+    switch (*aBag.timeZoneName) {
+      case DateTimeFormat::TimeZoneName::Short:
+        MOZ_TRY(PushString(aSkeleton, u"z"));
+        break;
+      case DateTimeFormat::TimeZoneName::Long:
+        MOZ_TRY(PushString(aSkeleton, u"zzzz"));
+        break;
+      case DateTimeFormat::TimeZoneName::ShortOffset:
+        MOZ_TRY(PushString(aSkeleton, u"O"));
+        break;
+      case DateTimeFormat::TimeZoneName::LongOffset:
+        MOZ_TRY(PushString(aSkeleton, u"OOOO"));
+        break;
+      case DateTimeFormat::TimeZoneName::ShortGeneric:
+        MOZ_TRY(PushString(aSkeleton, u"v"));
+        break;
+      case DateTimeFormat::TimeZoneName::LongGeneric:
+        MOZ_TRY(PushString(aSkeleton, u"vvvv"));
+        break;
+    }
+  }
+  return Ok();
+}
+
 /* static */
-Result<UniquePtr<DateTimeFormat>, DateTimeFormat::PatternError>
+Result<UniquePtr<DateTimeFormat>, ICUError>
+DateTimeFormat::TryCreateFromComponents(
+    Span<const char> aLocale, const DateTimeFormat::ComponentsBag& aBag,
+    DateTimePatternGenerator* aDateTimePatternGenerator,
+    Maybe<Span<const char16_t>> aTimeZoneOverride) {
+  DateTimeFormat::PatternVector skeleton;
+  MOZ_TRY(ToICUSkeleton(aBag, skeleton));
+  return TryCreateFromSkeleton(aLocale, skeleton, aDateTimePatternGenerator,
+                               aBag.hourCycle, aTimeZoneOverride);
+}
+
+/* static */
+Result<UniquePtr<DateTimeFormat>, ICUError>
 DateTimeFormat::TryCreateFromPattern(
     Span<const char> aLocale, Span<const char16_t> aPattern,
     Maybe<Span<const char16_t>> aTimeZoneOverride) {
   UErrorCode status = U_ZERO_ERROR;
 
   // The time zone is optional.
   int32_t tzIDLength = -1;
   const UChar* tzID = nullptr;
@@ -308,93 +536,77 @@ DateTimeFormat::TryCreateFromPattern(
 
   // Create the date formatter.
   UDateFormat* dateFormat = udat_open(
       UDAT_PATTERN, UDAT_PATTERN, static_cast<const char*>(aLocale.data()),
       tzID, tzIDLength, aPattern.data(), static_cast<int32_t>(aPattern.size()),
       &status);
 
   if (U_FAILURE(status)) {
-    return Err(PatternError::DateFormatFailure);
+    return Err(ToICUError(status));
   }
 
   // The DateTimeFormat wrapper will control the life cycle of the ICU
   // dateFormat object.
   return UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
 }
 
 /* static */
-Result<UniquePtr<DateTimeFormat>, DateTimeFormat::SkeletonError>
+Result<UniquePtr<DateTimeFormat>, ICUError>
 DateTimeFormat::TryCreateFromSkeleton(
     Span<const char> aLocale, Span<const char16_t> aSkeleton,
+    DateTimePatternGenerator* aDateTimePatternGenerator,
+    Maybe<DateTimeFormat::HourCycle> aHourCycle,
     Maybe<Span<const char16_t>> aTimeZoneOverride) {
-  UErrorCode status = U_ZERO_ERROR;
-
-  // Create a time pattern generator. Its lifetime is scoped to this function.
-  UDateTimePatternGenerator* dtpg = udatpg_open(aLocale.data(), &status);
-  if (U_FAILURE(status)) {
-    return Err(SkeletonError::PatternGeneratorFailure);
+  if (!aDateTimePatternGenerator) {
+    return Err(ICUError::InternalError);
   }
-  ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
 
   // Compute the best pattern for the skeleton.
-  mozilla::Vector<char16_t, DateTimeFormat::StackU16VectorSize> bestPattern;
+  DateTimeFormat::PatternVector pattern;
+  auto options = PatternMatchOptions(aSkeleton);
+  MOZ_TRY(
+      aDateTimePatternGenerator->GetBestPattern(aSkeleton, pattern, options));
 
-  auto result = FillVectorWithICUCall(
-      bestPattern,
-      [&dtpg, &aSkeleton](UChar* target, int32_t length, UErrorCode* status) {
-        return udatpg_getBestPattern(dtpg, aSkeleton.data(),
-                                     static_cast<int32_t>(aSkeleton.size()),
-                                     target, length, status);
-      });
-
-  if (result.isErr()) {
-    return Err(SkeletonError::GetBestPatternFailure);
+  if (aHourCycle) {
+    ReplaceHourSymbol(pattern, *aHourCycle);
   }
 
-  return DateTimeFormat::TryCreateFromPattern(aLocale, bestPattern,
-                                              aTimeZoneOverride)
-      .mapErr([](DateTimeFormat::PatternError error) {
-        switch (error) {
-          case DateTimeFormat::PatternError::DateFormatFailure:
-            return SkeletonError::DateFormatFailure;
-        }
-        // Do not use the default branch, so that the switch is exhaustively
-        // checked.
-        MOZ_ASSERT_UNREACHABLE();
-        return SkeletonError::DateFormatFailure;
-      });
+  return DateTimeFormat::TryCreateFromPattern(aLocale, pattern,
+                                              aTimeZoneOverride);
 }
 
 /* static */
-Result<UniquePtr<DateTimeFormat>, DateTimeFormat::SkeletonError>
+Result<UniquePtr<DateTimeFormat>, ICUError>
 DateTimeFormat::TryCreateFromSkeleton(
     Span<const char> aLocale, Span<const char> aSkeleton,
+    DateTimePatternGenerator* aDateTimePatternGenerator,
+    Maybe<DateTimeFormat::HourCycle> aHourCycle,
     Maybe<Span<const char>> aTimeZoneOverride) {
   // Convert the skeleton to UTF-16.
-  mozilla::Vector<char16_t, DateTimeFormat::StackU16VectorSize>
-      skeletonUtf16Buffer;
+  DateTimeFormat::PatternVector skeletonUtf16Buffer;
 
   if (!FillUTF16Vector(aSkeleton, skeletonUtf16Buffer)) {
-    return Err(SkeletonError::OutOfMemory);
+    return Err(ICUError::OutOfMemory);
   }
 
   // Convert the timezone to UTF-16 if it exists.
-  mozilla::Vector<char16_t, DateTimeFormat::StackU16VectorSize> tzUtf16Vec;
+  DateTimeFormat::PatternVector tzUtf16Vec;
   Maybe<Span<const char16_t>> timeZone = Nothing{};
   if (aTimeZoneOverride) {
     if (!FillUTF16Vector(*aTimeZoneOverride, tzUtf16Vec)) {
-      return Err(SkeletonError::OutOfMemory);
+      return Err(ICUError::OutOfMemory);
     };
     timeZone =
         Some(Span<const char16_t>(tzUtf16Vec.begin(), tzUtf16Vec.length()));
   }
 
   return DateTimeFormat::TryCreateFromSkeleton(aLocale, skeletonUtf16Buffer,
-                                               timeZone);
+                                               aDateTimePatternGenerator,
+                                               aHourCycle, timeZone);
 }
 
 void DateTimeFormat::SetStartTimeIfGregorian(double aTime) {
   UErrorCode status = U_ZERO_ERROR;
   UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(mDateFormat));
   ucal_setGregorianChange(cal, aTime, &status);
   // An error here means the calendar is not Gregorian, and can be ignored.
 }
@@ -411,9 +623,284 @@ Result<UniquePtr<Calendar>, InternalErro
 
   auto setTimeResult = calendar->SetTimeInMs(aUnixEpoch);
   if (setTimeResult.isErr()) {
     return Err(InternalError{});
   }
   return calendar;
 }
 
+Result<DateTimeFormat::ComponentsBag, ICUError>
+DateTimeFormat::ResolveComponents() {
+  // 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
+
+  DateTimeFormat::PatternVector pattern{};
+  VectorToBufferAdaptor buffer(pattern);
+  MOZ_TRY(GetPattern(buffer));
+
+  DateTimeFormat::ComponentsBag bag{};
+
+  using Text = DateTimeFormat::Text;
+  using HourCycle = DateTimeFormat::HourCycle;
+  using Numeric = DateTimeFormat::Numeric;
+  using Month = DateTimeFormat::Month;
+
+  auto text = Text::Long;
+  auto numeric = Numeric::Numeric;
+  auto month = Month::Long;
+  uint8_t fractionalSecondDigits = 0;
+
+  for (size_t i = 0, len = pattern.length(); i < len;) {
+    char16_t c = pattern[i++];
+    if (c == u'\'') {
+      // Skip past string literals.
+      while (i < len && pattern[i] != u'\'') {
+        i++;
+      }
+      i++;
+      continue;
+    }
+
+    // Count how many times the character is repeated.
+    size_t count = 1;
+    while (i < len && pattern[i] == c) {
+      i++;
+      count++;
+    }
+
+    // Determine the enum case of the field.
+    switch (c) {
+      // "text" cases
+      case u'G':
+      case u'E':
+      case u'c':
+      case u'B':
+      case u'z':
+      case u'O':
+      case u'v':
+      case u'V':
+        if (count <= 3) {
+          text = Text::Short;
+        } else if (count == 4) {
+          text = Text::Long;
+        } else {
+          text = Text::Narrow;
+        }
+        break;
+      // "number" cases
+      case u'y':
+      case u'd':
+      case u'h':
+      case u'H':
+      case u'm':
+      case u's':
+      case u'k':
+      case u'K':
+        if (count == 2) {
+          numeric = Numeric::TwoDigit;
+        } else {
+          numeric = Numeric::Numeric;
+        }
+        break;
+      // "text & number" cases
+      case u'M':
+      case u'L':
+        if (count == 1) {
+          month = Month::Numeric;
+        } else if (count == 2) {
+          month = Month::TwoDigit;
+        } else if (count == 3) {
+          month = Month::Short;
+        } else if (count == 4) {
+          month = Month::Long;
+        } else {
+          month = Month::Narrow;
+        }
+        break;
+      case u'S':
+        fractionalSecondDigits = 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 u'E':
+      case u'c':
+        bag.weekday = Some(text);
+        break;
+      case u'G':
+        bag.era = Some(text);
+        break;
+      case u'y':
+        bag.year = Some(numeric);
+        break;
+      case u'M':
+      case u'L':
+        bag.month = Some(month);
+        break;
+      case u'd':
+        bag.day = Some(numeric);
+        break;
+      case u'B':
+        bag.dayPeriod = Some(text);
+        break;
+      case u'K':
+        bag.hourCycle = Some(HourCycle::H11);
+        bag.hour = Some(numeric);
+        bag.hour12 = Some(true);
+        break;
+      case u'h':
+        bag.hourCycle = Some(HourCycle::H12);
+        bag.hour = Some(numeric);
+        bag.hour12 = Some(true);
+        break;
+      case u'H':
+        bag.hourCycle = Some(HourCycle::H23);
+        bag.hour = Some(numeric);
+        bag.hour12 = Some(false);
+        break;
+      case u'k':
+        bag.hourCycle = Some(HourCycle::H24);
+        bag.hour = Some(numeric);
+        bag.hour12 = Some(false);
+        break;
+      case u'm':
+        bag.minute = Some(numeric);
+        break;
+      case u's':
+        bag.second = Some(numeric);
+        break;
+      case u'S':
+        bag.fractionalSecondDigits = Some(fractionalSecondDigits);
+        break;
+      case u'z':
+        switch (text) {
+          case Text::Long:
+            bag.timeZoneName = Some(TimeZoneName::Long);
+            break;
+          case Text::Short:
+          case Text::Narrow:
+            bag.timeZoneName = Some(TimeZoneName::Short);
+            break;
+        }
+        break;
+      case u'O':
+        switch (text) {
+          case Text::Long:
+            bag.timeZoneName = Some(TimeZoneName::LongOffset);
+            break;
+          case Text::Short:
+          case Text::Narrow:
+            bag.timeZoneName = Some(TimeZoneName::ShortOffset);
+            break;
+        }
+        break;
+      case u'v':
+      case u'V':
+        switch (text) {
+          case Text::Long:
+            bag.timeZoneName = Some(TimeZoneName::LongGeneric);
+            break;
+          case Text::Short:
+          case Text::Narrow:
+            bag.timeZoneName = Some(TimeZoneName::ShortGeneric);
+            break;
+        }
+        break;
+    }
+  }
+  return bag;
+}
+
+const char* DateTimeFormat::ToString(
+    DateTimeFormat::TimeZoneName aTimeZoneName) {
+  switch (aTimeZoneName) {
+    case TimeZoneName::Long:
+      return "long";
+    case TimeZoneName::Short:
+      return "short";
+    case TimeZoneName::ShortOffset:
+      return "shortOffset";
+    case TimeZoneName::LongOffset:
+      return "longOffset";
+    case TimeZoneName::ShortGeneric:
+      return "shortGeneric";
+    case TimeZoneName::LongGeneric:
+      return "longGeneric";
+  }
+  MOZ_CRASH("Unexpected DateTimeFormat::TimeZoneName");
+}
+
+const char* DateTimeFormat::ToString(DateTimeFormat::Month aMonth) {
+  switch (aMonth) {
+    case Month::Numeric:
+      return "numeric";
+    case Month::TwoDigit:
+      return "2-digit";
+    case Month::Long:
+      return "long";
+    case Month::Short:
+      return "short";
+    case Month::Narrow:
+      return "narrow";
+  }
+  MOZ_CRASH("Unexpected DateTimeFormat::Month");
+}
+
+const char* DateTimeFormat::ToString(DateTimeFormat::Text aText) {
+  switch (aText) {
+    case Text::Long:
+      return "long";
+    case Text::Short:
+      return "short";
+    case Text::Narrow:
+      return "narrow";
+  }
+  MOZ_CRASH("Unexpected DateTimeFormat::Text");
+}
+
+const char* DateTimeFormat::ToString(DateTimeFormat::Numeric aNumeric) {
+  switch (aNumeric) {
+    case Numeric::Numeric:
+      return "numeric";
+    case Numeric::TwoDigit:
+      return "2-digit";
+  }
+  MOZ_CRASH("Unexpected DateTimeFormat::Numeric");
+}
+
+const char* DateTimeFormat::ToString(DateTimeFormat::Style aStyle) {
+  switch (aStyle) {
+    case Style::Full:
+      return "full";
+    case Style::Long:
+      return "long";
+    case Style::Medium:
+      return "medium";
+    case Style::Short:
+      return "short";
+  }
+  MOZ_CRASH("Unexpected DateTimeFormat::Style");
+}
+
+const char* DateTimeFormat::ToString(DateTimeFormat::HourCycle aHourCycle) {
+  switch (aHourCycle) {
+    case HourCycle::H11:
+      return "h11";
+    case HourCycle::H12:
+      return "h12";
+    case HourCycle::H23:
+      return "h23";
+    case HourCycle::H24:
+      return "h24";
+  }
+  MOZ_CRASH("Unexpected DateTimeFormat::HourCycle");
+}
 }  // namespace mozilla::intl
--- a/intl/components/src/DateTimeFormat.h
+++ b/intl/components/src/DateTimeFormat.h
@@ -1,59 +1,65 @@
 /* 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/. */
 #ifndef intl_components_DateTimeFormat_h_
 #define intl_components_DateTimeFormat_h_
+#include <functional>
 #include "unicode/udat.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/intl/ICU4CGlue.h"
 #include "mozilla/intl/ICUError.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Result.h"
 #include "mozilla/ResultVariant.h"
 #include "mozilla/Span.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Utf8.h"
+#include "mozilla/Variant.h"
 #include "mozilla/Vector.h"
 
 namespace mozilla::intl {
 
-enum class DateTimeStyle { Full, Long, Medium, Short, None };
-
+class DateTimePatternGenerator;
 class Calendar;
 
 /**
+ * Intro to mozilla::intl::DateTimeFormat
+ * ======================================
+ *
  * This component is a Mozilla-focused API for the date formatting provided by
  * ICU. The methods internally call out to ICU4C. This is responsible for and
  * owns any resources opened through ICU, through RAII.
  *
  * The construction of a DateTimeFormat contains the majority of the cost
  * of the DateTimeFormat operation. DateTimeFormat::TryFormat should be
  * relatively inexpensive after the initial construction.
  *
- * This class supports creating from Styles (a fixed set of options), and from
- * Skeletons (a list of fields and field widths to include).
+ * This class supports creating from Styles (a fixed set of options), from
+ * Skeletons (a list of fields and field widths to include), and from a
+ * components bag (a list of components and their lengths).
  *
- * This API will also serve to back the ECMA-402 Intl.DateTimeFormat API.
- * See Bug 1709473.
+ * This API serves to back the ECMA-402 Intl.DateTimeFormat API.
  * https://tc39.es/ecma402/#datetimeformat-objects
  *
- * Intl.DateTimeFormat and ICU skeletons and patterns
- * ==================================================
+ *
+ * ECMA-402 Intl.DateTimeFormat API and implementation details with ICU
+ * skeletons and patterns.
+ * ====================================================================
  *
  * Different locales have different ways to display dates using the same
  * basic components. For example, en-US might use "Sept. 24, 2012" while
  * fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to
  * permit production of a format for the locale that best matches the
  * set of date-time components and their desired representation as specified
  * by the API client.
  *
- * ICU supports specification of date and time formats in three ways:
+ * ICU4C supports specification of date and time formats in three ways:
  *
  * 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT.
  *    The date-time components included in each style and their representation
  *    are defined by ICU using CLDR locale data (CLDR is the Unicode
  *    Consortium's Common Locale Data Repository).
  *
  * 2) A skeleton is a string specifying which date-time components to include,
  *    and which representations to use for them. For example, "yyyyMMMMdd"
@@ -73,94 +79,231 @@ class Calendar;
  *    example, "EEEE, d MMMM y" specifies the full localized weekday name,
  *    followed by comma and space, followed by the day, followed by space,
  *    followed by the full month name in format form, followed by space,
  *    followed by the full year. It
  *    still does not specify localized strings for textual components and the
  *    numbering system - these are determined by ICU using CLDR locale data or
  *    possibly API parameters.
  *
- * All actual formatting in ICU is done with patterns; styles and skeletons
+ * All actual formatting in ICU4C is done with patterns; styles and skeletons
  * have to be mapped to patterns before processing.
  *
- * The options of DateTimeFormat most closely correspond to ICU skeletons. This
- * implementation therefore, in the toBestICUPattern function, converts
- * DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to
- * actual ICU patterns. The pattern may not directly correspond to what the
- * skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained
- * by the available locale data for the locale. The resulting ICU pattern is
- * kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU
- * in the format method.
+ * The options of Intl.DateTimeFormat most closely correspond to ICU skeletons.
+ * This implementation therefore converts DateTimeFormat options to ICU
+ * skeletons, and then lets ICU map skeletons to actual ICU patterns. The
+ * pattern may not directly correspond to what the skeleton requests, as the
+ * mapper (UDateTimePatternGenerator) is constrained by the available locale
+ * data for the locale.
  *
  * An ICU pattern represents the information of the following DateTimeFormat
  * internal properties described in the specification, which therefore don't
  * exist separately in the implementation:
  * - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]],
  *   [[second]], [[timeZoneName]]
  * - [[hour12]]
  * - [[hourCycle]]
  * - [[hourNo0]]
  * When needed for the resolvedOptions method, the resolveICUPattern function
- * maps the instance's ICU pattern back to the specified properties of the
- * object returned by resolvedOptions.
+ * queries the UDateFormat's internal pattern and then maps the it back to the
+ * specified properties of the object returned by resolvedOptions.
  *
  * ICU date-time skeletons and patterns aren't fully documented in the ICU
  * documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best
  * documentation at this point is in UTR 35:
  * http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
+ *
+ * Future support for ICU4X
+ * ========================
+ * This implementation exposes a components bag, and internally handles the
+ * complexity of working with skeletons and patterns to generate the correct
+ * results. In the future, if and when we switch to ICU4X, the complexities of
+ * manipulating patterns will be able to be removed, as ICU4X will directly know
+ * how to apply the components bag.
  */
 class DateTimeFormat final {
  public:
+  /**
+   * The hour cycle for components.
+   */
+  enum class HourCycle {
+    H11,
+    H12,
+    H23,
+    H24,
+  };
+
+  /**
+   * The style for dates or times.
+   */
+  enum class Style {
+    Full,
+    Long,
+    Medium,
+    Short,
+  };
+
+  /**
+   * A bag of options to determine the length of the time and date styles. The
+   * hour cycle can be overridden.
+   */
+  struct StyleBag {
+    Maybe<Style> date = Nothing();
+    Maybe<Style> time = Nothing();
+    Maybe<HourCycle> hourCycle = Nothing();
+    Maybe<bool> hour12 = Nothing();
+  };
+
+  /**
+   * How to to display numeric components such as the year and the day.
+   */
+  enum class Numeric {
+    Numeric,
+    TwoDigit,
+  };
+
+  /**
+   * How to display the text components, such as the weekday or day period.
+   */
+  enum class Text {
+    Long,
+    Short,
+    Narrow,
+  };
+
+  /**
+   * How to display the month.
+   */
+  enum class Month {
+    Numeric,
+    TwoDigit,
+    Long,
+    Short,
+    Narrow,
+  };
+
+  /**
+   * How to display the time zone name.
+   */
+  enum class TimeZoneName {
+    Long,
+    Short,
+    ShortOffset,
+    LongOffset,
+    ShortGeneric,
+    LongGeneric,
+  };
+
+  /**
+   * Get static strings representing the enums. These match ECMA-402's resolved
+   * options.
+   * https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.resolvedoptions
+   */
+  static const char* ToString(DateTimeFormat::HourCycle aHourCycle);
+  static const char* ToString(DateTimeFormat::Style aStyle);
+  static const char* ToString(DateTimeFormat::Numeric aNumeric);
+  static const char* ToString(DateTimeFormat::Text aText);
+  static const char* ToString(DateTimeFormat::Month aMonth);
+  static const char* ToString(DateTimeFormat::TimeZoneName aTimeZoneName);
+
+  /**
+   * A components bag specifies the components used to display a DateTime. Each
+   * component can be styled individually, and ICU will attempt to create a best
+   * match for a given locale.
+   */
+  struct ComponentsBag {
+    Maybe<Text> era = Nothing();
+    Maybe<Numeric> year = Nothing();
+    Maybe<Month> month = Nothing();
+    Maybe<Numeric> day = Nothing();
+    Maybe<Text> weekday = Nothing();
+    Maybe<Numeric> hour = Nothing();
+    Maybe<Numeric> minute = Nothing();
+    Maybe<Numeric> second = Nothing();
+    Maybe<TimeZoneName> timeZoneName = Nothing();
+    Maybe<bool> hour12 = Nothing();
+    Maybe<HourCycle> hourCycle = Nothing();
+    Maybe<Text> dayPeriod = Nothing();
+    Maybe<uint8_t> fractionalSecondDigits = Nothing();
+  };
+
   // Do not allow copy as this class owns the ICU resource. Move is not
   // currently implemented, but a custom move operator could be created if
   // needed.
   DateTimeFormat(const DateTimeFormat&) = delete;
   DateTimeFormat& operator=(const DateTimeFormat&) = delete;
 
+  // mozilla::Vector can avoid heap allocations for small transient buffers.
+  using PatternVector = Vector<char16_t, 128>;
+
   /**
    * Create a DateTimeFormat from styles.
    *
    * The "style" model uses different options for formatting a date or time
    * based on how the result will be styled, rather than picking specific
    * fields or lengths.
    *
    * Takes an optional time zone which will override the user's default
    * time zone. This is a UTF-16 string that takes the form "GMT±hh:mm", or
    * an IANA time zone identifier, e.g. "America/Chicago".
    */
   static Result<UniquePtr<DateTimeFormat>, ICUError> TryCreateFromStyle(
-      Span<const char> aLocale, DateTimeStyle aDateStyle,
-      DateTimeStyle aTimeStyle,
+      Span<const char> aLocale, const StyleBag& aStyleBag,
+      DateTimePatternGenerator* aDateTimePatternGenerator,
       Maybe<Span<const char16_t>> aTimeZoneOverride = Nothing{});
 
   /**
-   * Create a DateTimeFormat from a UTF-8 skeleton. See the UTF-16 version for
-   * the full documentation of this function. This overload requires additional
-   * work compared to the UTF-16 version.
-   */
-  static Result<UniquePtr<DateTimeFormat>, ICUError> TryCreateFromSkeleton(
-      Span<const char> aLocale, Span<const char> aSkeleton,
-      Maybe<Span<const char>> aTimeZoneOverride = Nothing{});
-
-  /**
    * Create a DateTimeFormat from a UTF-16 skeleton.
    *
    * A skeleton is an unordered list of fields that are used to find an
    * appropriate date time format pattern. Example skeletons would be "yMd",
    * "yMMMd", "EBhm". If the skeleton includes string literals or other
    * information, it will be discarded when matching against skeletons.
    *
    * Takes an optional time zone which will override the user's default
    * time zone. This is a string that takes the form "GMT±hh:mm", or
    * an IANA time zone identifier, e.g. "America/Chicago".
    */
   static Result<UniquePtr<DateTimeFormat>, ICUError> TryCreateFromSkeleton(
       Span<const char> aLocale, Span<const char16_t> aSkeleton,
+      DateTimePatternGenerator* aDateTimePatternGenerator,
+      Maybe<DateTimeFormat::HourCycle> aHourCycle = Nothing{},
       Maybe<Span<const char16_t>> aTimeZoneOverride = Nothing{});
 
+  /**
+   * Create a DateTimeFormat from a UTF-8 skeleton.
+   *
+   * See the TryCreateFromSkeleton for const char16_t for documentation.
+   */
+  static Result<UniquePtr<DateTimeFormat>, ICUError> TryCreateFromSkeleton(
+      Span<const char> aLocale, Span<const char> aSkeleton,
+      DateTimePatternGenerator* aDateTimePatternGenerator,
+      Maybe<DateTimeFormat::HourCycle> aHourCycle = Nothing{},
+      Maybe<Span<const char>> aTimeZoneOverride = Nothing{});
+
+  /**
+   * Create a DateTimeFormat from a ComponentsBag.
+   *
+   * See the ComponentsBag for additional documentation.
+   *
+   * Takes an optional time zone which will override the user's default
+   * time zone. This is a string that takes the form "GMT±hh:mm", or
+   * an IANA time zone identifier, e.g. "America/Chicago".
+   */
+  static Result<UniquePtr<DateTimeFormat>, ICUError> TryCreateFromComponents(
+      Span<const char> aLocale, const ComponentsBag& bag,
+      DateTimePatternGenerator* aDateTimePatternGenerator,
+      Maybe<Span<const char16_t>> aTimeZoneOverride = Nothing{});
+
+  /**
+   * Create a DateTimeFormat from a raw pattern.
+   *
+   * Warning: This method should not be added to new code. In the near future we
+   * plan to remove it.
+   */
   static Result<UniquePtr<DateTimeFormat>, ICUError> TryCreateFromPattern(
       Span<const char> aLocale, Span<const char16_t> aPattern,
       Maybe<Span<const char16_t>> aTimeZoneOverride = Nothing{});
 
   /**
    * Use the format settings to format a date time into a string. The non-null
    * terminated string will be placed into the provided buffer. The idea behind
    * this API is that the constructor is expensive, and then the format
@@ -177,17 +320,17 @@ class DateTimeFormat final {
         "The only buffer CharTypes supported by DateTimeFormat are char "
         "(for UTF-8 support) and char16_t (for UTF-16 support).");
 
     if constexpr (std::is_same<typename B::CharType, char>::value ||
                   std::is_same<typename B::CharType, unsigned char>::value) {
       // The output buffer is UTF-8, but ICU uses UTF-16 internally.
 
       // Write the formatted date into the u16Buffer.
-      mozilla::Vector<char16_t, StackU16VectorSize> u16Vec;
+      PatternVector u16Vec;
 
       auto result = FillVectorWithICUCall(
           u16Vec, [this, &aUnixEpoch](UChar* target, int32_t length,
                                       UErrorCode* status) {
             return udat_format(mDateFormat, aUnixEpoch, target, length,
                                /* UFieldPosition* */ nullptr, status);
           });
       if (result.isErr()) {
@@ -207,16 +350,19 @@ class DateTimeFormat final {
             return udat_format(mDateFormat, aUnixEpoch, target, length, nullptr,
                                status);
           });
     }
   };
 
   /**
    * Copies the pattern for the current DateTimeFormat to a buffer.
+   *
+   * Warning: This method should not be added to new code. In the near future we
+   * plan to remove it.
    */
   template <typename B>
   ICUResult GetPattern(B& aBuffer) const {
     return FillBufferWithICUCall(
         aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
           return udat_toPattern(mDateFormat, /* localized*/ false, target,
                                 length, status);
         });
@@ -224,36 +370,52 @@ class DateTimeFormat final {
 
   /**
    * Set the start time of the Gregorian calendar. This is useful for
    * ensuring the consistent use of a proleptic Gregorian calendar for ECMA-402.
    * https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
    */
   void SetStartTimeIfGregorian(double aTime);
 
+  /**
+   * Determines the resolved components for the current DateTimeFormat.
+   *
+   * When a DateTimeFormat is created, even from a components bag, the resolved
+   * formatter may tweak the resolved components depending on the configuration
+   * and the locale.
+   *
+   * For the implementation, with ICU4C, this takes a string pattern and maps it
+   * back to a ComponentsBag.
+   */
+  Result<ComponentsBag, ICUError> ResolveComponents();
+
   ~DateTimeFormat();
 
   /**
    * TODO(Bug 1686965) - Temporarily get the underlying ICU object while
    * migrating to the unified API. This should be removed when completing the
    * migration.
    */
   UDateFormat* UnsafeGetUDateFormat() const { return mDateFormat; }
 
   /**
    * Clones the Calendar from a DateTimeFormat, and sets its time with the
    * relative milliseconds since 1 January 1970, UTC.
    */
   Result<UniquePtr<Calendar>, InternalError> CloneCalendar(
       double aUnixEpoch) const;
 
+  /**
+   * Return the hour cycle used in the input pattern or Nothing if none was
+   * found.
+   */
+  static Maybe<DateTimeFormat::HourCycle> HourCycleFromPattern(
+      Span<const char16_t> aPattern);
+
  private:
   explicit DateTimeFormat(UDateFormat* aDateFormat);
 
-  // mozilla::Vector can avoid heap allocations for small transient buffers.
-  static constexpr size_t StackU16VectorSize = 128;
-
   UDateFormat* mDateFormat = nullptr;
 };
 
 }  // namespace mozilla::intl
 
 #endif
--- a/intl/components/src/DateTimePatternGenerator.h
+++ b/intl/components/src/DateTimePatternGenerator.h
@@ -73,32 +73,68 @@ class DateTimePatternGenerator final {
           return udatpg_getBestPatternWithOptions(
               mGenerator, aSkeleton.data(),
               static_cast<int32_t>(aSkeleton.Length()),
               toUDateTimePatternMatchOptions(options), target, length, status);
         });
   }
 
   /**
+   * Given a skeleton (a string with unordered datetime fields), get a best
+   * pattern that will fit for that locale. This pattern will be filled into the
+   * buffer. e.g. The skeleton "yMd" would return the pattern "M/d/y" for en-US,
+   * or "dd/MM/y" for en-GB.
+   */
+  template <size_t S>
+  ICUResult GetBestPattern(Span<const char16_t> aSkeleton,
+                           Vector<char16_t, S>& aVector,
+                           EnumSet<PatternMatchOption> options = {}) {
+    return FillVectorWithICUCall(
+        aVector, [&](UChar* target, int32_t length, UErrorCode* status) {
+          return udatpg_getBestPatternWithOptions(
+              mGenerator, aSkeleton.data(),
+              static_cast<int32_t>(aSkeleton.Length()),
+              toUDateTimePatternMatchOptions(options), target, length, status);
+        });
+  }
+
+  /**
    * Get a skeleton (a string with unordered datetime fields) from a pattern.
    * For example, both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
    */
   template <typename B>
   static ICUResult GetSkeleton(Span<const char16_t> aPattern, B& aBuffer) {
     // At one time udatpg_getSkeleton required a UDateTimePatternGenerator*, but
     // now it is valid to pass in a nullptr.
     return FillBufferWithICUCall(
         aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
           return udatpg_getSkeleton(nullptr, aPattern.data(),
                                     static_cast<int32_t>(aPattern.Length()),
                                     target, length, status);
         });
   }
 
   /**
+   * Get a skeleton (a string with unordered datetime fields) from a pattern.
+   * For example, both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
+   */
+  template <typename V, size_t N, typename A>
+  static ICUResult GetSkeleton(Span<const char16_t> aPattern,
+                               Vector<V, N, A>& aVector) {
+    // At one time udatpg_getSkeleton required a UDateTimePatternGenerator*, but
+    // now it is valid to pass in a nullptr.
+    return FillVectorWithICUCall(
+        aVector, [&](UChar* target, int32_t length, UErrorCode* status) {
+          return udatpg_getSkeleton(nullptr, aPattern.data(),
+                                    static_cast<int32_t>(aPattern.Length()),
+                                    target, length, status);
+        });
+  }
+
+  /**
    * TODO(Bug 1686965) - Temporarily get the underlying ICU object while
    * migrating to the unified API. This should be removed when completing the
    * migration.
    */
   UDateTimePatternGenerator* UnsafeGetUDateTimePatternGenerator() const {
     return mGenerator;
   }
 
--- a/intl/components/src/ICU4CGlue.h
+++ b/intl/components/src/ICU4CGlue.h
@@ -85,17 +85,18 @@ class ICUPointer {
  * stack-based buffer, the calls can often be done in one trip. However, if
  * additional memory is needed, this function will call the C-API twice, in
  * order to first get the size of the result, and then second to copy the result
  * over to the buffer.
  */
 template <typename ICUStringFunction, typename Buffer>
 static ICUResult FillBufferWithICUCall(Buffer& buffer,
                                        const ICUStringFunction& strFn) {
-  static_assert(std::is_same<typename Buffer::CharType, char16_t>::value);
+  static_assert(std::is_same_v<typename Buffer::CharType, char16_t> ||
+                std::is_same_v<typename Buffer::CharType, char>);
 
   UErrorCode status = U_ZERO_ERROR;
   int32_t length = strFn(buffer.data(), buffer.capacity(), &status);
   if (status == U_BUFFER_OVERFLOW_ERROR) {
     MOZ_ASSERT(length >= 0);
 
     if (!buffer.reserve(length)) {
       return Err(ICUError::OutOfMemory);
@@ -110,43 +111,49 @@ static ICUResult FillBufferWithICUCall(B
   }
 
   buffer.written(length);
 
   return Ok{};
 }
 
 /**
+ * Adaptor for mozilla::Vector to implement the Buffer interface.
+ */
+template <typename T, size_t N>
+class VectorToBufferAdaptor {
+  mozilla::Vector<T, N>& vector;
+
+ public:
+  using CharType = T;
+
+  explicit VectorToBufferAdaptor(mozilla::Vector<T, N>& vector)
+      : vector(vector) {}
+
+  T* data() { return vector.begin(); }
+
+  size_t capacity() const { return vector.capacity(); }
+
+  bool reserve(size_t length) { return vector.reserve(length); }
+
+  void written(size_t length) {
+    mozilla::DebugOnly<bool> result = vector.resizeUninitialized(length);
+    MOZ_ASSERT(result);
+  }
+};
+
+/**
  * A variant of FillBufferWithICUCall that accepts a mozilla::Vector rather than
  * a Buffer.
  */
 template <typename ICUStringFunction, size_t InlineSize, typename CharType>
 static ICUResult FillVectorWithICUCall(Vector<CharType, InlineSize>& vector,
                                        const ICUStringFunction& strFn) {
-  UErrorCode status = U_ZERO_ERROR;
-  int32_t length = strFn(vector.begin(), vector.capacity(), &status);
-  if (status == U_BUFFER_OVERFLOW_ERROR) {
-    MOZ_ASSERT(length >= 0);
-
-    if (!vector.reserve(length)) {
-      return Err(ICUError::OutOfMemory);
-    }
-
-    status = U_ZERO_ERROR;
-    mozilla::DebugOnly<int32_t> length2 =
-        strFn(vector.begin(), length, &status);
-    MOZ_ASSERT(length == length2);
-  }
-  if (!ICUSuccessForStringSpan(status)) {
-    return Err(ToICUError(status));
-  }
-
-  mozilla::DebugOnly<bool> result = vector.resizeUninitialized(length);
-  MOZ_ASSERT(result);
-  return Ok{};
+  VectorToBufferAdaptor buffer(vector);
+  return FillBufferWithICUCall(buffer, strFn);
 }
 
 /**
  * ICU4C works with UTF-16 strings, but consumers of mozilla::intl may require
  * UTF-8 strings.
  */
 template <typename Buffer>
 [[nodiscard]] bool FillUTF8Buffer(Span<const char16_t> utf16Span,
--- a/intl/l10n/FluentBundle.cpp
+++ b/intl/l10n/FluentBundle.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 
 #include "FluentBundle.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/intl/NumberFormat.h"
 #include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePatternGenerator.h"
 #include "nsIInputStream.h"
 #include "nsStringFwd.h"
 #include "nsTArray.h"
 #include "js/PropertyAndElement.h"  // JS_DefineElement
 
 using namespace mozilla::dom;
 
 namespace mozilla {
@@ -318,54 +319,66 @@ uint8_t* FluentBuiltInNumberFormatterFor
 }
 
 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter* aFormatter) {
   delete reinterpret_cast<NumberFormat*>(aFormatter);
 }
 
 /* DateTime */
 
-static DateTimeStyle GetStyle(ffi::FluentDateTimeStyle aStyle) {
+static Maybe<DateTimeFormat::Style> GetStyle(ffi::FluentDateTimeStyle aStyle) {
   switch (aStyle) {
     case ffi::FluentDateTimeStyle::Full:
-      return DateTimeStyle::Full;
+      return Some(DateTimeFormat::Style::Full);
     case ffi::FluentDateTimeStyle::Long:
-      return DateTimeStyle::Long;
+      return Some(DateTimeFormat::Style::Long);
     case ffi::FluentDateTimeStyle::Medium:
-      return DateTimeStyle::Medium;
+      return Some(DateTimeFormat::Style::Medium);
     case ffi::FluentDateTimeStyle::Short:
-      return DateTimeStyle::Short;
+      return Some(DateTimeFormat::Style::Short);
     case ffi::FluentDateTimeStyle::None:
-      return DateTimeStyle::None;
-    default:
-      MOZ_ASSERT_UNREACHABLE("Unsupported date time style.");
-      return DateTimeStyle::None;
+      return Nothing();
   }
+  MOZ_ASSERT_UNREACHABLE();
+  return Nothing();
 }
 
 ffi::RawDateTimeFormatter* FluentBuiltInDateTimeFormatterCreate(
     const nsCString* aLocale, const ffi::FluentDateTimeOptionsRaw* aOptions) {
+  auto genResult = DateTimePatternGenerator::TryCreate(aLocale->get());
+  if (genResult.isErr()) {
+    MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
+    return nullptr;
+  }
+  UniquePtr<DateTimePatternGenerator> dateTimePatternGenerator =
+      genResult.unwrap();
+
   if (aOptions->date_style == ffi::FluentDateTimeStyle::None &&
       aOptions->time_style == ffi::FluentDateTimeStyle::None &&
       !aOptions->skeleton.IsEmpty()) {
     auto result = DateTimeFormat::TryCreateFromSkeleton(
         Span(aLocale->get(), aLocale->Length()),
-        Span(aOptions->skeleton.get(), aOptions->skeleton.Length()));
+        Span(aOptions->skeleton.get(), aOptions->skeleton.Length()),
+        dateTimePatternGenerator.get(), Nothing());
     if (result.isErr()) {
       MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
       return nullptr;
     }
 
     return reinterpret_cast<ffi::RawDateTimeFormatter*>(
         result.unwrap().release());
   }
 
+  DateTimeFormat::StyleBag style;
+  style.date = GetStyle(aOptions->date_style);
+  style.time = GetStyle(aOptions->time_style);
+
   auto result = DateTimeFormat::TryCreateFromStyle(
-      Span(aLocale->get(), aLocale->Length()), GetStyle(aOptions->date_style),
-      GetStyle(aOptions->time_style));
+      Span(aLocale->get(), aLocale->Length()), style,
+      dateTimePatternGenerator.get());
 
   if (result.isErr()) {
     MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
     return nullptr;
   }
 
   return reinterpret_cast<ffi::RawDateTimeFormatter*>(
       result.unwrap().release());