tor-browser

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

TestDateTimeFormat.cpp (23397B)


      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 #include "gtest/gtest.h"
      5 
      6 #include "mozilla/intl/Calendar.h"
      7 #include "mozilla/intl/DateTimeFormat.h"
      8 #include "mozilla/intl/DateTimePart.h"
      9 #include "mozilla/intl/DateTimePatternGenerator.h"
     10 #include "mozilla/Span.h"
     11 #include "TestBuffer.h"
     12 
     13 #include <string_view>
     14 
     15 namespace mozilla::intl {
     16 
     17 // Firefox 1.0 release date.
     18 const double DATE = 1032800850000.0;
     19 
     20 static UniquePtr<DateTimeFormat> testStyle(
     21    const char* aLocale, DateTimeFormat::StyleBag& aStyleBag) {
     22  // Always specify a time zone in the tests, otherwise it will use the system
     23  // time zone which can vary between test runs.
     24  auto timeZone = Some(MakeStringSpan(u"GMT+3"));
     25  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
     26  return DateTimeFormat::TryCreateFromStyle(MakeStringSpan(aLocale), aStyleBag,
     27                                            gen.get(), timeZone)
     28      .unwrap();
     29 }
     30 
     31 TEST(IntlDateTimeFormat, Style_enUS_utf8)
     32 {
     33  DateTimeFormat::StyleBag style;
     34  style.date = Some(DateTimeFormat::Style::Medium);
     35  style.time = Some(DateTimeFormat::Style::Medium);
     36 
     37  auto dtFormat = testStyle("en-US", style);
     38  TestBuffer<char> buffer;
     39  dtFormat->TryFormat(DATE, buffer).unwrap();
     40 
     41  ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 8:07:30 PM"));
     42 }
     43 
     44 TEST(IntlDateTimeFormat, Style_enUS_utf16)
     45 {
     46  DateTimeFormat::StyleBag style;
     47  style.date = Some(DateTimeFormat::Style::Medium);
     48  style.time = Some(DateTimeFormat::Style::Medium);
     49 
     50  auto dtFormat = testStyle("en-US", style);
     51  TestBuffer<char16_t> buffer;
     52  dtFormat->TryFormat(DATE, buffer).unwrap();
     53 
     54  ASSERT_TRUE(buffer.verboseMatches(u"Sep 23, 2002, 8:07:30 PM"));
     55 }
     56 
     57 TEST(IntlDateTimeFormat, Style_ar_utf8)
     58 {
     59  DateTimeFormat::StyleBag style;
     60  style.time = Some(DateTimeFormat::Style::Medium);
     61 
     62  auto dtFormat = testStyle("ar-EG", style);
     63  TestBuffer<char> buffer;
     64  dtFormat->TryFormat(DATE, buffer).unwrap();
     65 
     66  ASSERT_TRUE(buffer.verboseMatches("٨:٠٧:٣٠ م"));
     67 }
     68 
     69 TEST(IntlDateTimeFormat, Style_ar_utf16)
     70 {
     71  DateTimeFormat::StyleBag style;
     72  style.time = Some(DateTimeFormat::Style::Medium);
     73 
     74  auto dtFormat = testStyle("ar-EG", style);
     75  TestBuffer<char16_t> buffer;
     76  dtFormat->TryFormat(DATE, buffer).unwrap();
     77 
     78  ASSERT_TRUE(buffer.verboseMatches(u"٨:٠٧:٣٠ م"));
     79 }
     80 
     81 TEST(IntlDateTimeFormat, Style_enUS_fallback_to_default_styles)
     82 {
     83  DateTimeFormat::StyleBag style;
     84 
     85  auto dtFormat = testStyle("en-US", style);
     86  TestBuffer<char> buffer;
     87  dtFormat->TryFormat(DATE, buffer).unwrap();
     88 
     89  ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 8:07:30 PM"));
     90 }
     91 
     92 TEST(IntlDateTimeFormat, Time_zone_IANA_identifier)
     93 {
     94  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
     95 
     96  DateTimeFormat::StyleBag style;
     97  style.date = Some(DateTimeFormat::Style::Medium);
     98  style.time = Some(DateTimeFormat::Style::Medium);
     99 
    100  auto dtFormat = DateTimeFormat::TryCreateFromStyle(
    101                      MakeStringSpan("en-US"), style, gen.get(),
    102                      Some(MakeStringSpan(u"America/Chicago")))
    103                      .unwrap();
    104  TestBuffer<char> buffer;
    105  dtFormat->TryFormat(DATE, buffer).unwrap();
    106  ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 12:07:30 PM"));
    107 }
    108 
    109 TEST(IntlDateTimeFormat, GetAllowedHourCycles)
    110 {
    111  auto allowed_en_US = DateTimeFormat::GetAllowedHourCycles(
    112                           MakeStringSpan("en"), Some(MakeStringSpan("US")))
    113                           .unwrap();
    114 
    115  ASSERT_TRUE(allowed_en_US.length() == 2);
    116  ASSERT_EQ(allowed_en_US[0], DateTimeFormat::HourCycle::H12);
    117  ASSERT_EQ(allowed_en_US[1], DateTimeFormat::HourCycle::H23);
    118 
    119  auto allowed_de =
    120      DateTimeFormat::GetAllowedHourCycles(MakeStringSpan("de"), Nothing())
    121          .unwrap();
    122 
    123  ASSERT_TRUE(allowed_de.length() == 2);
    124  ASSERT_EQ(allowed_de[0], DateTimeFormat::HourCycle::H23);
    125  ASSERT_EQ(allowed_de[1], DateTimeFormat::HourCycle::H12);
    126 }
    127 
    128 TEST(IntlDateTimePatternGenerator, GetBestPattern)
    129 {
    130  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
    131  TestBuffer<char16_t> buffer;
    132 
    133  gen->GetBestPattern(MakeStringSpan(u"yMd"), buffer).unwrap();
    134  ASSERT_TRUE(buffer.verboseMatches(u"M/d/y"));
    135 }
    136 
    137 TEST(IntlDateTimePatternGenerator, GetSkeleton)
    138 {
    139  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
    140  TestBuffer<char16_t> buffer;
    141 
    142  DateTimePatternGenerator::GetSkeleton(MakeStringSpan(u"M/d/y"), buffer)
    143      .unwrap();
    144  ASSERT_TRUE(buffer.verboseMatches(u"yMd"));
    145 }
    146 
    147 TEST(IntlDateTimePatternGenerator, GetPlaceholderPattern)
    148 {
    149  auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
    150  auto span = gen->GetPlaceholderPattern();
    151  // The default date-time pattern for 'en' locale is u"{1}, {0}".
    152  ASSERT_EQ(span, MakeStringSpan(u"{1}, {0}"));
    153 }
    154 
    155 // A utility function to help test the DateTimeFormat::ComponentsBag.
    156 [[nodiscard]] bool FormatComponents(
    157    TestBuffer<char16_t>& aBuffer, DateTimeFormat::ComponentsBag& aComponents,
    158    Span<const char> aLocale = MakeStringSpan("en-US")) {
    159  UniquePtr<DateTimePatternGenerator> gen = nullptr;
    160  auto dateTimePatternGenerator =
    161      DateTimePatternGenerator::TryCreate(aLocale.data()).unwrap();
    162 
    163  auto dtFormat = DateTimeFormat::TryCreateFromComponents(
    164      aLocale, aComponents, dateTimePatternGenerator.get(),
    165      Some(MakeStringSpan(u"GMT+3")));
    166  if (dtFormat.isErr()) {
    167    fprintf(stderr, "Could not create a DateTimeFormat\n");
    168    return false;
    169  }
    170 
    171  auto result = dtFormat.unwrap()->TryFormat(DATE, aBuffer);
    172  if (result.isErr()) {
    173    fprintf(stderr, "Could not format a DateTimeFormat\n");
    174    return false;
    175  }
    176 
    177  return true;
    178 }
    179 
    180 TEST(IntlDateTimeFormat, Components)
    181 {
    182  DateTimeFormat::ComponentsBag components{};
    183 
    184  components.year = Some(DateTimeFormat::Numeric::Numeric);
    185  components.month = Some(DateTimeFormat::Month::Numeric);
    186  components.day = Some(DateTimeFormat::Numeric::Numeric);
    187 
    188  components.hour = Some(DateTimeFormat::Numeric::Numeric);
    189  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    190  components.second = Some(DateTimeFormat::Numeric::TwoDigit);
    191 
    192  TestBuffer<char16_t> buffer;
    193  ASSERT_TRUE(FormatComponents(buffer, components));
    194  ASSERT_TRUE(buffer.verboseMatches(u"9/23/2002, 8:07:30 PM"));
    195 }
    196 
    197 TEST(IntlDateTimeFormat, Components_es_ES)
    198 {
    199  DateTimeFormat::ComponentsBag components{};
    200 
    201  components.year = Some(DateTimeFormat::Numeric::Numeric);
    202  components.month = Some(DateTimeFormat::Month::Numeric);
    203  components.day = Some(DateTimeFormat::Numeric::Numeric);
    204 
    205  components.hour = Some(DateTimeFormat::Numeric::Numeric);
    206  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    207  components.second = Some(DateTimeFormat::Numeric::TwoDigit);
    208 
    209  TestBuffer<char16_t> buffer;
    210  ASSERT_TRUE(FormatComponents(buffer, components, MakeStringSpan("es-ES")));
    211  ASSERT_TRUE(buffer.verboseMatches(u"23/9/2002, 20:07:30"));
    212 }
    213 
    214 TEST(IntlDateTimeFormat, ComponentsAll)
    215 {
    216  // Use most all of the components.
    217  DateTimeFormat::ComponentsBag components{};
    218 
    219  components.era = Some(DateTimeFormat::Text::Short);
    220 
    221  components.year = Some(DateTimeFormat::Numeric::Numeric);
    222  components.month = Some(DateTimeFormat::Month::Numeric);
    223  components.day = Some(DateTimeFormat::Numeric::Numeric);
    224 
    225  components.weekday = Some(DateTimeFormat::Text::Short);
    226 
    227  components.hour = Some(DateTimeFormat::Numeric::Numeric);
    228  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    229  components.second = Some(DateTimeFormat::Numeric::TwoDigit);
    230 
    231  components.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
    232  components.hourCycle = Some(DateTimeFormat::HourCycle::H24);
    233  components.fractionalSecondDigits = Some(3);
    234 
    235  TestBuffer<char16_t> buffer;
    236  ASSERT_TRUE(FormatComponents(buffer, components));
    237  ASSERT_TRUE(buffer.verboseMatches(u"Mon, 9/23/2002 AD, 20:07:30.000 GMT+3"));
    238 }
    239 
    240 TEST(IntlDateTimeFormat, ComponentsHour12Default)
    241 {
    242  // Assert the behavior of the default "en-US" 12 hour time with day period.
    243  DateTimeFormat::ComponentsBag components{};
    244  components.hour = Some(DateTimeFormat::Numeric::Numeric);
    245  components.minute = Some(DateTimeFormat::Numeric::Numeric);
    246 
    247  TestBuffer<char16_t> buffer;
    248  ASSERT_TRUE(FormatComponents(buffer, components));
    249  ASSERT_TRUE(buffer.verboseMatches(u"8:07 PM"));
    250 }
    251 
    252 TEST(IntlDateTimeFormat, ComponentsHour24)
    253 {
    254  // Test the behavior of using 24 hour time to override the default of
    255  // hour 12 with a day period.
    256  DateTimeFormat::ComponentsBag components{};
    257  components.hour = Some(DateTimeFormat::Numeric::Numeric);
    258  components.minute = Some(DateTimeFormat::Numeric::Numeric);
    259  components.hour12 = Some(false);
    260 
    261  TestBuffer<char16_t> buffer;
    262  ASSERT_TRUE(FormatComponents(buffer, components));
    263  ASSERT_TRUE(buffer.verboseMatches(u"20:07"));
    264 }
    265 
    266 TEST(IntlDateTimeFormat, ComponentsHour12DayPeriod)
    267 {
    268  // Test the behavior of specifying a specific day period.
    269  DateTimeFormat::ComponentsBag components{};
    270 
    271  components.hour = Some(DateTimeFormat::Numeric::Numeric);
    272  components.minute = Some(DateTimeFormat::Numeric::Numeric);
    273  components.dayPeriod = Some(DateTimeFormat::Text::Long);
    274 
    275  TestBuffer<char16_t> buffer;
    276  ASSERT_TRUE(FormatComponents(buffer, components));
    277  ASSERT_TRUE(buffer.verboseMatches(u"8:07 in the evening"));
    278 }
    279 
    280 const char* ToString(uint8_t b) { return "uint8_t"; }
    281 const char* ToString(bool b) { return b ? "true" : "false"; }
    282 
    283 template <typename T>
    284 const char* ToString(Maybe<T> option) {
    285  if (option) {
    286    if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, uint8_t>) {
    287      return ToString(*option);
    288    } else {
    289      return DateTimeFormat::ToString(*option);
    290    }
    291  }
    292  return "Nothing";
    293 }
    294 
    295 template <typename T>
    296 [[nodiscard]] bool VerboseEquals(T expected, T actual, const char* msg) {
    297  if (expected != actual) {
    298    fprintf(stderr, "%s\n  Actual: %s\nExpected: %s\n", msg, ToString(actual),
    299            ToString(expected));
    300    return false;
    301  }
    302  return true;
    303 }
    304 
    305 // A testing utility for getting nice errors when ComponentsBags don't match.
    306 [[nodiscard]] bool VerboseEquals(DateTimeFormat::ComponentsBag& expected,
    307                                 DateTimeFormat::ComponentsBag& actual) {
    308  // clang-format off
    309  return
    310      VerboseEquals(expected.era, actual.era, "Components do not match: bag.era") &&
    311      VerboseEquals(expected.year, actual.year, "Components do not match: bag.year") &&
    312      VerboseEquals(expected.month, actual.month, "Components do not match: bag.month") &&
    313      VerboseEquals(expected.day, actual.day, "Components do not match: bag.day") &&
    314      VerboseEquals(expected.weekday, actual.weekday, "Components do not match: bag.weekday") &&
    315      VerboseEquals(expected.hour, actual.hour, "Components do not match: bag.hour") &&
    316      VerboseEquals(expected.minute, actual.minute, "Components do not match: bag.minute") &&
    317      VerboseEquals(expected.second, actual.second, "Components do not match: bag.second") &&
    318      VerboseEquals(expected.timeZoneName, actual.timeZoneName, "Components do not match: bag.timeZoneName") &&
    319      VerboseEquals(expected.hour12, actual.hour12, "Components do not match: bag.hour12") &&
    320      VerboseEquals(expected.hourCycle, actual.hourCycle, "Components do not match: bag.hourCycle") &&
    321      VerboseEquals(expected.dayPeriod, actual.dayPeriod, "Components do not match: bag.dayPeriod") &&
    322      VerboseEquals(expected.fractionalSecondDigits, actual.fractionalSecondDigits, "Components do not match: bag.fractionalSecondDigits");
    323  // clang-format on
    324 }
    325 
    326 // A utility function to help test the DateTimeFormat::ComponentsBag.
    327 [[nodiscard]] bool ResolveComponentsBag(
    328    DateTimeFormat::ComponentsBag& aComponentsIn,
    329    DateTimeFormat::ComponentsBag* aComponentsOut,
    330    Span<const char> aLocale = MakeStringSpan("en-US")) {
    331  UniquePtr<DateTimePatternGenerator> gen = nullptr;
    332  auto dateTimePatternGenerator =
    333      DateTimePatternGenerator::TryCreate("en").unwrap();
    334  auto dtFormat = DateTimeFormat::TryCreateFromComponents(
    335      aLocale, aComponentsIn, dateTimePatternGenerator.get(),
    336      Some(MakeStringSpan(u"GMT+3")));
    337  if (dtFormat.isErr()) {
    338    fprintf(stderr, "Could not create a DateTimeFormat\n");
    339    return false;
    340  }
    341 
    342  auto result = dtFormat.unwrap()->ResolveComponents();
    343  if (result.isErr()) {
    344    fprintf(stderr, "Could not resolve the components\n");
    345    return false;
    346  }
    347 
    348  *aComponentsOut = result.unwrap();
    349  return true;
    350 }
    351 
    352 TEST(IntlDateTimeFormat, ResolvedComponentsDate)
    353 {
    354  DateTimeFormat::ComponentsBag input{};
    355  {
    356    input.year = Some(DateTimeFormat::Numeric::Numeric);
    357    input.month = Some(DateTimeFormat::Month::Numeric);
    358    input.day = Some(DateTimeFormat::Numeric::Numeric);
    359  }
    360 
    361  DateTimeFormat::ComponentsBag expected = input;
    362 
    363  DateTimeFormat::ComponentsBag resolved{};
    364  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
    365  ASSERT_TRUE(VerboseEquals(expected, resolved));
    366 }
    367 
    368 TEST(IntlDateTimeFormat, ResolvedComponentsAll)
    369 {
    370  DateTimeFormat::ComponentsBag input{};
    371  {
    372    input.era = Some(DateTimeFormat::Text::Short);
    373 
    374    input.year = Some(DateTimeFormat::Numeric::Numeric);
    375    input.month = Some(DateTimeFormat::Month::Numeric);
    376    input.day = Some(DateTimeFormat::Numeric::Numeric);
    377 
    378    input.weekday = Some(DateTimeFormat::Text::Short);
    379 
    380    input.hour = Some(DateTimeFormat::Numeric::Numeric);
    381    input.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    382    input.second = Some(DateTimeFormat::Numeric::TwoDigit);
    383 
    384    input.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
    385    input.hourCycle = Some(DateTimeFormat::HourCycle::H24);
    386    input.fractionalSecondDigits = Some(3);
    387  }
    388 
    389  DateTimeFormat::ComponentsBag expected = input;
    390  {
    391    expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
    392    expected.hourCycle = Some(DateTimeFormat::HourCycle::H24);
    393    expected.hour12 = Some(false);
    394  }
    395 
    396  DateTimeFormat::ComponentsBag resolved{};
    397  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
    398  ASSERT_TRUE(VerboseEquals(expected, resolved));
    399 }
    400 
    401 TEST(IntlDateTimeFormat, ResolvedComponentsHourDayPeriod)
    402 {
    403  DateTimeFormat::ComponentsBag input{};
    404  {
    405    input.hour = Some(DateTimeFormat::Numeric::Numeric);
    406    input.minute = Some(DateTimeFormat::Numeric::Numeric);
    407  }
    408 
    409  DateTimeFormat::ComponentsBag expected = input;
    410  {
    411    expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    412    expected.hourCycle = Some(DateTimeFormat::HourCycle::H12);
    413    expected.hour12 = Some(true);
    414  }
    415 
    416  DateTimeFormat::ComponentsBag resolved{};
    417  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
    418  ASSERT_TRUE(VerboseEquals(expected, resolved));
    419 }
    420 
    421 TEST(IntlDateTimeFormat, ResolvedComponentsHour12)
    422 {
    423  DateTimeFormat::ComponentsBag input{};
    424  {
    425    input.hour = Some(DateTimeFormat::Numeric::Numeric);
    426    input.minute = Some(DateTimeFormat::Numeric::Numeric);
    427    input.hour12 = Some(false);
    428  }
    429 
    430  DateTimeFormat::ComponentsBag expected = input;
    431  {
    432    expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
    433    expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    434    expected.hourCycle = Some(DateTimeFormat::HourCycle::H23);
    435    expected.hour12 = Some(false);
    436  }
    437 
    438  DateTimeFormat::ComponentsBag resolved{};
    439  ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
    440  ASSERT_TRUE(VerboseEquals(expected, resolved));
    441 }
    442 
    443 TEST(IntlDateTimeFormat, GetOriginalSkeleton)
    444 {
    445  // Demonstrate that the original skeleton and the resolved skeleton can
    446  // differ.
    447  DateTimeFormat::ComponentsBag components{};
    448  components.month = Some(DateTimeFormat::Month::Narrow);
    449  components.day = Some(DateTimeFormat::Numeric::TwoDigit);
    450 
    451  const char* locale = "zh-Hans-CN";
    452  auto dateTimePatternGenerator =
    453      DateTimePatternGenerator::TryCreate(locale).unwrap();
    454 
    455  auto result = DateTimeFormat::TryCreateFromComponents(
    456      MakeStringSpan(locale), components, dateTimePatternGenerator.get(),
    457      Some(MakeStringSpan(u"GMT+3")));
    458  ASSERT_TRUE(result.isOk());
    459  auto dtFormat = result.unwrap();
    460 
    461  TestBuffer<char16_t> originalSkeleton;
    462  auto originalSkeletonResult = dtFormat->GetOriginalSkeleton(originalSkeleton);
    463  ASSERT_TRUE(originalSkeletonResult.isOk());
    464  ASSERT_TRUE(originalSkeleton.verboseMatches(u"MMMMMdd"));
    465 
    466  TestBuffer<char16_t> pattern;
    467  auto patternResult = dtFormat->GetPattern(pattern);
    468  ASSERT_TRUE(patternResult.isOk());
    469  ASSERT_TRUE(pattern.verboseMatches(u"M月dd日"));
    470 
    471  TestBuffer<char16_t> resolvedSkeleton;
    472  auto resolvedSkeletonResult = DateTimePatternGenerator::GetSkeleton(
    473      Span(pattern.data(), pattern.length()), resolvedSkeleton);
    474 
    475  ASSERT_TRUE(resolvedSkeletonResult.isOk());
    476  ASSERT_TRUE(resolvedSkeleton.verboseMatches(u"Mdd"));
    477 }
    478 
    479 TEST(IntlDateTimeFormat, GetAvailableLocales)
    480 {
    481  using namespace std::literals;
    482 
    483  int32_t english = 0;
    484  int32_t german = 0;
    485  int32_t chinese = 0;
    486 
    487  // Since this list is dependent on ICU, and may change between upgrades, only
    488  // test a subset of the available locales.
    489  for (const char* locale : DateTimeFormat::GetAvailableLocales()) {
    490    if (locale == "en"sv) {
    491      english++;
    492    } else if (locale == "de"sv) {
    493      german++;
    494    } else if (locale == "zh"sv) {
    495      chinese++;
    496    }
    497  }
    498 
    499  // Each locale should be found exactly once.
    500  ASSERT_EQ(english, 1);
    501  ASSERT_EQ(german, 1);
    502  ASSERT_EQ(chinese, 1);
    503 }
    504 
    505 TEST(IntlDateTimeFormat, TryFormatToParts)
    506 {
    507  auto dateTimePatternGenerator =
    508      DateTimePatternGenerator::TryCreate("en").unwrap();
    509 
    510  DateTimeFormat::ComponentsBag components;
    511  components.year = Some(DateTimeFormat::Numeric::Numeric);
    512  components.month = Some(DateTimeFormat::Month::TwoDigit);
    513  components.day = Some(DateTimeFormat::Numeric::TwoDigit);
    514  components.hour = Some(DateTimeFormat::Numeric::TwoDigit);
    515  components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
    516  components.hour12 = Some(false);
    517 
    518  UniquePtr<DateTimeFormat> dtFormat =
    519      DateTimeFormat::TryCreateFromComponents(
    520          MakeStringSpan("en-US"), components, dateTimePatternGenerator.get(),
    521          Some(MakeStringSpan(u"GMT")))
    522          .unwrap();
    523 
    524  TestBuffer<char16_t> buffer;
    525  mozilla::intl::DateTimePartVector parts;
    526  auto result = dtFormat->TryFormatToParts(DATE, buffer, parts);
    527  ASSERT_TRUE(result.isOk());
    528 
    529  std::u16string_view strView = buffer.get_string_view();
    530  ASSERT_EQ(strView, u"09/23/2002, 17:07");
    531 
    532  auto getSubStringView = [strView, &parts](size_t index) {
    533    size_t pos = index == 0 ? 0 : parts[index - 1].mEndIndex;
    534    size_t count = parts[index].mEndIndex - pos;
    535    return strView.substr(pos, count);
    536  };
    537 
    538  ASSERT_EQ(parts[0].mType, DateTimePartType::Month);
    539  ASSERT_EQ(getSubStringView(0), u"09");
    540 
    541  ASSERT_EQ(parts[1].mType, DateTimePartType::Literal);
    542  ASSERT_EQ(getSubStringView(1), u"/");
    543 
    544  ASSERT_EQ(parts[2].mType, DateTimePartType::Day);
    545  ASSERT_EQ(getSubStringView(2), u"23");
    546 
    547  ASSERT_EQ(parts[3].mType, DateTimePartType::Literal);
    548  ASSERT_EQ(getSubStringView(3), u"/");
    549 
    550  ASSERT_EQ(parts[4].mType, DateTimePartType::Year);
    551  ASSERT_EQ(getSubStringView(4), u"2002");
    552 
    553  ASSERT_EQ(parts[5].mType, DateTimePartType::Literal);
    554  ASSERT_EQ(getSubStringView(5), u", ");
    555 
    556  ASSERT_EQ(parts[6].mType, DateTimePartType::Hour);
    557  ASSERT_EQ(getSubStringView(6), u"17");
    558 
    559  ASSERT_EQ(parts[7].mType, DateTimePartType::Literal);
    560  ASSERT_EQ(getSubStringView(7), u":");
    561 
    562  ASSERT_EQ(parts[8].mType, DateTimePartType::Minute);
    563  ASSERT_EQ(getSubStringView(8), u"07");
    564 
    565  ASSERT_EQ(parts.length(), 9u);
    566 }
    567 
    568 TEST(IntlDateTimeFormat, SetStartTimeIfGregorian)
    569 {
    570  using namespace std::literals;
    571 
    572  DateTimeFormat::StyleBag style{};
    573  style.date = Some(DateTimeFormat::Style::Long);
    574 
    575  auto timeZone = Some(MakeStringSpan(u"UTC"));
    576 
    577  // Gregorian change date defaults to October 15, 1582 in ICU. Test with a date
    578  // before the default change date, in this case January 1, 1582.
    579  constexpr double FirstJanuary1582 = -12244089600000.0;
    580 
    581  // One year expressed in milliseconds.
    582  constexpr double oneYear = (365 * 24 * 60 * 60) * 1000.0;
    583 
    584  // Test with and without explicit calendar. The start time of the calendar can
    585  // only be adjusted for the Gregorian and the ISO-8601 calendar.
    586  for (const char* locale : {
    587           "en-US",
    588           "en-US-u-ca-gregory",
    589           "en-US-u-ca-iso8601",
    590       }) {
    591    auto gen = DateTimePatternGenerator::TryCreate(locale).unwrap();
    592 
    593    auto dtFormat = DateTimeFormat::TryCreateFromStyle(
    594                        MakeStringSpan(locale), style, gen.get(), timeZone)
    595                        .unwrap();
    596 
    597    const char* Jan01_1582;
    598    const char* Jan01_1583;
    599    if (locale == "en-US-u-ca-iso8601"sv) {
    600      Jan01_1582 = "1582 January 1";
    601      Jan01_1583 = "1583 January 1";
    602    } else {
    603      Jan01_1582 = "January 1, 1582";
    604      Jan01_1583 = "January 1, 1583";
    605    }
    606 
    607    TestBuffer<char> buffer;
    608 
    609    // Before the default Gregorian change date, but not interpreted in the
    610    // Julian calendar, which is December 22, 1581. Instead interpreted in
    611    // proleptic Gregorian calendar at January 1, 1582.
    612    dtFormat->TryFormat(FirstJanuary1582, buffer).unwrap();
    613    ASSERT_TRUE(buffer.verboseMatches(Jan01_1582));
    614 
    615    // After default Gregorian change date, so January 1, 1583.
    616    dtFormat->TryFormat(FirstJanuary1582 + oneYear, buffer).unwrap();
    617    ASSERT_TRUE(buffer.verboseMatches(Jan01_1583));
    618  }
    619 }
    620 
    621 TEST(IntlDateTimeFormat, GetTimeSeparator)
    622 {
    623  struct TestData {
    624    const char* locale;
    625    const char* numberingSystem;
    626    const char16_t* expected;
    627  } testData[] = {
    628      {"root", "latn", u":"},
    629      {"root", "arab", u":"},
    630      {"root", "thai", u":"},
    631      {"root", "arabext", u"٫"},
    632 
    633      // English uses the same data as the root locale.
    634      {"en", "latn", u":"},
    635      {"en", "arab", u":"},
    636      {"en", "thai", u":"},
    637      {"en", "arabext", u"٫"},
    638 
    639      // Spanish uses the same data as the root locale.
    640      {"es", "latn", u":"},
    641      {"es", "arab", u":"},
    642      {"es", "thai", u":"},
    643      {"es", "arabext", u"٫"},
    644 
    645      // German (Austria) uses the same data as the root locale.
    646      {"de-AT", "latn", u":"},
    647      {"de-AT", "arab", u":"},
    648      {"de-AT", "thai", u":"},
    649      {"de-AT", "arabext", u"٫"},
    650 
    651      // Danish has a different time separator for "latn".
    652      {"da", "latn", u"."},
    653      {"da", "arab", u":"},
    654      {"da", "thai", u"."},
    655      {"da", "arabext", u"٫"},
    656 
    657      // Same time separator as Danish.
    658      {"en-DK", "latn", u"."},
    659      {"en-DK", "arab", u":"},
    660      {"en-DK", "thai", u"."},
    661      {"en-DK", "arabext", u"٫"},
    662 
    663      // Norwegian overrides time separators for "arab" and "arabext".
    664      {"no", "latn", u":"},
    665      {"no", "arab", u"."},
    666      {"no", "thai", u":"},
    667      {"no", "arabext", u"."},
    668 
    669      // Parent locale of Bokmål is Norwegian.
    670      {"nb", "latn", u":"},
    671      {"nb", "arab", u"."},
    672      {"nb", "thai", u":"},
    673      {"nb", "arabext", u"."},
    674 
    675      // Farsi overrides the time separator for "arabext".
    676      {"fa", "latn", u":"},
    677      {"fa", "arab", u":"},
    678      {"fa", "thai", u":"},
    679      {"fa", "arabext", u":"},
    680  };
    681 
    682  for (const auto& data : testData) {
    683    TestBuffer<char16_t> timeSeparator;
    684    auto timeSeparatorResult = DateTimeFormat::GetTimeSeparator(
    685        MakeStringSpan(data.locale), MakeStringSpan(data.numberingSystem),
    686        timeSeparator);
    687    ASSERT_TRUE(timeSeparatorResult.isOk());
    688    ASSERT_TRUE(timeSeparator.verboseMatches(data.expected));
    689  }
    690 }
    691 }  // namespace mozilla::intl