Bug 1719696 - Naively move the hour cycle computations; r=anba,platform-i18n-reviewers,dminor
authorGreg Tatum <tatum.creative@gmail.com>
Mon, 13 Sep 2021 20:52:59 +0000
changeset 591821 79a3304d03df928bf0422e7bcba8554f60fe58c8
parent 591820 60b2ef13fc96e511dfaf0cade53e555f86bc15b3
child 591822 0b5d9fdd57a2fc4207dd8aff99e58fd73c3150f3
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 - Naively move the hour cycle computations; r=anba,platform-i18n-reviewers,dminor This patch will not compile. It naively copies over the hour cycle code from SpiderMonkey into the unified components. In a following patch, these will be modified to use only the unified types. Differential Revision: https://phabricator.services.mozilla.com/D123821
intl/components/src/DateTimeFormat.cpp
intl/components/src/DateTimeFormat.h
js/src/builtin/intl/DateTimeFormat.cpp
js/src/builtin/intl/DateTimeFormat.js
--- a/intl/components/src/DateTimeFormat.cpp
+++ b/intl/components/src/DateTimeFormat.cpp
@@ -31,16 +31,233 @@ static UDateFormatStyle ToUDateFormatSty
     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_;
+
+ public:
+  explicit PatternIterator(mozilla::Span<CharT> pattern)
+      : iter_(pattern.data()), end_(pattern.data() + pattern.size()) {}
+
+  CharT* next() {
+    MOZ_ASSERT(iter_ != nullptr);
+
+    bool inQuote = false;
+    while (iter_ < end_) {
+      CharT* cur = iter_++;
+      if (*cur == '\'') {
+        inQuote = !inQuote;
+      } else if (!inQuote) {
+        return cur;
+      }
+    }
+
+    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);
+  while (const auto* ptr = iter.next()) {
+    switch (*ptr) {
+      case 'K':
+        return mozilla::Some(HourCycle::H11);
+      case 'h':
+        return mozilla::Some(HourCycle::H12);
+      case 'H':
+        return mozilla::Some(HourCycle::H23);
+      case 'k':
+        return mozilla::Some(HourCycle::H24);
+    }
+  }
+  return mozilla::Nothing();
+}
+
+static bool IsHour12(HourCycle hc) {
+  return hc == HourCycle::H11 || hc == HourCycle::H12;
+}
+
+static char16_t HourSymbol(HourCycle hc) {
+  switch (hc) {
+    case HourCycle::H11:
+      return 'K';
+    case HourCycle::H12:
+      return 'h';
+    case HourCycle::H23:
+      return 'H';
+    case 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') {
+    return PatternField::Hour;
+  }
+  if (ch == 'm') {
+    return PatternField::Minute;
+  }
+  if (ch == '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);
+  while (auto* ptr = iter.next()) {
+    auto field = ToPatternField(*ptr);
+    if (field == PatternField::Hour) {
+      *ptr = replacement;
+    }
+  }
+}
+
+/**
+ * Find a matching pattern using the requested hour-12 options.
+ *
+ * This function is needed to work around the following two issues.
+ * - https://unicode-org.atlassian.net/browse/ICU-21023
+ * - https://unicode-org.atlassian.net/browse/CLDR-13425
+ *
+ * We're currently using a relatively simple workaround, which doesn't give the
+ * most accurate results. For example:
+ *
+ * ```
+ * var dtf = new Intl.DateTimeFormat("en", {
+ *   timeZone: "UTC",
+ *   dateStyle: "long",
+ *   timeStyle: "long",
+ *   hourCycle: "h12",
+ * });
+ * print(dtf.format(new Date("2020-01-01T00:00Z")));
+ * ```
+ *
+ * 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;
+  }
+
+  // Input skeletons don't differentiate between "K" and "h" resp. "k" and "H".
+  ReplaceHourSymbol(skeleton, hour12 ? HourCycle::H12 : HourCycle::H23);
+
+  auto result = gen->GetBestPattern(skeleton, pattern);
+  if (result.isErr()) {
+    intl::ReportInternalError(cx, result.unwrapErr());
+    return false;
+  }
+  return true;
+}
+
+static auto PatternMatchOptions(mozilla::Span<const char16_t> skeleton) {
+  // 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);
+  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);
+        minute += 1;
+        break;
+      case PatternField::Second:
+        MOZ_ASSERT(second < 2);
+        second += 1;
+        break;
+      case PatternField::Other:
+        break;
+    }
+  }
+
+  // Adjust the field length when the user requested '2-digit' representation.
+  //
+  // We can't just always adjust the field length, because
+  // 1. The default value for hour, minute, and second fields is 'numeric'. If
+  //    the length is always adjusted, |date.toLocaleTime()| will start to
+  //    return strings like "1:5:9 AM" instead of "1:05:09 AM".
+  // 2. ICU doesn't support to adjust the field length to 'numeric' in certain
+  //    cases. For example when the locale is "de" (German):
+  //      a. hour='numeric' and minute='2-digit' will return "1:05".
+  //      b. whereas hour='numeric' and minute='numeric' will return "01:05".
+  //
+  // Therefore we only support adjusting the field length when the user
+  // explicitly requested the '2-digit' representation.
+
+  using PatternMatchOption =
+      mozilla::intl::DateTimePatternGenerator::PatternMatchOption;
+  mozilla::EnumSet<PatternMatchOption> options;
+  if (hour == 2) {
+    options += PatternMatchOption::HourField;
+  }
+  if (minute == 2) {
+    options += PatternMatchOption::MinuteField;
+  }
+  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);
 
--- a/intl/components/src/DateTimeFormat.h
+++ b/intl/components/src/DateTimeFormat.h
@@ -32,16 +32,85 @@ class Calendar;
  * 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 API will also serve to back the ECMA-402 Intl.DateTimeFormat API.
  * See Bug 1709473.
  * https://tc39.es/ecma402/#datetimeformat-objects
+ *
+ * Intl.DateTimeFormat and 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:
+ *
+ * 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"
+ *    specifies a year with at least four digits, a full month name, and a
+ *    two-digit day. It does not specify in which order the components appear,
+ *    how they are separated, the localized strings for textual components
+ *    (such as weekday or month), whether the month is in format or
+ *    stand-alone form¹, or the numbering system used for numeric components.
+ *    All that information is filled in by ICU using CLDR locale data.
+ *    ¹ The format form is the one used in formatted strings that include a
+ *    day; the stand-alone form is used when not including days, e.g., in
+ *    calendar headers. The two forms differ at least in some Slavic languages,
+ *    e.g. Russian: "22 марта 2013 г." vs. "Март 2013".
+ *
+ * 3) A pattern is a string specifying which date-time components to include,
+ *    in which order, with which separators, in which grammatical case. For
+ *    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
+ * 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.
+ *
+ * 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.
+ *
+ * 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
  */
 class DateTimeFormat final {
  public:
   // 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;
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -508,65 +508,16 @@ enum class HourCycle {
 
   // 24 hour cycle, from 0 to 23.
   H23,
 
   // 24 hour cycle, from 1 to 24.
   H24
 };
 
-static bool IsHour12(HourCycle hc) {
-  return hc == HourCycle::H11 || hc == HourCycle::H12;
-}
-
-static char16_t HourSymbol(HourCycle hc) {
-  switch (hc) {
-    case HourCycle::H11:
-      return 'K';
-    case HourCycle::H12:
-      return 'h';
-    case HourCycle::H23:
-      return 'H';
-    case HourCycle::H24:
-      return 'k';
-  }
-  MOZ_CRASH("unexpected hour cycle");
-}
-
-/**
- * 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_;
-
- public:
-  explicit PatternIterator(mozilla::Span<CharT> pattern)
-      : iter_(pattern.data()), end_(pattern.data() + pattern.size()) {}
-
-  CharT* next() {
-    MOZ_ASSERT(iter_ != nullptr);
-
-    bool inQuote = false;
-    while (iter_ < end_) {
-      CharT* cur = iter_++;
-      if (*cur == '\'') {
-        inQuote = !inQuote;
-      } else if (!inQuote) {
-        return cur;
-      }
-    }
-
-    iter_ = nullptr;
-    return nullptr;
-  }
-};
-
 /**
  * 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")) {
@@ -574,128 +525,16 @@ static HourCycle HourCycleFromOption(JSL
   }
   if (StringEqualsLiteral(str, "h23")) {
     return HourCycle::H23;
   }
   MOZ_ASSERT(StringEqualsLiteral(str, "h24"));
   return HourCycle::H24;
 }
 
-/**
- * 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);
-  while (const auto* ptr = iter.next()) {
-    switch (*ptr) {
-      case 'K':
-        return mozilla::Some(HourCycle::H11);
-      case 'h':
-        return mozilla::Some(HourCycle::H12);
-      case 'H':
-        return mozilla::Some(HourCycle::H23);
-      case 'k':
-        return mozilla::Some(HourCycle::H24);
-    }
-  }
-  return mozilla::Nothing();
-}
-
-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') {
-    return PatternField::Hour;
-  }
-  if (ch == 'm') {
-    return PatternField::Minute;
-  }
-  if (ch == '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);
-  while (auto* ptr = iter.next()) {
-    auto field = ToPatternField(*ptr);
-    if (field == PatternField::Hour) {
-      *ptr = replacement;
-    }
-  }
-}
-
-static auto PatternMatchOptions(mozilla::Span<const char16_t> skeleton) {
-  // 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);
-  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);
-        minute += 1;
-        break;
-      case PatternField::Second:
-        MOZ_ASSERT(second < 2);
-        second += 1;
-        break;
-      case PatternField::Other:
-        break;
-    }
-  }
-
-  // Adjust the field length when the user requested '2-digit' representation.
-  //
-  // We can't just always adjust the field length, because
-  // 1. The default value for hour, minute, and second fields is 'numeric'. If
-  //    the length is always adjusted, |date.toLocaleTime()| will start to
-  //    return strings like "1:5:9 AM" instead of "1:05:09 AM".
-  // 2. ICU doesn't support to adjust the field length to 'numeric' in certain
-  //    cases. For example when the locale is "de" (German):
-  //      a. hour='numeric' and minute='2-digit' will return "1:05".
-  //      b. whereas hour='numeric' and minute='numeric' will return "01:05".
-  //
-  // Therefore we only support adjusting the field length when the user
-  // explicitly requested the '2-digit' representation.
-
-  using PatternMatchOption =
-      mozilla::intl::DateTimePatternGenerator::PatternMatchOption;
-  mozilla::EnumSet<PatternMatchOption> options;
-  if (hour == 2) {
-    options += PatternMatchOption::HourField;
-  }
-  if (minute == 2) {
-    options += PatternMatchOption::MinuteField;
-  }
-  if (second == 2) {
-    options += PatternMatchOption::SecondField;
-  }
-  return options;
-}
-
 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());
@@ -744,72 +583,16 @@ bool js::intl_patternForSkeleton(JSConte
   JSString* str = pattern.toString();
   if (!str) {
     return false;
   }
   args.rval().setString(str);
   return true;
 }
 
-/**
- * Find a matching pattern using the requested hour-12 options.
- *
- * This function is needed to work around the following two issues.
- * - https://unicode-org.atlassian.net/browse/ICU-21023
- * - https://unicode-org.atlassian.net/browse/CLDR-13425
- *
- * We're currently using a relatively simple workaround, which doesn't give the
- * most accurate results. For example:
- *
- * ```
- * var dtf = new Intl.DateTimeFormat("en", {
- *   timeZone: "UTC",
- *   dateStyle: "long",
- *   timeStyle: "long",
- *   hourCycle: "h12",
- * });
- * print(dtf.format(new Date("2020-01-01T00:00Z")));
- * ```
- *
- * 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;
-  }
-
-  // Input skeletons don't differentiate between "K" and "h" resp. "k" and "H".
-  ReplaceHourSymbol(skeleton, hour12 ? HourCycle::H12 : HourCycle::H23);
-
-  auto result = gen->GetBestPattern(skeleton, pattern);
-  if (result.isErr()) {
-    intl::ReportInternalError(cx, result.unwrapErr());
-    return false;
-  }
-  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());
--- a/js/src/builtin/intl/DateTimeFormat.js
+++ b/js/src/builtin/intl/DateTimeFormat.js
@@ -452,85 +452,16 @@ function InitializeDateTimeFormat(dateTi
 
         return thisValue;
     }
 
     // 12.2.1, step 6.
     return dateTimeFormat;
 }
 
-// Intl.DateTimeFormat and 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:
-//
-// 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"
-//    specifies a year with at least four digits, a full month name, and a
-//    two-digit day. It does not specify in which order the components appear,
-//    how they are separated, the localized strings for textual components
-//    (such as weekday or month), whether the month is in format or
-//    stand-alone form¹, or the numbering system used for numeric components.
-//    All that information is filled in by ICU using CLDR locale data.
-//    ¹ The format form is the one used in formatted strings that include a
-//    day; the stand-alone form is used when not including days, e.g., in
-//    calendar headers. The two forms differ at least in some Slavic languages,
-//    e.g. Russian: "22 марта 2013 г." vs. "Март 2013".
-//
-// 3) A pattern is a string specifying which date-time components to include,
-//    in which order, with which separators, in which grammatical case. For
-//    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
-// 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.
-//
-// 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.
-//
-// 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
-
 /* 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 = "";