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