tor-browser

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

DateTimeFormat.cpp (41424B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include <algorithm>
      6 #include <cstring>
      7 
      8 #include "unicode/ucal.h"
      9 #include "unicode/udat.h"
     10 #include "unicode/udatpg.h"
     11 #include "unicode/ures.h"
     12 
     13 #include "DateTimeFormatUtils.h"
     14 #include "ScopedICUObject.h"
     15 
     16 #include "mozilla/Buffer.h"
     17 #include "mozilla/EnumSet.h"
     18 #include "mozilla/intl/Calendar.h"
     19 #include "mozilla/intl/DateTimeFormat.h"
     20 #include "mozilla/intl/DateTimePatternGenerator.h"
     21 
     22 namespace mozilla::intl {
     23 
     24 DateTimeFormat::~DateTimeFormat() {
     25  MOZ_ASSERT(mDateFormat);
     26  udat_close(mDateFormat);
     27 }
     28 
     29 static UDateFormatStyle ToUDateFormatStyle(
     30    Maybe<DateTimeFormat::Style> aLength) {
     31  if (!aLength) {
     32    return UDAT_NONE;
     33  }
     34  switch (*aLength) {
     35    case DateTimeFormat::Style::Full:
     36      return UDAT_FULL;
     37    case DateTimeFormat::Style::Long:
     38      return UDAT_LONG;
     39    case DateTimeFormat::Style::Medium:
     40      return UDAT_MEDIUM;
     41    case DateTimeFormat::Style::Short:
     42      return UDAT_SHORT;
     43  }
     44  MOZ_ASSERT_UNREACHABLE();
     45  // Do not use the default: branch so that the enum is exhaustively checked.
     46  return UDAT_NONE;
     47 }
     48 
     49 /**
     50 * Parse a pattern according to the format specified in
     51 * <https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>.
     52 */
     53 template <typename CharT>
     54 class PatternIterator {
     55  CharT* iter;
     56  const CharT* const end;
     57 
     58 public:
     59  explicit PatternIterator(mozilla::Span<CharT> aPattern)
     60      : iter(aPattern.data()), end(aPattern.data() + aPattern.size()) {}
     61 
     62  CharT* next() {
     63    MOZ_ASSERT(iter != nullptr);
     64 
     65    bool inQuote = false;
     66    while (iter < end) {
     67      CharT* cur = iter++;
     68      if (*cur == '\'') {
     69        inQuote = !inQuote;
     70      } else if (!inQuote) {
     71        return cur;
     72      }
     73    }
     74 
     75    iter = nullptr;
     76    return nullptr;
     77  }
     78 };
     79 
     80 Maybe<DateTimeFormat::HourCycle> DateTimeFormat::HourCycleFromPattern(
     81    Span<const char16_t> aPattern) {
     82  PatternIterator<const char16_t> iter(aPattern);
     83  while (const auto* ptr = iter.next()) {
     84    switch (*ptr) {
     85      case 'K':
     86        return Some(DateTimeFormat::HourCycle::H11);
     87      case 'h':
     88        return Some(DateTimeFormat::HourCycle::H12);
     89      case 'H':
     90        return Some(DateTimeFormat::HourCycle::H23);
     91      case 'k':
     92        return Some(DateTimeFormat::HourCycle::H24);
     93    }
     94  }
     95  return Nothing();
     96 }
     97 
     98 static bool IsHour12(DateTimeFormat::HourCycle aHourCycle) {
     99  return aHourCycle == DateTimeFormat::HourCycle::H11 ||
    100         aHourCycle == DateTimeFormat::HourCycle::H12;
    101 }
    102 
    103 static char16_t HourSymbol(DateTimeFormat::HourCycle aHourCycle) {
    104  switch (aHourCycle) {
    105    case DateTimeFormat::HourCycle::H11:
    106      return 'K';
    107    case DateTimeFormat::HourCycle::H12:
    108      return 'h';
    109    case DateTimeFormat::HourCycle::H23:
    110      return 'H';
    111    case DateTimeFormat::HourCycle::H24:
    112      return 'k';
    113  }
    114  MOZ_CRASH("unexpected hour cycle");
    115 }
    116 
    117 enum class PatternField { Hour, Minute, Second, Other };
    118 
    119 template <typename CharT>
    120 static PatternField ToPatternField(CharT aCh) {
    121  if (aCh == 'K' || aCh == 'h' || aCh == 'H' || aCh == 'k' || aCh == 'j') {
    122    return PatternField::Hour;
    123  }
    124  if (aCh == 'm') {
    125    return PatternField::Minute;
    126  }
    127  if (aCh == 's') {
    128    return PatternField::Second;
    129  }
    130  return PatternField::Other;
    131 }
    132 
    133 /**
    134 * Replaces all hour pattern characters in |patternOrSkeleton| to use the
    135 * matching hour representation for |hourCycle|.
    136 */
    137 /* static */
    138 void DateTimeFormat::ReplaceHourSymbol(
    139    mozilla::Span<char16_t> aPatternOrSkeleton,
    140    DateTimeFormat::HourCycle aHourCycle) {
    141  char16_t replacement = HourSymbol(aHourCycle);
    142  PatternIterator<char16_t> iter(aPatternOrSkeleton);
    143  while (auto* ptr = iter.next()) {
    144    auto field = ToPatternField(*ptr);
    145    if (field == PatternField::Hour) {
    146      *ptr = replacement;
    147    }
    148  }
    149 }
    150 
    151 /**
    152 * Find a matching pattern using the requested hour-12 options.
    153 *
    154 * This function is needed to work around the following two issues.
    155 * - https://unicode-org.atlassian.net/browse/ICU-21023
    156 * - https://unicode-org.atlassian.net/browse/CLDR-13425
    157 *
    158 * We're currently using a relatively simple workaround, which doesn't give the
    159 * most accurate results. For example:
    160 *
    161 * ```
    162 * var dtf = new Intl.DateTimeFormat("en", {
    163 *   timeZone: "UTC",
    164 *   dateStyle: "long",
    165 *   timeStyle: "long",
    166 *   hourCycle: "h12",
    167 * });
    168 * print(dtf.format(new Date("2020-01-01T00:00Z")));
    169 * ```
    170 *
    171 * Returns the pattern "MMMM d, y 'at' h:mm:ss a z", but when going through
    172 * |DateTimePatternGenerator::GetSkeleton| and then
    173 * |DateTimePatternGenerator::GetBestPattern| to find an equivalent pattern for
    174 * "h23", we'll end up with the pattern "MMMM d, y, HH:mm:ss z", so the
    175 * combinator element " 'at' " was lost in the process.
    176 */
    177 /* static */
    178 ICUResult DateTimeFormat::FindPatternWithHourCycle(
    179    DateTimePatternGenerator& aDateTimePatternGenerator,
    180    DateTimeFormat::PatternVector& aPattern, bool aHour12,
    181    DateTimeFormat::SkeletonVector& aSkeleton) {
    182  MOZ_TRY(mozilla::intl::DateTimePatternGenerator::GetSkeleton(aPattern,
    183                                                               aSkeleton));
    184 
    185  // Input skeletons don't differentiate between "K" and "h" resp. "k" and "H".
    186  DateTimeFormat::ReplaceHourSymbol(aSkeleton,
    187                                    aHour12 ? DateTimeFormat::HourCycle::H12
    188                                            : DateTimeFormat::HourCycle::H23);
    189 
    190  MOZ_TRY(aDateTimePatternGenerator.GetBestPattern(aSkeleton, aPattern));
    191 
    192  return Ok();
    193 }
    194 
    195 static auto PatternMatchOptions(mozilla::Span<const char16_t> aSkeleton) {
    196  // Values for hour, minute, and second are:
    197  // - absent: 0
    198  // - numeric: 1
    199  // - 2-digit: 2
    200  int32_t hour = 0;
    201  int32_t minute = 0;
    202  int32_t second = 0;
    203 
    204  PatternIterator<const char16_t> iter(aSkeleton);
    205  while (const auto* ptr = iter.next()) {
    206    switch (ToPatternField(*ptr)) {
    207      case PatternField::Hour:
    208        MOZ_ASSERT(hour < 2);
    209        hour += 1;
    210        break;
    211      case PatternField::Minute:
    212        MOZ_ASSERT(minute < 2);
    213        minute += 1;
    214        break;
    215      case PatternField::Second:
    216        MOZ_ASSERT(second < 2);
    217        second += 1;
    218        break;
    219      case PatternField::Other:
    220        break;
    221    }
    222  }
    223 
    224  // Adjust the field length when the user requested '2-digit' representation.
    225  //
    226  // We can't just always adjust the field length, because
    227  // 1. The default value for hour, minute, and second fields is 'numeric'. If
    228  //    the length is always adjusted, |date.toLocaleTime()| will start to
    229  //    return strings like "1:5:9 AM" instead of "1:05:09 AM".
    230  // 2. ICU doesn't support to adjust the field length to 'numeric' in certain
    231  //    cases. For example when the locale is "de" (German):
    232  //      a. hour='numeric' and minute='2-digit' will return "1:05".
    233  //      b. whereas hour='numeric' and minute='numeric' will return "01:05".
    234  //
    235  // Therefore we only support adjusting the field length when the user
    236  // explicitly requested the '2-digit' representation.
    237 
    238  using PatternMatchOption =
    239      mozilla::intl::DateTimePatternGenerator::PatternMatchOption;
    240  mozilla::EnumSet<PatternMatchOption> options;
    241  if (hour == 2) {
    242    options += PatternMatchOption::HourField;
    243  }
    244  if (minute == 2) {
    245    options += PatternMatchOption::MinuteField;
    246  }
    247  if (second == 2) {
    248    options += PatternMatchOption::SecondField;
    249  }
    250  return options;
    251 }
    252 
    253 /* static */
    254 Result<UniquePtr<DateTimeFormat>, ICUError> DateTimeFormat::TryCreateFromStyle(
    255    Span<const char> aLocale, const StyleBag& aStyleBag,
    256    DateTimePatternGenerator* aDateTimePatternGenerator,
    257    Maybe<Span<const char16_t>> aTimeZoneOverride) {
    258  auto dateStyle = ToUDateFormatStyle(aStyleBag.date);
    259  auto timeStyle = ToUDateFormatStyle(aStyleBag.time);
    260 
    261  if (dateStyle == UDAT_NONE && timeStyle == UDAT_NONE) {
    262    dateStyle = UDAT_DEFAULT;
    263    timeStyle = UDAT_DEFAULT;
    264  }
    265 
    266  // The time zone is optional.
    267  int32_t tzIDLength = -1;
    268  const UChar* tzID = nullptr;
    269  if (aTimeZoneOverride) {
    270    tzIDLength = static_cast<int32_t>(aTimeZoneOverride->size());
    271    tzID = aTimeZoneOverride->Elements();
    272  }
    273 
    274  UErrorCode status = U_ZERO_ERROR;
    275  UDateFormat* dateFormat =
    276      udat_open(timeStyle, dateStyle, IcuLocale(aLocale), tzID, tzIDLength,
    277                /* pattern */ nullptr, /* pattern length */ -1, &status);
    278  if (U_FAILURE(status)) {
    279    return Err(ToICUError(status));
    280  }
    281 
    282  MOZ_TRY(ApplyCalendarOverride(dateFormat));
    283 
    284  auto df = UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
    285 
    286  if (aStyleBag.time && (aStyleBag.hour12 || aStyleBag.hourCycle)) {
    287    // Only adjust the style pattern for time if there is an override.
    288    // Extract the pattern and adjust it for the preferred hour cycle.
    289    DateTimeFormat::PatternVector pattern{};
    290 
    291    VectorToBufferAdaptor buffer(pattern);
    292    MOZ_TRY(df->GetPattern(buffer));
    293 
    294    Maybe<DateTimeFormat::HourCycle> hcPattern = HourCycleFromPattern(pattern);
    295    DateTimeFormat::SkeletonVector skeleton{};
    296 
    297    if (hcPattern) {
    298      bool wantHour12 =
    299          aStyleBag.hour12 ? *aStyleBag.hour12 : IsHour12(*aStyleBag.hourCycle);
    300      if (wantHour12 == IsHour12(*hcPattern)) {
    301        // Return the date-time format when its hour-cycle settings match the
    302        // requested options.
    303        if (aStyleBag.hour12 || *hcPattern == *aStyleBag.hourCycle) {
    304          return df;
    305        }
    306      } else {
    307        MOZ_ASSERT(aDateTimePatternGenerator);
    308        MOZ_TRY(DateTimeFormat::FindPatternWithHourCycle(
    309            *aDateTimePatternGenerator, pattern, wantHour12, skeleton));
    310      }
    311      // Replace the hourCycle, if present, in the pattern string. But only do
    312      // this if no hour12 option is present, because the latter takes
    313      // precedence over hourCycle.
    314      if (!aStyleBag.hour12) {
    315        DateTimeFormat::ReplaceHourSymbol(pattern, *aStyleBag.hourCycle);
    316      }
    317 
    318      auto result = DateTimeFormat::TryCreateFromPattern(aLocale, pattern,
    319                                                         aTimeZoneOverride);
    320      if (result.isErr()) {
    321        return Err(result.unwrapErr());
    322      }
    323      auto dateTimeFormat = result.unwrap();
    324      MOZ_TRY(dateTimeFormat->CacheSkeleton(skeleton));
    325      return dateTimeFormat;
    326    }
    327  }
    328 
    329  return df;
    330 }
    331 
    332 DateTimeFormat::DateTimeFormat(UDateFormat* aDateFormat) {
    333  MOZ_RELEASE_ASSERT(aDateFormat, "Expected aDateFormat to not be a nullptr.");
    334  mDateFormat = aDateFormat;
    335 }
    336 
    337 // A helper to ergonomically push a string onto a string vector.
    338 template <typename V, size_t N>
    339 static ICUResult PushString(V& aVec, const char16_t (&aString)[N]) {
    340  if (!aVec.append(aString, N - 1)) {
    341    return Err(ICUError::OutOfMemory);
    342  }
    343  return Ok();
    344 }
    345 
    346 // A helper to ergonomically push a char onto a string vector.
    347 template <typename V>
    348 static ICUResult PushChar(V& aVec, char16_t aCh) {
    349  if (!aVec.append(aCh)) {
    350    return Err(ICUError::OutOfMemory);
    351  }
    352  return Ok();
    353 }
    354 
    355 /**
    356 * Returns an ICU skeleton string representing the specified options.
    357 * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
    358 */
    359 ICUResult ToICUSkeleton(const DateTimeFormat::ComponentsBag& aBag,
    360                        DateTimeFormat::SkeletonVector& aSkeleton) {
    361  // Create an ICU skeleton representing the specified aBag. See
    362  if (aBag.weekday) {
    363    switch (*aBag.weekday) {
    364      case DateTimeFormat::Text::Narrow:
    365        MOZ_TRY(PushString(aSkeleton, u"EEEEE"));
    366        break;
    367      case DateTimeFormat::Text::Short:
    368        MOZ_TRY(PushString(aSkeleton, u"E"));
    369        break;
    370      case DateTimeFormat::Text::Long:
    371        MOZ_TRY(PushString(aSkeleton, u"EEEE"));
    372    }
    373  }
    374  if (aBag.era) {
    375    switch (*aBag.era) {
    376      case DateTimeFormat::Text::Narrow:
    377        MOZ_TRY(PushString(aSkeleton, u"GGGGG"));
    378        break;
    379      case DateTimeFormat::Text::Short:
    380        // Use "GGG" instead of "G" to return the same results as other
    381        // browsers. This is exploiting the following ICU bug
    382        // <https://unicode-org.atlassian.net/browse/ICU-22138>. As soon as that
    383        // bug has been fixed, we can change this back to "G".
    384        //
    385        // In practice the bug only affects "G", so we only apply it for "G"
    386        // and not for other symbols like "B" or "z".
    387        MOZ_TRY(PushString(aSkeleton, u"GGG"));
    388        break;
    389      case DateTimeFormat::Text::Long:
    390        MOZ_TRY(PushString(aSkeleton, u"GGGG"));
    391        break;
    392    }
    393  }
    394  if (aBag.year) {
    395    switch (*aBag.year) {
    396      case DateTimeFormat::Numeric::TwoDigit:
    397        MOZ_TRY(PushString(aSkeleton, u"yy"));
    398        break;
    399      case DateTimeFormat::Numeric::Numeric:
    400        MOZ_TRY(PushString(aSkeleton, u"y"));
    401        break;
    402    }
    403  }
    404  if (aBag.month) {
    405    switch (*aBag.month) {
    406      case DateTimeFormat::Month::TwoDigit:
    407        MOZ_TRY(PushString(aSkeleton, u"MM"));
    408        break;
    409      case DateTimeFormat::Month::Numeric:
    410        MOZ_TRY(PushString(aSkeleton, u"M"));
    411        break;
    412      case DateTimeFormat::Month::Narrow:
    413        MOZ_TRY(PushString(aSkeleton, u"MMMMM"));
    414        break;
    415      case DateTimeFormat::Month::Short:
    416        MOZ_TRY(PushString(aSkeleton, u"MMM"));
    417        break;
    418      case DateTimeFormat::Month::Long:
    419        MOZ_TRY(PushString(aSkeleton, u"MMMM"));
    420        break;
    421    }
    422  }
    423  if (aBag.day) {
    424    switch (*aBag.day) {
    425      case DateTimeFormat::Numeric::TwoDigit:
    426        MOZ_TRY(PushString(aSkeleton, u"dd"));
    427        break;
    428      case DateTimeFormat::Numeric::Numeric:
    429        MOZ_TRY(PushString(aSkeleton, u"d"));
    430        break;
    431    }
    432  }
    433 
    434  // If hour12 and hourCycle are both present, hour12 takes precedence.
    435  char16_t hourSkeletonChar = 'j';
    436  if (aBag.hour12) {
    437    if (*aBag.hour12) {
    438      hourSkeletonChar = 'h';
    439    } else {
    440      hourSkeletonChar = 'H';
    441    }
    442  } else if (aBag.hourCycle) {
    443    switch (*aBag.hourCycle) {
    444      case DateTimeFormat::HourCycle::H11:
    445      case DateTimeFormat::HourCycle::H12:
    446        hourSkeletonChar = 'h';
    447        break;
    448      case DateTimeFormat::HourCycle::H23:
    449      case DateTimeFormat::HourCycle::H24:
    450        hourSkeletonChar = 'H';
    451        break;
    452    }
    453  }
    454  if (aBag.hour) {
    455    switch (*aBag.hour) {
    456      case DateTimeFormat::Numeric::TwoDigit:
    457        MOZ_TRY(PushChar(aSkeleton, hourSkeletonChar));
    458        MOZ_TRY(PushChar(aSkeleton, hourSkeletonChar));
    459        break;
    460      case DateTimeFormat::Numeric::Numeric:
    461        MOZ_TRY(PushChar(aSkeleton, hourSkeletonChar));
    462        break;
    463    }
    464  }
    465  // ICU requires that "B" is set after the "j" hour skeleton symbol.
    466  // https://unicode-org.atlassian.net/browse/ICU-20731
    467  if (aBag.dayPeriod) {
    468    switch (*aBag.dayPeriod) {
    469      case DateTimeFormat::Text::Narrow:
    470        MOZ_TRY(PushString(aSkeleton, u"BBBBB"));
    471        break;
    472      case DateTimeFormat::Text::Short:
    473        MOZ_TRY(PushString(aSkeleton, u"B"));
    474        break;
    475      case DateTimeFormat::Text::Long:
    476        MOZ_TRY(PushString(aSkeleton, u"BBBB"));
    477        break;
    478    }
    479  }
    480  if (aBag.minute) {
    481    switch (*aBag.minute) {
    482      case DateTimeFormat::Numeric::TwoDigit:
    483        MOZ_TRY(PushString(aSkeleton, u"mm"));
    484        break;
    485      case DateTimeFormat::Numeric::Numeric:
    486        MOZ_TRY(PushString(aSkeleton, u"m"));
    487        break;
    488    }
    489  }
    490  if (aBag.second) {
    491    switch (*aBag.second) {
    492      case DateTimeFormat::Numeric::TwoDigit:
    493        MOZ_TRY(PushString(aSkeleton, u"ss"));
    494        break;
    495      case DateTimeFormat::Numeric::Numeric:
    496        MOZ_TRY(PushString(aSkeleton, u"s"));
    497        break;
    498    }
    499  }
    500  if (aBag.fractionalSecondDigits) {
    501    switch (*aBag.fractionalSecondDigits) {
    502      case 1:
    503        MOZ_TRY(PushString(aSkeleton, u"S"));
    504        break;
    505      case 2:
    506        MOZ_TRY(PushString(aSkeleton, u"SS"));
    507        break;
    508      default:
    509        MOZ_TRY(PushString(aSkeleton, u"SSS"));
    510        break;
    511    }
    512  }
    513  if (aBag.timeZoneName) {
    514    switch (*aBag.timeZoneName) {
    515      case DateTimeFormat::TimeZoneName::Short:
    516        MOZ_TRY(PushString(aSkeleton, u"z"));
    517        break;
    518      case DateTimeFormat::TimeZoneName::Long:
    519        MOZ_TRY(PushString(aSkeleton, u"zzzz"));
    520        break;
    521      case DateTimeFormat::TimeZoneName::ShortOffset:
    522        MOZ_TRY(PushString(aSkeleton, u"O"));
    523        break;
    524      case DateTimeFormat::TimeZoneName::LongOffset:
    525        MOZ_TRY(PushString(aSkeleton, u"OOOO"));
    526        break;
    527      case DateTimeFormat::TimeZoneName::ShortGeneric:
    528        MOZ_TRY(PushString(aSkeleton, u"v"));
    529        break;
    530      case DateTimeFormat::TimeZoneName::LongGeneric:
    531        MOZ_TRY(PushString(aSkeleton, u"vvvv"));
    532        break;
    533    }
    534  }
    535  return Ok();
    536 }
    537 
    538 /* static */
    539 Result<UniquePtr<DateTimeFormat>, ICUError>
    540 DateTimeFormat::TryCreateFromComponents(
    541    Span<const char> aLocale, const DateTimeFormat::ComponentsBag& aBag,
    542    DateTimePatternGenerator* aDateTimePatternGenerator,
    543    Maybe<Span<const char16_t>> aTimeZoneOverride) {
    544  DateTimeFormat::SkeletonVector skeleton;
    545  MOZ_TRY(ToICUSkeleton(aBag, skeleton));
    546  return TryCreateFromSkeleton(aLocale, skeleton, aDateTimePatternGenerator,
    547                               aBag.hourCycle, aTimeZoneOverride);
    548 }
    549 
    550 /* static */
    551 Result<UniquePtr<DateTimeFormat>, ICUError>
    552 DateTimeFormat::TryCreateFromPattern(
    553    Span<const char> aLocale, Span<const char16_t> aPattern,
    554    Maybe<Span<const char16_t>> aTimeZoneOverride) {
    555  UErrorCode status = U_ZERO_ERROR;
    556 
    557  // The time zone is optional.
    558  int32_t tzIDLength = -1;
    559  const UChar* tzID = nullptr;
    560  if (aTimeZoneOverride) {
    561    tzIDLength = static_cast<int32_t>(aTimeZoneOverride->size());
    562    tzID = aTimeZoneOverride->data();
    563  }
    564 
    565  // Create the date formatter.
    566  UDateFormat* dateFormat = udat_open(
    567      UDAT_PATTERN, UDAT_PATTERN, IcuLocale(aLocale), tzID, tzIDLength,
    568      aPattern.data(), static_cast<int32_t>(aPattern.size()), &status);
    569  if (U_FAILURE(status)) {
    570    return Err(ToICUError(status));
    571  }
    572 
    573  MOZ_TRY(ApplyCalendarOverride(dateFormat));
    574 
    575  // The DateTimeFormat wrapper will control the life cycle of the ICU
    576  // dateFormat object.
    577  return UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
    578 }
    579 
    580 /* static */
    581 Result<UniquePtr<DateTimeFormat>, ICUError>
    582 DateTimeFormat::TryCreateFromSkeleton(
    583    Span<const char> aLocale, Span<const char16_t> aSkeleton,
    584    DateTimePatternGenerator* aDateTimePatternGenerator,
    585    Maybe<DateTimeFormat::HourCycle> aHourCycle,
    586    Maybe<Span<const char16_t>> aTimeZoneOverride) {
    587  if (!aDateTimePatternGenerator) {
    588    return Err(ICUError::InternalError);
    589  }
    590 
    591  // Compute the best pattern for the skeleton.
    592  DateTimeFormat::PatternVector pattern;
    593  auto options = PatternMatchOptions(aSkeleton);
    594  MOZ_TRY(
    595      aDateTimePatternGenerator->GetBestPattern(aSkeleton, pattern, options));
    596 
    597  if (aHourCycle) {
    598    DateTimeFormat::ReplaceHourSymbol(pattern, *aHourCycle);
    599  }
    600 
    601  auto result =
    602      DateTimeFormat::TryCreateFromPattern(aLocale, pattern, aTimeZoneOverride);
    603  if (result.isErr()) {
    604    return Err(result.unwrapErr());
    605  }
    606  auto dateTimeFormat = result.unwrap();
    607  MOZ_TRY(dateTimeFormat->CacheSkeleton(aSkeleton));
    608  return dateTimeFormat;
    609 }
    610 
    611 ICUResult DateTimeFormat::CacheSkeleton(Span<const char16_t> aSkeleton) {
    612  if (mOriginalSkeleton.append(aSkeleton.Elements(), aSkeleton.Length())) {
    613    return Ok();
    614  }
    615  return Err(ICUError::OutOfMemory);
    616 }
    617 
    618 /* static */
    619 Result<UniquePtr<Calendar>, ICUError> DateTimeFormat::CloneCalendar(
    620    double aUnixEpoch) const {
    621  UErrorCode status = U_ZERO_ERROR;
    622  UCalendar* calendarRaw = ucal_clone(udat_getCalendar(mDateFormat), &status);
    623  if (U_FAILURE(status)) {
    624    return Err(ToICUError(status));
    625  }
    626  auto calendar = MakeUnique<Calendar>(calendarRaw);
    627 
    628  MOZ_TRY(calendar->SetTimeInMs(aUnixEpoch));
    629 
    630  return calendar;
    631 }
    632 
    633 /**
    634 * ICU locale identifier consisting of a language and a region subtag.
    635 */
    636 class LanguageRegionLocaleId {
    637  // unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
    638  static constexpr size_t LanguageLength = 8;
    639 
    640  // unicode_region_subtag = (alpha{2} | digit{3}) ;
    641  static constexpr size_t RegionLength = 3;
    642 
    643  // Add +1 to account for the separator.
    644  static constexpr size_t LRLength = LanguageLength + RegionLength + 1;
    645 
    646  // Add +1 to zero terminate the string.
    647  char mLocale[LRLength + 1] = {};
    648 
    649  // Pointer to the start of the region subtag within |locale_|.
    650  char* mRegion = nullptr;
    651 
    652 public:
    653  LanguageRegionLocaleId(Span<const char> aLanguage,
    654                         Maybe<Span<const char>> aRegion);
    655 
    656  const char* languageRegion() const { return mLocale; }
    657  const char* region() const { return mRegion; }
    658 };
    659 
    660 LanguageRegionLocaleId::LanguageRegionLocaleId(
    661    Span<const char> aLanguage, Maybe<Span<const char>> aRegion) {
    662  MOZ_RELEASE_ASSERT(aLanguage.Length() <= LanguageLength);
    663  MOZ_RELEASE_ASSERT(!aRegion || aRegion->Length() <= RegionLength);
    664 
    665  size_t languageLength = aLanguage.Length();
    666 
    667  std::memcpy(mLocale, aLanguage.Elements(), languageLength);
    668 
    669  // ICU locale identifiers are separated by underscores.
    670  mLocale[languageLength] = '_';
    671 
    672  mRegion = mLocale + languageLength + 1;
    673  if (aRegion) {
    674    std::memcpy(mRegion, aRegion->Elements(), aRegion->Length());
    675  } else {
    676    // Use "001" (UN M.49 code for the World) as the fallback to match ICU.
    677    std::strcpy(mRegion, "001");
    678  }
    679 }
    680 
    681 /* static */
    682 Result<DateTimeFormat::HourCyclesVector, ICUError>
    683 DateTimeFormat::GetAllowedHourCycles(Span<const char> aLanguage,
    684                                     Maybe<Span<const char>> aRegion) {
    685  // ICU doesn't expose a public API to retrieve the hour cyles for a locale, so
    686  // we have to reconstruct |DateTimePatternGenerator::getAllowedHourFormats()|
    687  // using the public UResourceBundle API.
    688  //
    689  // The time data format is specified in UTS 35 at [1] and the data itself is
    690  // located at [2].
    691  //
    692  // [1] https://unicode.org/reports/tr35/tr35-dates.html#Time_Data
    693  // [2]
    694  // https://github.com/unicode-org/cldr/blob/master/common/supplemental/supplementalData.xml
    695 
    696  HourCyclesVector result;
    697 
    698  // Reserve space for the maximum number of hour cycles. This call always
    699  // succeeds because it matches the inline capacity. We can now infallibly
    700  // append all hour cycles to the vector.
    701  MOZ_ALWAYS_TRUE(result.reserve(HourCyclesVector::InlineLength));
    702 
    703  LanguageRegionLocaleId localeId(aLanguage, aRegion);
    704 
    705  // First open the "supplementalData" resource bundle.
    706  UErrorCode status = U_ZERO_ERROR;
    707  UResourceBundle* res = ures_openDirect(nullptr, "supplementalData", &status);
    708  if (U_FAILURE(status)) {
    709    return Err(ToICUError(status));
    710  }
    711  ScopedICUObject<UResourceBundle, ures_close> closeRes(res);
    712  MOZ_ASSERT(ures_getType(res) == URES_TABLE);
    713 
    714  // Locate "timeDate" within the "supplementalData" resource bundle.
    715  UResourceBundle* timeData = ures_getByKey(res, "timeData", nullptr, &status);
    716  if (U_FAILURE(status)) {
    717    return Err(ToICUError(status));
    718  }
    719  ScopedICUObject<UResourceBundle, ures_close> closeTimeData(timeData);
    720  MOZ_ASSERT(ures_getType(timeData) == URES_TABLE);
    721 
    722  // Try to find a matching resource within "timeData". The two possible keys
    723  // into the "timeData" resource bundle are `language_region` and `region`.
    724  // Prefer `language_region` and otherwise fallback to `region`.
    725  UResourceBundle* hclocale =
    726      ures_getByKey(timeData, localeId.languageRegion(), nullptr, &status);
    727  if (status == U_MISSING_RESOURCE_ERROR) {
    728    status = U_ZERO_ERROR;
    729    hclocale = ures_getByKey(timeData, localeId.region(), nullptr, &status);
    730  }
    731  if (status == U_MISSING_RESOURCE_ERROR) {
    732    // Default to "h23" if no resource was found at all. This matches ICU.
    733    result.infallibleAppend(HourCycle::H23);
    734    return result;
    735  }
    736  if (U_FAILURE(status)) {
    737    return Err(ToICUError(status));
    738  }
    739  ScopedICUObject<UResourceBundle, ures_close> closeHcLocale(hclocale);
    740  MOZ_ASSERT(ures_getType(hclocale) == URES_TABLE);
    741 
    742  EnumSet<HourCycle> added{};
    743 
    744  auto addToResult = [&](const UChar* str, int32_t len) {
    745    // An hour cycle strings is one of "K", "h", "H", or "k"; optionally
    746    // followed by the suffix "b" or "B". We ignore the suffix because day
    747    // periods can't be expressed in the "hc" Unicode extension.
    748    MOZ_ASSERT(len == 1 || len == 2);
    749 
    750    // Default to "h23" for unsupported hour cycle strings.
    751    HourCycle hc = HourCycle::H23;
    752    switch (str[0]) {
    753      case 'K':
    754        hc = HourCycle::H11;
    755        break;
    756      case 'h':
    757        hc = HourCycle::H12;
    758        break;
    759      case 'H':
    760        hc = HourCycle::H23;
    761        break;
    762      case 'k':
    763        hc = HourCycle::H24;
    764        break;
    765    }
    766 
    767    // Add each unique hour cycle to the result array.
    768    if (!added.contains(hc)) {
    769      added += hc;
    770 
    771      result.infallibleAppend(hc);
    772    }
    773  };
    774 
    775  // Determine the preferred hour cycle for the locale.
    776  int32_t len = 0;
    777  const UChar* hc = ures_getStringByKey(hclocale, "preferred", &len, &status);
    778  if (U_FAILURE(status)) {
    779    return Err(ToICUError(status));
    780  }
    781  addToResult(hc, len);
    782 
    783  // Find any additionally allowed hour cycles of the locale.
    784  UResourceBundle* allowed =
    785      ures_getByKey(hclocale, "allowed", nullptr, &status);
    786  if (U_FAILURE(status)) {
    787    return Err(ToICUError(status));
    788  }
    789  ScopedICUObject<UResourceBundle, ures_close> closeAllowed(allowed);
    790  MOZ_ASSERT(ures_getType(allowed) == URES_ARRAY ||
    791             ures_getType(allowed) == URES_STRING);
    792 
    793  while (ures_hasNext(allowed)) {
    794    int32_t len = 0;
    795    const UChar* hc = ures_getNextString(allowed, &len, nullptr, &status);
    796    if (U_FAILURE(status)) {
    797      return Err(ToICUError(status));
    798    }
    799    addToResult(hc, len);
    800  }
    801 
    802  return result;
    803 }
    804 
    805 template <typename CharT>
    806 static Result<Buffer<char>, ICUError> DuplicateChars(Span<CharT> aView) {
    807  auto chars = MakeUnique<char[]>(aView.Length() + 1);
    808  std::copy_n(aView.Elements(), aView.Length(), chars.get());
    809  chars[aView.Length()] = '\0';
    810  return Buffer{std::move(chars), aView.Length()};
    811 }
    812 
    813 static Result<Buffer<char>, ICUError> GetParentLocale(
    814    const UResourceBundle* aLocaleBundle) {
    815  UErrorCode status = U_ZERO_ERROR;
    816 
    817  // First check for an explicit parent locale using the "%%Parent" key.
    818  int32_t length = 0;
    819  const char16_t* parent =
    820      ures_getStringByKey(aLocaleBundle, "%%Parent", &length, &status);
    821  if (status == U_MISSING_RESOURCE_ERROR) {
    822    status = U_ZERO_ERROR;
    823    parent = nullptr;
    824  }
    825  if (U_FAILURE(status)) {
    826    return Err(ToICUError(status));
    827  }
    828  if (parent) {
    829    return DuplicateChars(Span{parent, size_t(length)});
    830  }
    831 
    832  // Retrieve the actual locale of the resource bundle.
    833  const char* locale =
    834      ures_getLocaleByType(aLocaleBundle, ULOC_ACTUAL_LOCALE, &status);
    835  if (U_FAILURE(status)) {
    836    return Err(ToICUError(status));
    837  }
    838 
    839  // Strip off the last subtag, if possible.
    840  if (const char* sep = std::strrchr(locale, '_')) {
    841    return DuplicateChars(Span{locale, size_t(sep - locale)});
    842  }
    843 
    844  // The parent locale of all locales is "root".
    845  if (std::strcmp(locale, "root") != 0) {
    846    static constexpr auto root = MakeStringSpan("root");
    847    return DuplicateChars(root);
    848  }
    849 
    850  // "root" itself doesn't have a parent locale.
    851  static constexpr auto empty = MakeStringSpan("");
    852  return DuplicateChars(empty);
    853 }
    854 
    855 static Result<Span<const char16_t>, ICUError> FindTimeSeparator(
    856    Span<const char> aRequestedLocale, Span<const char> aLocale,
    857    Span<const char> aNumberingSystem) {
    858  // We didn't find the numbering system. Retry using the default numbering
    859  // system "latn". (We don't use the default numbering system of the requested
    860  // locale to match ICU.)
    861  if (aLocale == MakeStringSpan("")) {
    862    return FindTimeSeparator(aRequestedLocale, aRequestedLocale, "latn");
    863  }
    864 
    865  // First open the resource bundle of the input locale.
    866  //
    867  // Note: ICU's resource API accepts both Unicode CLDR locale identifiers and
    868  // Unicode BCP 47 locale identifiers, so we don't have to convert the input
    869  // into a Unicode CLDR locale identifier.
    870  UErrorCode status = U_ZERO_ERROR;
    871  UResourceBundle* localeBundle =
    872      ures_open(nullptr, AssertNullTerminatedString(aLocale), &status);
    873  if (U_FAILURE(status)) {
    874    return Err(ToICUError(status));
    875  }
    876  ScopedICUObject<UResourceBundle, ures_close> closeLocaleBundle(localeBundle);
    877 
    878  do {
    879    // Search for the "NumberElements" table. Fall back to the parent locale if
    880    // no "NumberElements" table is present.
    881    UResourceBundle* numberElements =
    882        ures_getByKey(localeBundle, "NumberElements", nullptr, &status);
    883    if (status == U_MISSING_RESOURCE_ERROR) {
    884      break;
    885    }
    886    if (U_FAILURE(status)) {
    887      return Err(ToICUError(status));
    888    }
    889    ScopedICUObject<UResourceBundle, ures_close> closeNumberElements(
    890        numberElements);
    891 
    892    // Search for the table of the requested numbering system. Fall back to the
    893    // parent locale if no table was found.
    894    UResourceBundle* numberingSystem = ures_getByKey(
    895        numberElements, AssertNullTerminatedString(aNumberingSystem), nullptr,
    896        &status);
    897    if (status == U_MISSING_RESOURCE_ERROR) {
    898      break;
    899    }
    900    if (U_FAILURE(status)) {
    901      return Err(ToICUError(status));
    902    }
    903    ScopedICUObject<UResourceBundle, ures_close> closeNumberingSystem(
    904        numberingSystem);
    905 
    906    // Search for the "symbols" table. Fall back to the parent locale if no
    907    // "symbols" table is present.
    908    UResourceBundle* symbols =
    909        ures_getByKey(numberingSystem, "symbols", nullptr, &status);
    910    if (status == U_MISSING_RESOURCE_ERROR) {
    911      break;
    912    }
    913    if (U_FAILURE(status)) {
    914      return Err(ToICUError(status));
    915    }
    916    ScopedICUObject<UResourceBundle, ures_close> closeSymbols(symbols);
    917 
    918    // And finally look up the "timeSeparator" string in the "symbols" table. If
    919    // the string isn't present, fall back to the parent locale.
    920    int32_t length = 0;
    921    const UChar* str =
    922        ures_getStringByKey(symbols, "timeSeparator", &length, &status);
    923    if (status == U_MISSING_RESOURCE_ERROR) {
    924      break;
    925    }
    926    if (U_FAILURE(status)) {
    927      return Err(ToICUError(status));
    928    }
    929 
    930    Span<const char16_t> timeSeparator{str, size_t(length)};
    931 
    932    static constexpr auto defaultTimeSeparator = MakeStringSpan(u":");
    933 
    934    // Many numbering systems don't define their own symbols, but instead link
    935    // to the symbols for "latn" of the requested locale. The link is performed
    936    // through an alias entry like:
    937    // `symbols:alias{"/LOCALE/NumberElements/latn/symbols"}`
    938    //
    939    // ICU doesn't provide a public API to detect these alias entries, but
    940    // instead always automatically resolves the link. But that leads to
    941    // incorrectly using the symbols from the "root" locale instead of the
    942    // requested locale.
    943    //
    944    // Thankfully these alias entries are only present on the "root" locale. So
    945    // we are using this heuristic to detect alias entries:
    946    //
    947    // - If the resolved time separator is the default time separator ":".
    948    // - The current locale is "root".
    949    // - And the numbering system is neither "latn" nor "arab".
    950    // - Then search the time separator for "latn" of the requested locale.
    951    //
    952    // We have to exclude "arab", because it's also using ":" for the time
    953    // separator, but doesn't use an alias link to "latn".
    954    if (timeSeparator == defaultTimeSeparator &&
    955        aLocale == MakeStringSpan("root") &&
    956        aNumberingSystem != MakeStringSpan("latn") &&
    957        aNumberingSystem != MakeStringSpan("arab")) {
    958      return FindTimeSeparator(aRequestedLocale, aRequestedLocale,
    959                               MakeStringSpan("latn"));
    960    }
    961 
    962    return timeSeparator;
    963  } while (false);
    964 
    965  // Fall back to the parent locale.
    966  auto parent = GetParentLocale(localeBundle);
    967  if (parent.isErr()) {
    968    return parent.propagateErr();
    969  }
    970  return FindTimeSeparator(aRequestedLocale, parent.inspect().AsSpan(),
    971                           aNumberingSystem);
    972 }
    973 
    974 /* static */
    975 Result<Span<const char16_t>, ICUError> DateTimeFormat::GetTimeSeparator(
    976    Span<const char> aLocale, Span<const char> aNumberingSystem) {
    977  return FindTimeSeparator(aLocale, aLocale, aNumberingSystem);
    978 }
    979 
    980 Result<DateTimeFormat::ComponentsBag, ICUError>
    981 DateTimeFormat::ResolveComponents() {
    982  // Maps an ICU pattern string to a corresponding set of date-time components
    983  // and their values, and adds properties for these components to the result
    984  // object, which will be returned by the resolvedOptions method. For the
    985  // interpretation of ICU pattern characters, see
    986  // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
    987 
    988  DateTimeFormat::PatternVector pattern{};
    989  VectorToBufferAdaptor buffer(pattern);
    990  MOZ_TRY(GetPattern(buffer));
    991 
    992  DateTimeFormat::ComponentsBag bag{};
    993 
    994  using Text = DateTimeFormat::Text;
    995  using HourCycle = DateTimeFormat::HourCycle;
    996  using Numeric = DateTimeFormat::Numeric;
    997  using Month = DateTimeFormat::Month;
    998 
    999  auto text = Text::Long;
   1000  auto numeric = Numeric::Numeric;
   1001  auto month = Month::Long;
   1002  uint8_t fractionalSecondDigits = 0;
   1003 
   1004  for (size_t i = 0, len = pattern.length(); i < len;) {
   1005    char16_t c = pattern[i++];
   1006    if (c == u'\'') {
   1007      // Skip past string literals.
   1008      while (i < len && pattern[i] != u'\'') {
   1009        i++;
   1010      }
   1011      i++;
   1012      continue;
   1013    }
   1014 
   1015    // Count how many times the character is repeated.
   1016    size_t count = 1;
   1017    while (i < len && pattern[i] == c) {
   1018      i++;
   1019      count++;
   1020    }
   1021 
   1022    // Determine the enum case of the field.
   1023    switch (c) {
   1024      // "text" cases
   1025      case u'G':
   1026      case u'E':
   1027      case u'c':
   1028      case u'B':
   1029      case u'z':
   1030      case u'O':
   1031      case u'v':
   1032      case u'V':
   1033        if (count <= 3) {
   1034          text = Text::Short;
   1035        } else if (count == 4) {
   1036          text = Text::Long;
   1037        } else {
   1038          text = Text::Narrow;
   1039        }
   1040        break;
   1041      // "number" cases
   1042      case u'y':
   1043      case u'd':
   1044      case u'h':
   1045      case u'H':
   1046      case u'm':
   1047      case u's':
   1048      case u'k':
   1049      case u'K':
   1050        if (count == 2) {
   1051          numeric = Numeric::TwoDigit;
   1052        } else {
   1053          numeric = Numeric::Numeric;
   1054        }
   1055        break;
   1056      // "numeric" cases
   1057      case u'r':
   1058      case u'U':
   1059        // Both are mapped to numeric years.
   1060        numeric = Numeric::Numeric;
   1061        break;
   1062      // "text & number" cases
   1063      case u'M':
   1064      case u'L':
   1065        if (count == 1) {
   1066          month = Month::Numeric;
   1067        } else if (count == 2) {
   1068          month = Month::TwoDigit;
   1069        } else if (count == 3) {
   1070          month = Month::Short;
   1071        } else if (count == 4) {
   1072          month = Month::Long;
   1073        } else {
   1074          month = Month::Narrow;
   1075        }
   1076        break;
   1077      case u'S':
   1078        fractionalSecondDigits = count;
   1079        break;
   1080      default: {
   1081        // skip other pattern characters and literal text
   1082      }
   1083    }
   1084 
   1085    // Map ICU pattern characters back to the corresponding date-time
   1086    // components of DateTimeFormat. See
   1087    // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
   1088    switch (c) {
   1089      case u'E':
   1090      case u'c':
   1091        bag.weekday = Some(text);
   1092        break;
   1093      case u'G':
   1094        bag.era = Some(text);
   1095        break;
   1096      case u'y':
   1097      case u'r':
   1098      case u'U':
   1099        bag.year = Some(numeric);
   1100        break;
   1101      case u'M':
   1102      case u'L':
   1103        bag.month = Some(month);
   1104        break;
   1105      case u'd':
   1106        bag.day = Some(numeric);
   1107        break;
   1108      case u'B':
   1109        bag.dayPeriod = Some(text);
   1110        break;
   1111      case u'K':
   1112        bag.hourCycle = Some(HourCycle::H11);
   1113        bag.hour = Some(numeric);
   1114        bag.hour12 = Some(true);
   1115        break;
   1116      case u'h':
   1117        bag.hourCycle = Some(HourCycle::H12);
   1118        bag.hour = Some(numeric);
   1119        bag.hour12 = Some(true);
   1120        break;
   1121      case u'H':
   1122        bag.hourCycle = Some(HourCycle::H23);
   1123        bag.hour = Some(numeric);
   1124        bag.hour12 = Some(false);
   1125        break;
   1126      case u'k':
   1127        bag.hourCycle = Some(HourCycle::H24);
   1128        bag.hour = Some(numeric);
   1129        bag.hour12 = Some(false);
   1130        break;
   1131      case u'm':
   1132        bag.minute = Some(numeric);
   1133        break;
   1134      case u's':
   1135        bag.second = Some(numeric);
   1136        break;
   1137      case u'S':
   1138        bag.fractionalSecondDigits = Some(fractionalSecondDigits);
   1139        break;
   1140      case u'z':
   1141        switch (text) {
   1142          case Text::Long:
   1143            bag.timeZoneName = Some(TimeZoneName::Long);
   1144            break;
   1145          case Text::Short:
   1146          case Text::Narrow:
   1147            bag.timeZoneName = Some(TimeZoneName::Short);
   1148            break;
   1149        }
   1150        break;
   1151      case u'O':
   1152        switch (text) {
   1153          case Text::Long:
   1154            bag.timeZoneName = Some(TimeZoneName::LongOffset);
   1155            break;
   1156          case Text::Short:
   1157          case Text::Narrow:
   1158            bag.timeZoneName = Some(TimeZoneName::ShortOffset);
   1159            break;
   1160        }
   1161        break;
   1162      case u'v':
   1163      case u'V':
   1164        switch (text) {
   1165          case Text::Long:
   1166            bag.timeZoneName = Some(TimeZoneName::LongGeneric);
   1167            break;
   1168          case Text::Short:
   1169          case Text::Narrow:
   1170            bag.timeZoneName = Some(TimeZoneName::ShortGeneric);
   1171            break;
   1172        }
   1173        break;
   1174    }
   1175  }
   1176  return bag;
   1177 }
   1178 
   1179 const char* DateTimeFormat::ToString(
   1180    DateTimeFormat::TimeZoneName aTimeZoneName) {
   1181  switch (aTimeZoneName) {
   1182    case TimeZoneName::Long:
   1183      return "long";
   1184    case TimeZoneName::Short:
   1185      return "short";
   1186    case TimeZoneName::ShortOffset:
   1187      return "shortOffset";
   1188    case TimeZoneName::LongOffset:
   1189      return "longOffset";
   1190    case TimeZoneName::ShortGeneric:
   1191      return "shortGeneric";
   1192    case TimeZoneName::LongGeneric:
   1193      return "longGeneric";
   1194  }
   1195  MOZ_CRASH("Unexpected DateTimeFormat::TimeZoneName");
   1196 }
   1197 
   1198 const char* DateTimeFormat::ToString(DateTimeFormat::Month aMonth) {
   1199  switch (aMonth) {
   1200    case Month::Numeric:
   1201      return "numeric";
   1202    case Month::TwoDigit:
   1203      return "2-digit";
   1204    case Month::Long:
   1205      return "long";
   1206    case Month::Short:
   1207      return "short";
   1208    case Month::Narrow:
   1209      return "narrow";
   1210  }
   1211  MOZ_CRASH("Unexpected DateTimeFormat::Month");
   1212 }
   1213 
   1214 const char* DateTimeFormat::ToString(DateTimeFormat::Text aText) {
   1215  switch (aText) {
   1216    case Text::Long:
   1217      return "long";
   1218    case Text::Short:
   1219      return "short";
   1220    case Text::Narrow:
   1221      return "narrow";
   1222  }
   1223  MOZ_CRASH("Unexpected DateTimeFormat::Text");
   1224 }
   1225 
   1226 const char* DateTimeFormat::ToString(DateTimeFormat::Numeric aNumeric) {
   1227  switch (aNumeric) {
   1228    case Numeric::Numeric:
   1229      return "numeric";
   1230    case Numeric::TwoDigit:
   1231      return "2-digit";
   1232  }
   1233  MOZ_CRASH("Unexpected DateTimeFormat::Numeric");
   1234 }
   1235 
   1236 const char* DateTimeFormat::ToString(DateTimeFormat::Style aStyle) {
   1237  switch (aStyle) {
   1238    case Style::Full:
   1239      return "full";
   1240    case Style::Long:
   1241      return "long";
   1242    case Style::Medium:
   1243      return "medium";
   1244    case Style::Short:
   1245      return "short";
   1246  }
   1247  MOZ_CRASH("Unexpected DateTimeFormat::Style");
   1248 }
   1249 
   1250 const char* DateTimeFormat::ToString(DateTimeFormat::HourCycle aHourCycle) {
   1251  switch (aHourCycle) {
   1252    case HourCycle::H11:
   1253      return "h11";
   1254    case HourCycle::H12:
   1255      return "h12";
   1256    case HourCycle::H23:
   1257      return "h23";
   1258    case HourCycle::H24:
   1259      return "h24";
   1260  }
   1261  MOZ_CRASH("Unexpected DateTimeFormat::HourCycle");
   1262 }
   1263 
   1264 ICUResult DateTimeFormat::TryFormatToParts(
   1265    UFieldPositionIterator* aFieldPositionIterator, size_t aSpanSize,
   1266    DateTimePartVector& aParts) const {
   1267  ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(
   1268      aFieldPositionIterator);
   1269 
   1270  size_t lastEndIndex = 0;
   1271  auto AppendPart = [&](DateTimePartType type, size_t endIndex) {
   1272    // For the part defined in FormatDateTimeToParts, it doesn't have ||Source||
   1273    // property, we store Shared for simplicity,
   1274    if (!aParts.emplaceBack(type, endIndex, DateTimePartSource::Shared)) {
   1275      return false;
   1276    }
   1277 
   1278    lastEndIndex = endIndex;
   1279    return true;
   1280  };
   1281 
   1282  int32_t fieldInt, beginIndexInt, endIndexInt;
   1283  while ((fieldInt = ufieldpositer_next(aFieldPositionIterator, &beginIndexInt,
   1284                                        &endIndexInt)) >= 0) {
   1285    MOZ_ASSERT(beginIndexInt <= endIndexInt,
   1286               "field iterator returning invalid range");
   1287 
   1288    size_t beginIndex = AssertedCast<size_t>(beginIndexInt);
   1289    size_t endIndex = AssertedCast<size_t>(endIndexInt);
   1290 
   1291    // Technically this isn't guaranteed.  But it appears true in pratice,
   1292    // and http://bugs.icu-project.org/trac/ticket/12024 is expected to
   1293    // correct the documentation lapse.
   1294    MOZ_ASSERT(lastEndIndex <= beginIndex,
   1295               "field iteration didn't return fields in order start to "
   1296               "finish as expected");
   1297 
   1298    DateTimePartType type =
   1299        ConvertUFormatFieldToPartType(static_cast<UDateFormatField>(fieldInt));
   1300    if (lastEndIndex < beginIndex) {
   1301      if (!AppendPart(DateTimePartType::Literal, beginIndex)) {
   1302        return Err(ICUError::InternalError);
   1303      }
   1304    }
   1305 
   1306    if (!AppendPart(type, endIndex)) {
   1307      return Err(ICUError::InternalError);
   1308    }
   1309  }
   1310 
   1311  // Append any final literal.
   1312  if (lastEndIndex < aSpanSize) {
   1313    if (!AppendPart(DateTimePartType::Literal, aSpanSize)) {
   1314      return Err(ICUError::InternalError);
   1315    }
   1316  }
   1317 
   1318  return Ok();
   1319 }
   1320 
   1321 }  // namespace mozilla::intl