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 
testStyle(const char * aLocale,DateTimeFormat::StyleBag & aStyleBag)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 
TEST(IntlDateTimeFormat,Style_enUS_utf8)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 
TEST(IntlDateTimeFormat,Style_enUS_utf16)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 
TEST(IntlDateTimeFormat,Style_ar_utf8)57 TEST(IntlDateTimeFormat, Style_ar_utf8)
58 {
59   DateTimeFormat::StyleBag style;
60   style.time = Some(DateTimeFormat::Style::Medium);
61 
62   auto dtFormat = testStyle("ar", style);
63   TestBuffer<char> buffer;
64   dtFormat->TryFormat(DATE, buffer).unwrap();
65 
66   ASSERT_TRUE(buffer.verboseMatches("٨:٠٧:٣٠ م"));
67 }
68 
TEST(IntlDateTimeFormat,Style_ar_utf16)69 TEST(IntlDateTimeFormat, Style_ar_utf16)
70 {
71   DateTimeFormat::StyleBag style;
72   style.time = Some(DateTimeFormat::Style::Medium);
73 
74   auto dtFormat = testStyle("ar", style);
75   TestBuffer<char16_t> buffer;
76   dtFormat->TryFormat(DATE, buffer).unwrap();
77 
78   ASSERT_TRUE(buffer.verboseMatches(u"٨:٠٧:٣٠ م"));
79 }
80 
TEST(IntlDateTimeFormat,Style_enUS_fallback_to_default_styles)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 
TEST(IntlDateTimeFormat,Skeleton_enUS_utf8_in)92 TEST(IntlDateTimeFormat, Skeleton_enUS_utf8_in)
93 {
94   UniquePtr<DateTimePatternGenerator> gen = nullptr;
95   auto dateTimePatternGenerator =
96       DateTimePatternGenerator::TryCreate("en").unwrap();
97 
98   UniquePtr<DateTimeFormat> dtFormat =
99       DateTimeFormat::TryCreateFromSkeleton(
100           MakeStringSpan("en-US"), MakeStringSpan("yMdhhmmss"),
101           dateTimePatternGenerator.get(), Nothing(),
102           Some(MakeStringSpan("GMT+3")))
103           .unwrap();
104   TestBuffer<char> buffer;
105   dtFormat->TryFormat(DATE, buffer).unwrap();
106 
107   ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 08:07:30 PM"));
108 }
109 
TEST(IntlDateTimeFormat,Skeleton_enUS_utf16_in)110 TEST(IntlDateTimeFormat, Skeleton_enUS_utf16_in)
111 {
112   UniquePtr<DateTimePatternGenerator> gen = nullptr;
113   auto dateTimePatternGenerator =
114       DateTimePatternGenerator::TryCreate("en").unwrap();
115 
116   UniquePtr<DateTimeFormat> dtFormat =
117       DateTimeFormat::TryCreateFromSkeleton(
118           MakeStringSpan("en-US"), MakeStringSpan(u"yMdhhmmss"),
119           dateTimePatternGenerator.get(), Nothing(),
120           Some(MakeStringSpan(u"GMT+3")))
121           .unwrap();
122   TestBuffer<char> buffer;
123   dtFormat->TryFormat(DATE, buffer).unwrap();
124 
125   ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 08:07:30 PM"));
126 }
127 
TEST(IntlDateTimeFormat,Time_zone_IANA_identifier)128 TEST(IntlDateTimeFormat, Time_zone_IANA_identifier)
129 {
130   auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
131 
132   DateTimeFormat::StyleBag style;
133   style.date = Some(DateTimeFormat::Style::Medium);
134   style.time = Some(DateTimeFormat::Style::Medium);
135 
136   auto dtFormat = DateTimeFormat::TryCreateFromStyle(
137                       MakeStringSpan("en-US"), style, gen.get(),
138                       Some(MakeStringSpan(u"America/Chicago")))
139                       .unwrap();
140   TestBuffer<char> buffer;
141   dtFormat->TryFormat(DATE, buffer).unwrap();
142   ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 12:07:30 PM"));
143 }
144 
TEST(IntlDateTimeFormat,GetAllowedHourCycles)145 TEST(IntlDateTimeFormat, GetAllowedHourCycles)
146 {
147   auto allowed_en_US = DateTimeFormat::GetAllowedHourCycles(
148                            MakeStringSpan("en"), Some(MakeStringSpan("US")))
149                            .unwrap();
150 
151   ASSERT_TRUE(allowed_en_US.length() == 2);
152   ASSERT_EQ(allowed_en_US[0], DateTimeFormat::HourCycle::H12);
153   ASSERT_EQ(allowed_en_US[1], DateTimeFormat::HourCycle::H23);
154 
155   auto allowed_de =
156       DateTimeFormat::GetAllowedHourCycles(MakeStringSpan("de"), Nothing())
157           .unwrap();
158 
159   ASSERT_TRUE(allowed_de.length() == 2);
160   ASSERT_EQ(allowed_de[0], DateTimeFormat::HourCycle::H23);
161   ASSERT_EQ(allowed_de[1], DateTimeFormat::HourCycle::H12);
162 }
163 
TEST(IntlDateTimePatternGenerator,GetBestPattern)164 TEST(IntlDateTimePatternGenerator, GetBestPattern)
165 {
166   auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
167   TestBuffer<char16_t> buffer;
168 
169   gen->GetBestPattern(MakeStringSpan(u"yMd"), buffer).unwrap();
170   ASSERT_TRUE(buffer.verboseMatches(u"M/d/y"));
171 }
172 
TEST(IntlDateTimePatternGenerator,GetSkeleton)173 TEST(IntlDateTimePatternGenerator, GetSkeleton)
174 {
175   auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
176   TestBuffer<char16_t> buffer;
177 
178   DateTimePatternGenerator::GetSkeleton(MakeStringSpan(u"M/d/y"), buffer)
179       .unwrap();
180   ASSERT_TRUE(buffer.verboseMatches(u"yMd"));
181 }
182 
TEST(IntlDateTimePatternGenerator,GetPlaceholderPattern)183 TEST(IntlDateTimePatternGenerator, GetPlaceholderPattern)
184 {
185   auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
186   auto span = gen->GetPlaceholderPattern();
187   // The default date-time pattern for 'en' locale is u"{1}, {0}".
188   ASSERT_EQ(span, MakeStringSpan(u"{1}, {0}"));
189 }
190 
191 // A utility function to help test the DateTimeFormat::ComponentsBag.
FormatComponents(TestBuffer<char16_t> & aBuffer,DateTimeFormat::ComponentsBag & aComponents,Span<const char> aLocale=MakeStringSpan ("en-US"))192 [[nodiscard]] bool FormatComponents(
193     TestBuffer<char16_t>& aBuffer, DateTimeFormat::ComponentsBag& aComponents,
194     Span<const char> aLocale = MakeStringSpan("en-US")) {
195   UniquePtr<DateTimePatternGenerator> gen = nullptr;
196   auto dateTimePatternGenerator =
197       DateTimePatternGenerator::TryCreate(aLocale.data()).unwrap();
198 
199   auto dtFormat = DateTimeFormat::TryCreateFromComponents(
200       aLocale, aComponents, dateTimePatternGenerator.get(),
201       Some(MakeStringSpan(u"GMT+3")));
202   if (dtFormat.isErr()) {
203     fprintf(stderr, "Could not create a DateTimeFormat\n");
204     return false;
205   }
206 
207   auto result = dtFormat.unwrap()->TryFormat(DATE, aBuffer);
208   if (result.isErr()) {
209     fprintf(stderr, "Could not format a DateTimeFormat\n");
210     return false;
211   }
212 
213   return true;
214 }
215 
TEST(IntlDateTimeFormat,Components)216 TEST(IntlDateTimeFormat, Components)
217 {
218   DateTimeFormat::ComponentsBag components{};
219 
220   components.year = Some(DateTimeFormat::Numeric::Numeric);
221   components.month = Some(DateTimeFormat::Month::Numeric);
222   components.day = Some(DateTimeFormat::Numeric::Numeric);
223 
224   components.hour = Some(DateTimeFormat::Numeric::Numeric);
225   components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
226   components.second = Some(DateTimeFormat::Numeric::TwoDigit);
227 
228   TestBuffer<char16_t> buffer;
229   ASSERT_TRUE(FormatComponents(buffer, components));
230   ASSERT_TRUE(buffer.verboseMatches(u"9/23/2002, 8:07:30 PM"));
231 }
232 
TEST(IntlDateTimeFormat,Components_es_ES)233 TEST(IntlDateTimeFormat, Components_es_ES)
234 {
235   DateTimeFormat::ComponentsBag components{};
236 
237   components.year = Some(DateTimeFormat::Numeric::Numeric);
238   components.month = Some(DateTimeFormat::Month::Numeric);
239   components.day = Some(DateTimeFormat::Numeric::Numeric);
240 
241   components.hour = Some(DateTimeFormat::Numeric::Numeric);
242   components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
243   components.second = Some(DateTimeFormat::Numeric::TwoDigit);
244 
245   TestBuffer<char16_t> buffer;
246   ASSERT_TRUE(FormatComponents(buffer, components, MakeStringSpan("es-ES")));
247   ASSERT_TRUE(buffer.verboseMatches(u"23/9/2002, 20:07:30"));
248 }
249 
TEST(IntlDateTimeFormat,ComponentsAll)250 TEST(IntlDateTimeFormat, ComponentsAll)
251 {
252   // Use most all of the components.
253   DateTimeFormat::ComponentsBag components{};
254 
255   components.era = Some(DateTimeFormat::Text::Short);
256 
257   components.year = Some(DateTimeFormat::Numeric::Numeric);
258   components.month = Some(DateTimeFormat::Month::Numeric);
259   components.day = Some(DateTimeFormat::Numeric::Numeric);
260 
261   components.weekday = Some(DateTimeFormat::Text::Short);
262 
263   components.hour = Some(DateTimeFormat::Numeric::Numeric);
264   components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
265   components.second = Some(DateTimeFormat::Numeric::TwoDigit);
266 
267   components.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
268   components.hourCycle = Some(DateTimeFormat::HourCycle::H24);
269   components.fractionalSecondDigits = Some(3);
270 
271   TestBuffer<char16_t> buffer;
272   ASSERT_TRUE(FormatComponents(buffer, components));
273   ASSERT_TRUE(buffer.verboseMatches(u"Mon, 9 23, 2002 AD, 20:07:30.000 GMT+3"));
274 }
275 
TEST(IntlDateTimeFormat,ComponentsHour12Default)276 TEST(IntlDateTimeFormat, ComponentsHour12Default)
277 {
278   // Assert the behavior of the default "en-US" 12 hour time with day period.
279   DateTimeFormat::ComponentsBag components{};
280   components.hour = Some(DateTimeFormat::Numeric::Numeric);
281   components.minute = Some(DateTimeFormat::Numeric::Numeric);
282 
283   TestBuffer<char16_t> buffer;
284   ASSERT_TRUE(FormatComponents(buffer, components));
285   ASSERT_TRUE(buffer.verboseMatches(u"8:07 PM"));
286 }
287 
TEST(IntlDateTimeFormat,ComponentsHour24)288 TEST(IntlDateTimeFormat, ComponentsHour24)
289 {
290   // Test the behavior of using 24 hour time to override the default of
291   // hour 12 with a day period.
292   DateTimeFormat::ComponentsBag components{};
293   components.hour = Some(DateTimeFormat::Numeric::Numeric);
294   components.minute = Some(DateTimeFormat::Numeric::Numeric);
295   components.hour12 = Some(false);
296 
297   TestBuffer<char16_t> buffer;
298   ASSERT_TRUE(FormatComponents(buffer, components));
299   ASSERT_TRUE(buffer.verboseMatches(u"20:07"));
300 }
301 
TEST(IntlDateTimeFormat,ComponentsHour12DayPeriod)302 TEST(IntlDateTimeFormat, ComponentsHour12DayPeriod)
303 {
304   // Test the behavior of specifying a specific day period.
305   DateTimeFormat::ComponentsBag components{};
306 
307   components.hour = Some(DateTimeFormat::Numeric::Numeric);
308   components.minute = Some(DateTimeFormat::Numeric::Numeric);
309   components.dayPeriod = Some(DateTimeFormat::Text::Long);
310 
311   TestBuffer<char16_t> buffer;
312   ASSERT_TRUE(FormatComponents(buffer, components));
313   ASSERT_TRUE(buffer.verboseMatches(u"8:07 in the evening"));
314 }
315 
ToString(uint8_t b)316 const char* ToString(uint8_t b) { return "uint8_t"; }
ToString(bool b)317 const char* ToString(bool b) { return b ? "true" : "false"; }
318 
319 template <typename T>
ToString(Maybe<T> option)320 const char* ToString(Maybe<T> option) {
321   if (option) {
322     if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, uint8_t>) {
323       return ToString(*option);
324     } else {
325       return DateTimeFormat::ToString(*option);
326     }
327   }
328   return "Nothing";
329 }
330 
331 template <typename T>
VerboseEquals(T expected,T actual,const char * msg)332 [[nodiscard]] bool VerboseEquals(T expected, T actual, const char* msg) {
333   if (expected != actual) {
334     fprintf(stderr, "%s\n  Actual: %s\nExpected: %s\n", msg, ToString(actual),
335             ToString(expected));
336     return false;
337   }
338   return true;
339 }
340 
341 // A testing utility for getting nice errors when ComponentsBags don't match.
VerboseEquals(DateTimeFormat::ComponentsBag & expected,DateTimeFormat::ComponentsBag & actual)342 [[nodiscard]] bool VerboseEquals(DateTimeFormat::ComponentsBag& expected,
343                                  DateTimeFormat::ComponentsBag& actual) {
344   // clang-format off
345   return
346       VerboseEquals(expected.era, actual.era, "Components do not match: bag.era") &&
347       VerboseEquals(expected.year, actual.year, "Components do not match: bag.year") &&
348       VerboseEquals(expected.month, actual.month, "Components do not match: bag.month") &&
349       VerboseEquals(expected.day, actual.day, "Components do not match: bag.day") &&
350       VerboseEquals(expected.weekday, actual.weekday, "Components do not match: bag.weekday") &&
351       VerboseEquals(expected.hour, actual.hour, "Components do not match: bag.hour") &&
352       VerboseEquals(expected.minute, actual.minute, "Components do not match: bag.minute") &&
353       VerboseEquals(expected.second, actual.second, "Components do not match: bag.second") &&
354       VerboseEquals(expected.timeZoneName, actual.timeZoneName, "Components do not match: bag.timeZoneName") &&
355       VerboseEquals(expected.hour12, actual.hour12, "Components do not match: bag.hour12") &&
356       VerboseEquals(expected.hourCycle, actual.hourCycle, "Components do not match: bag.hourCycle") &&
357       VerboseEquals(expected.dayPeriod, actual.dayPeriod, "Components do not match: bag.dayPeriod") &&
358       VerboseEquals(expected.fractionalSecondDigits, actual.fractionalSecondDigits, "Components do not match: bag.fractionalSecondDigits");
359   // clang-format on
360 }
361 
362 // A utility function to help test the DateTimeFormat::ComponentsBag.
ResolveComponentsBag(DateTimeFormat::ComponentsBag & aComponentsIn,DateTimeFormat::ComponentsBag * aComponentsOut,Span<const char> aLocale=MakeStringSpan ("en-US"))363 [[nodiscard]] bool ResolveComponentsBag(
364     DateTimeFormat::ComponentsBag& aComponentsIn,
365     DateTimeFormat::ComponentsBag* aComponentsOut,
366     Span<const char> aLocale = MakeStringSpan("en-US")) {
367   UniquePtr<DateTimePatternGenerator> gen = nullptr;
368   auto dateTimePatternGenerator =
369       DateTimePatternGenerator::TryCreate("en").unwrap();
370   auto dtFormat = DateTimeFormat::TryCreateFromComponents(
371       aLocale, aComponentsIn, dateTimePatternGenerator.get(),
372       Some(MakeStringSpan(u"GMT+3")));
373   if (dtFormat.isErr()) {
374     fprintf(stderr, "Could not create a DateTimeFormat\n");
375     return false;
376   }
377 
378   auto result = dtFormat.unwrap()->ResolveComponents();
379   if (result.isErr()) {
380     fprintf(stderr, "Could not resolve the components\n");
381     return false;
382   }
383 
384   *aComponentsOut = result.unwrap();
385   return true;
386 }
387 
TEST(IntlDateTimeFormat,ResolvedComponentsDate)388 TEST(IntlDateTimeFormat, ResolvedComponentsDate)
389 {
390   DateTimeFormat::ComponentsBag input{};
391   {
392     input.year = Some(DateTimeFormat::Numeric::Numeric);
393     input.month = Some(DateTimeFormat::Month::Numeric);
394     input.day = Some(DateTimeFormat::Numeric::Numeric);
395   }
396 
397   DateTimeFormat::ComponentsBag expected = input;
398 
399   DateTimeFormat::ComponentsBag resolved{};
400   ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
401   ASSERT_TRUE(VerboseEquals(expected, resolved));
402 }
403 
TEST(IntlDateTimeFormat,ResolvedComponentsAll)404 TEST(IntlDateTimeFormat, ResolvedComponentsAll)
405 {
406   DateTimeFormat::ComponentsBag input{};
407   {
408     input.era = Some(DateTimeFormat::Text::Short);
409 
410     input.year = Some(DateTimeFormat::Numeric::Numeric);
411     input.month = Some(DateTimeFormat::Month::Numeric);
412     input.day = Some(DateTimeFormat::Numeric::Numeric);
413 
414     input.weekday = Some(DateTimeFormat::Text::Short);
415 
416     input.hour = Some(DateTimeFormat::Numeric::Numeric);
417     input.minute = Some(DateTimeFormat::Numeric::TwoDigit);
418     input.second = Some(DateTimeFormat::Numeric::TwoDigit);
419 
420     input.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
421     input.hourCycle = Some(DateTimeFormat::HourCycle::H24);
422     input.fractionalSecondDigits = Some(3);
423   }
424 
425   DateTimeFormat::ComponentsBag expected = input;
426   {
427     expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
428     expected.hourCycle = Some(DateTimeFormat::HourCycle::H24);
429     expected.hour12 = Some(false);
430   }
431 
432   DateTimeFormat::ComponentsBag resolved{};
433   ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
434   ASSERT_TRUE(VerboseEquals(expected, resolved));
435 }
436 
TEST(IntlDateTimeFormat,ResolvedComponentsHourDayPeriod)437 TEST(IntlDateTimeFormat, ResolvedComponentsHourDayPeriod)
438 {
439   DateTimeFormat::ComponentsBag input{};
440   {
441     input.hour = Some(DateTimeFormat::Numeric::Numeric);
442     input.minute = Some(DateTimeFormat::Numeric::Numeric);
443   }
444 
445   DateTimeFormat::ComponentsBag expected = input;
446   {
447     expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
448     expected.hourCycle = Some(DateTimeFormat::HourCycle::H12);
449     expected.hour12 = Some(true);
450   }
451 
452   DateTimeFormat::ComponentsBag resolved{};
453   ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
454   ASSERT_TRUE(VerboseEquals(expected, resolved));
455 }
456 
TEST(IntlDateTimeFormat,ResolvedComponentsHour12)457 TEST(IntlDateTimeFormat, ResolvedComponentsHour12)
458 {
459   DateTimeFormat::ComponentsBag input{};
460   {
461     input.hour = Some(DateTimeFormat::Numeric::Numeric);
462     input.minute = Some(DateTimeFormat::Numeric::Numeric);
463     input.hour12 = Some(false);
464   }
465 
466   DateTimeFormat::ComponentsBag expected = input;
467   {
468     expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
469     expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
470     expected.hourCycle = Some(DateTimeFormat::HourCycle::H23);
471     expected.hour12 = Some(false);
472   }
473 
474   DateTimeFormat::ComponentsBag resolved{};
475   ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
476   ASSERT_TRUE(VerboseEquals(expected, resolved));
477 }
478 
TEST(IntlDateTimeFormat,GetOriginalSkeleton)479 TEST(IntlDateTimeFormat, GetOriginalSkeleton)
480 {
481   // Demonstrate that the original skeleton and the resolved skeleton can
482   // differ.
483   DateTimeFormat::ComponentsBag components{};
484   components.month = Some(DateTimeFormat::Month::Narrow);
485   components.day = Some(DateTimeFormat::Numeric::TwoDigit);
486 
487   const char* locale = "zh-Hans-CN";
488   auto dateTimePatternGenerator =
489       DateTimePatternGenerator::TryCreate(locale).unwrap();
490 
491   auto result = DateTimeFormat::TryCreateFromComponents(
492       MakeStringSpan(locale), components, dateTimePatternGenerator.get(),
493       Some(MakeStringSpan(u"GMT+3")));
494   ASSERT_TRUE(result.isOk());
495   auto dtFormat = result.unwrap();
496 
497   TestBuffer<char16_t> originalSkeleton;
498   auto originalSkeletonResult = dtFormat->GetOriginalSkeleton(originalSkeleton);
499   ASSERT_TRUE(originalSkeletonResult.isOk());
500   ASSERT_TRUE(originalSkeleton.verboseMatches(u"MMMMMdd"));
501 
502   TestBuffer<char16_t> pattern;
503   auto patternResult = dtFormat->GetPattern(pattern);
504   ASSERT_TRUE(patternResult.isOk());
505   ASSERT_TRUE(pattern.verboseMatches(u"M月dd日"));
506 
507   TestBuffer<char16_t> resolvedSkeleton;
508   auto resolvedSkeletonResult = DateTimePatternGenerator::GetSkeleton(
509       Span(pattern.data(), pattern.length()), resolvedSkeleton);
510 
511   ASSERT_TRUE(resolvedSkeletonResult.isOk());
512   ASSERT_TRUE(resolvedSkeleton.verboseMatches(u"Mdd"));
513 }
514 
TEST(IntlDateTimeFormat,GetAvailableLocales)515 TEST(IntlDateTimeFormat, GetAvailableLocales)
516 {
517   using namespace std::literals;
518 
519   int32_t english = 0;
520   int32_t german = 0;
521   int32_t chinese = 0;
522 
523   // Since this list is dependent on ICU, and may change between upgrades, only
524   // test a subset of the available locales.
525   for (const char* locale : DateTimeFormat::GetAvailableLocales()) {
526     if (locale == "en"sv) {
527       english++;
528     } else if (locale == "de"sv) {
529       german++;
530     } else if (locale == "zh"sv) {
531       chinese++;
532     }
533   }
534 
535   // Each locale should be found exactly once.
536   ASSERT_EQ(english, 1);
537   ASSERT_EQ(german, 1);
538   ASSERT_EQ(chinese, 1);
539 }
540 
TEST(IntlDateTimeFormat,TryFormatToParts)541 TEST(IntlDateTimeFormat, TryFormatToParts)
542 {
543   auto dateTimePatternGenerator =
544       DateTimePatternGenerator::TryCreate("en").unwrap();
545 
546   UniquePtr<DateTimeFormat> dtFormat =
547       DateTimeFormat::TryCreateFromSkeleton(
548           MakeStringSpan("en-US"), MakeStringSpan(u"yMMddHHmm"),
549           dateTimePatternGenerator.get(), Nothing(),
550           Some(MakeStringSpan(u"GMT")))
551           .unwrap();
552 
553   TestBuffer<char16_t> buffer;
554   mozilla::intl::DateTimePartVector parts;
555   auto result = dtFormat->TryFormatToParts(DATE, buffer, parts);
556   ASSERT_TRUE(result.isOk());
557 
558   std::u16string_view strView = buffer.get_string_view();
559   ASSERT_EQ(strView, u"09/23/2002, 17:07");
560 
561   auto getSubStringView = [strView, &parts](size_t index) {
562     size_t pos = index == 0 ? 0 : parts[index - 1].mEndIndex;
563     size_t count = parts[index].mEndIndex - pos;
564     return strView.substr(pos, count);
565   };
566 
567   ASSERT_EQ(parts[0].mType, DateTimePartType::Month);
568   ASSERT_EQ(getSubStringView(0), u"09");
569 
570   ASSERT_EQ(parts[1].mType, DateTimePartType::Literal);
571   ASSERT_EQ(getSubStringView(1), u"/");
572 
573   ASSERT_EQ(parts[2].mType, DateTimePartType::Day);
574   ASSERT_EQ(getSubStringView(2), u"23");
575 
576   ASSERT_EQ(parts[3].mType, DateTimePartType::Literal);
577   ASSERT_EQ(getSubStringView(3), u"/");
578 
579   ASSERT_EQ(parts[4].mType, DateTimePartType::Year);
580   ASSERT_EQ(getSubStringView(4), u"2002");
581 
582   ASSERT_EQ(parts[5].mType, DateTimePartType::Literal);
583   ASSERT_EQ(getSubStringView(5), u", ");
584 
585   ASSERT_EQ(parts[6].mType, DateTimePartType::Hour);
586   ASSERT_EQ(getSubStringView(6), u"17");
587 
588   ASSERT_EQ(parts[7].mType, DateTimePartType::Literal);
589   ASSERT_EQ(getSubStringView(7), u":");
590 
591   ASSERT_EQ(parts[8].mType, DateTimePartType::Minute);
592   ASSERT_EQ(getSubStringView(8), u"07");
593 
594   ASSERT_EQ(parts.length(), 9u);
595 }
596 }  // namespace mozilla::intl
597