summaryrefslogtreecommitdiffstats
path: root/intl/components/gtest/TestDateTimeFormat.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/components/gtest/TestDateTimeFormat.cpp')
-rw-r--r--intl/components/gtest/TestDateTimeFormat.cpp621
1 files changed, 621 insertions, 0 deletions
diff --git a/intl/components/gtest/TestDateTimeFormat.cpp b/intl/components/gtest/TestDateTimeFormat.cpp
new file mode 100644
index 0000000000..057e26ea13
--- /dev/null
+++ b/intl/components/gtest/TestDateTimeFormat.cpp
@@ -0,0 +1,621 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "gtest/gtest.h"
+
+#include "mozilla/intl/Calendar.h"
+#include "mozilla/intl/DateTimeFormat.h"
+#include "mozilla/intl/DateTimePart.h"
+#include "mozilla/intl/DateTimePatternGenerator.h"
+#include "mozilla/Span.h"
+#include "TestBuffer.h"
+
+#include <string_view>
+
+namespace mozilla::intl {
+
+// Firefox 1.0 release date.
+const double DATE = 1032800850000.0;
+
+static UniquePtr<DateTimeFormat> testStyle(
+ const char* aLocale, DateTimeFormat::StyleBag& aStyleBag) {
+ // Always specify a time zone in the tests, otherwise it will use the system
+ // time zone which can vary between test runs.
+ auto timeZone = Some(MakeStringSpan(u"GMT+3"));
+ auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+ return DateTimeFormat::TryCreateFromStyle(MakeStringSpan(aLocale), aStyleBag,
+ gen.get(), timeZone)
+ .unwrap();
+}
+
+TEST(IntlDateTimeFormat, Style_enUS_utf8)
+{
+ DateTimeFormat::StyleBag style;
+ style.date = Some(DateTimeFormat::Style::Medium);
+ style.time = Some(DateTimeFormat::Style::Medium);
+
+ auto dtFormat = testStyle("en-US", style);
+ TestBuffer<char> buffer;
+ dtFormat->TryFormat(DATE, buffer).unwrap();
+
+ ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 8:07:30 PM"));
+}
+
+TEST(IntlDateTimeFormat, Style_enUS_utf16)
+{
+ DateTimeFormat::StyleBag style;
+ style.date = Some(DateTimeFormat::Style::Medium);
+ style.time = Some(DateTimeFormat::Style::Medium);
+
+ auto dtFormat = testStyle("en-US", style);
+ TestBuffer<char16_t> buffer;
+ dtFormat->TryFormat(DATE, buffer).unwrap();
+
+ ASSERT_TRUE(buffer.verboseMatches(u"Sep 23, 2002, 8:07:30 PM"));
+}
+
+TEST(IntlDateTimeFormat, Style_ar_utf8)
+{
+ DateTimeFormat::StyleBag style;
+ style.time = Some(DateTimeFormat::Style::Medium);
+
+ auto dtFormat = testStyle("ar", style);
+ TestBuffer<char> buffer;
+ dtFormat->TryFormat(DATE, buffer).unwrap();
+
+ ASSERT_TRUE(buffer.verboseMatches("٨:٠٧:٣٠ م"));
+}
+
+TEST(IntlDateTimeFormat, Style_ar_utf16)
+{
+ DateTimeFormat::StyleBag style;
+ style.time = Some(DateTimeFormat::Style::Medium);
+
+ auto dtFormat = testStyle("ar", style);
+ TestBuffer<char16_t> buffer;
+ dtFormat->TryFormat(DATE, buffer).unwrap();
+
+ ASSERT_TRUE(buffer.verboseMatches(u"٨:٠٧:٣٠ م"));
+}
+
+TEST(IntlDateTimeFormat, Style_enUS_fallback_to_default_styles)
+{
+ DateTimeFormat::StyleBag style;
+
+ auto dtFormat = testStyle("en-US", style);
+ TestBuffer<char> buffer;
+ dtFormat->TryFormat(DATE, buffer).unwrap();
+
+ ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 8:07:30 PM"));
+}
+
+TEST(IntlDateTimeFormat, Time_zone_IANA_identifier)
+{
+ auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+
+ DateTimeFormat::StyleBag style;
+ style.date = Some(DateTimeFormat::Style::Medium);
+ style.time = Some(DateTimeFormat::Style::Medium);
+
+ auto dtFormat = DateTimeFormat::TryCreateFromStyle(
+ MakeStringSpan("en-US"), style, gen.get(),
+ Some(MakeStringSpan(u"America/Chicago")))
+ .unwrap();
+ TestBuffer<char> buffer;
+ dtFormat->TryFormat(DATE, buffer).unwrap();
+ ASSERT_TRUE(buffer.verboseMatches("Sep 23, 2002, 12:07:30 PM"));
+}
+
+TEST(IntlDateTimeFormat, GetAllowedHourCycles)
+{
+ auto allowed_en_US = DateTimeFormat::GetAllowedHourCycles(
+ MakeStringSpan("en"), Some(MakeStringSpan("US")))
+ .unwrap();
+
+ ASSERT_TRUE(allowed_en_US.length() == 2);
+ ASSERT_EQ(allowed_en_US[0], DateTimeFormat::HourCycle::H12);
+ ASSERT_EQ(allowed_en_US[1], DateTimeFormat::HourCycle::H23);
+
+ auto allowed_de =
+ DateTimeFormat::GetAllowedHourCycles(MakeStringSpan("de"), Nothing())
+ .unwrap();
+
+ ASSERT_TRUE(allowed_de.length() == 2);
+ ASSERT_EQ(allowed_de[0], DateTimeFormat::HourCycle::H23);
+ ASSERT_EQ(allowed_de[1], DateTimeFormat::HourCycle::H12);
+}
+
+TEST(IntlDateTimePatternGenerator, GetBestPattern)
+{
+ auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+ TestBuffer<char16_t> buffer;
+
+ gen->GetBestPattern(MakeStringSpan(u"yMd"), buffer).unwrap();
+ ASSERT_TRUE(buffer.verboseMatches(u"M/d/y"));
+}
+
+TEST(IntlDateTimePatternGenerator, GetSkeleton)
+{
+ auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+ TestBuffer<char16_t> buffer;
+
+ DateTimePatternGenerator::GetSkeleton(MakeStringSpan(u"M/d/y"), buffer)
+ .unwrap();
+ ASSERT_TRUE(buffer.verboseMatches(u"yMd"));
+}
+
+TEST(IntlDateTimePatternGenerator, GetPlaceholderPattern)
+{
+ auto gen = DateTimePatternGenerator::TryCreate("en").unwrap();
+ auto span = gen->GetPlaceholderPattern();
+ // The default date-time pattern for 'en' locale is u"{1}, {0}".
+ ASSERT_EQ(span, MakeStringSpan(u"{1}, {0}"));
+}
+
+// A utility function to help test the DateTimeFormat::ComponentsBag.
+[[nodiscard]] bool FormatComponents(
+ TestBuffer<char16_t>& aBuffer, DateTimeFormat::ComponentsBag& aComponents,
+ Span<const char> aLocale = MakeStringSpan("en-US")) {
+ UniquePtr<DateTimePatternGenerator> gen = nullptr;
+ auto dateTimePatternGenerator =
+ DateTimePatternGenerator::TryCreate(aLocale.data()).unwrap();
+
+ auto dtFormat = DateTimeFormat::TryCreateFromComponents(
+ aLocale, aComponents, dateTimePatternGenerator.get(),
+ Some(MakeStringSpan(u"GMT+3")));
+ if (dtFormat.isErr()) {
+ fprintf(stderr, "Could not create a DateTimeFormat\n");
+ return false;
+ }
+
+ auto result = dtFormat.unwrap()->TryFormat(DATE, aBuffer);
+ if (result.isErr()) {
+ fprintf(stderr, "Could not format a DateTimeFormat\n");
+ return false;
+ }
+
+ return true;
+}
+
+TEST(IntlDateTimeFormat, Components)
+{
+ DateTimeFormat::ComponentsBag components{};
+
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::Numeric);
+ components.day = Some(DateTimeFormat::Numeric::Numeric);
+
+ components.hour = Some(DateTimeFormat::Numeric::Numeric);
+ components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ components.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+ TestBuffer<char16_t> buffer;
+ ASSERT_TRUE(FormatComponents(buffer, components));
+ ASSERT_TRUE(buffer.verboseMatches(u"9/23/2002, 8:07:30 PM"));
+}
+
+TEST(IntlDateTimeFormat, Components_es_ES)
+{
+ DateTimeFormat::ComponentsBag components{};
+
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::Numeric);
+ components.day = Some(DateTimeFormat::Numeric::Numeric);
+
+ components.hour = Some(DateTimeFormat::Numeric::Numeric);
+ components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ components.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+ TestBuffer<char16_t> buffer;
+ ASSERT_TRUE(FormatComponents(buffer, components, MakeStringSpan("es-ES")));
+ ASSERT_TRUE(buffer.verboseMatches(u"23/9/2002, 20:07:30"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsAll)
+{
+ // Use most all of the components.
+ DateTimeFormat::ComponentsBag components{};
+
+ components.era = Some(DateTimeFormat::Text::Short);
+
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::Numeric);
+ components.day = Some(DateTimeFormat::Numeric::Numeric);
+
+ components.weekday = Some(DateTimeFormat::Text::Short);
+
+ components.hour = Some(DateTimeFormat::Numeric::Numeric);
+ components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ components.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+ components.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
+ components.hourCycle = Some(DateTimeFormat::HourCycle::H24);
+ components.fractionalSecondDigits = Some(3);
+
+ TestBuffer<char16_t> buffer;
+ ASSERT_TRUE(FormatComponents(buffer, components));
+ ASSERT_TRUE(buffer.verboseMatches(u"Mon, 9 23, 2002 AD, 20:07:30.000 GMT+3"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsHour12Default)
+{
+ // Assert the behavior of the default "en-US" 12 hour time with day period.
+ DateTimeFormat::ComponentsBag components{};
+ components.hour = Some(DateTimeFormat::Numeric::Numeric);
+ components.minute = Some(DateTimeFormat::Numeric::Numeric);
+
+ TestBuffer<char16_t> buffer;
+ ASSERT_TRUE(FormatComponents(buffer, components));
+ ASSERT_TRUE(buffer.verboseMatches(u"8:07 PM"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsHour24)
+{
+ // Test the behavior of using 24 hour time to override the default of
+ // hour 12 with a day period.
+ DateTimeFormat::ComponentsBag components{};
+ components.hour = Some(DateTimeFormat::Numeric::Numeric);
+ components.minute = Some(DateTimeFormat::Numeric::Numeric);
+ components.hour12 = Some(false);
+
+ TestBuffer<char16_t> buffer;
+ ASSERT_TRUE(FormatComponents(buffer, components));
+ ASSERT_TRUE(buffer.verboseMatches(u"20:07"));
+}
+
+TEST(IntlDateTimeFormat, ComponentsHour12DayPeriod)
+{
+ // Test the behavior of specifying a specific day period.
+ DateTimeFormat::ComponentsBag components{};
+
+ components.hour = Some(DateTimeFormat::Numeric::Numeric);
+ components.minute = Some(DateTimeFormat::Numeric::Numeric);
+ components.dayPeriod = Some(DateTimeFormat::Text::Long);
+
+ TestBuffer<char16_t> buffer;
+ ASSERT_TRUE(FormatComponents(buffer, components));
+ ASSERT_TRUE(buffer.verboseMatches(u"8:07 in the evening"));
+}
+
+const char* ToString(uint8_t b) { return "uint8_t"; }
+const char* ToString(bool b) { return b ? "true" : "false"; }
+
+template <typename T>
+const char* ToString(Maybe<T> option) {
+ if (option) {
+ if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, uint8_t>) {
+ return ToString(*option);
+ } else {
+ return DateTimeFormat::ToString(*option);
+ }
+ }
+ return "Nothing";
+}
+
+template <typename T>
+[[nodiscard]] bool VerboseEquals(T expected, T actual, const char* msg) {
+ if (expected != actual) {
+ fprintf(stderr, "%s\n Actual: %s\nExpected: %s\n", msg, ToString(actual),
+ ToString(expected));
+ return false;
+ }
+ return true;
+}
+
+// A testing utility for getting nice errors when ComponentsBags don't match.
+[[nodiscard]] bool VerboseEquals(DateTimeFormat::ComponentsBag& expected,
+ DateTimeFormat::ComponentsBag& actual) {
+ // clang-format off
+ return
+ VerboseEquals(expected.era, actual.era, "Components do not match: bag.era") &&
+ VerboseEquals(expected.year, actual.year, "Components do not match: bag.year") &&
+ VerboseEquals(expected.month, actual.month, "Components do not match: bag.month") &&
+ VerboseEquals(expected.day, actual.day, "Components do not match: bag.day") &&
+ VerboseEquals(expected.weekday, actual.weekday, "Components do not match: bag.weekday") &&
+ VerboseEquals(expected.hour, actual.hour, "Components do not match: bag.hour") &&
+ VerboseEquals(expected.minute, actual.minute, "Components do not match: bag.minute") &&
+ VerboseEquals(expected.second, actual.second, "Components do not match: bag.second") &&
+ VerboseEquals(expected.timeZoneName, actual.timeZoneName, "Components do not match: bag.timeZoneName") &&
+ VerboseEquals(expected.hour12, actual.hour12, "Components do not match: bag.hour12") &&
+ VerboseEquals(expected.hourCycle, actual.hourCycle, "Components do not match: bag.hourCycle") &&
+ VerboseEquals(expected.dayPeriod, actual.dayPeriod, "Components do not match: bag.dayPeriod") &&
+ VerboseEquals(expected.fractionalSecondDigits, actual.fractionalSecondDigits, "Components do not match: bag.fractionalSecondDigits");
+ // clang-format on
+}
+
+// A utility function to help test the DateTimeFormat::ComponentsBag.
+[[nodiscard]] bool ResolveComponentsBag(
+ DateTimeFormat::ComponentsBag& aComponentsIn,
+ DateTimeFormat::ComponentsBag* aComponentsOut,
+ Span<const char> aLocale = MakeStringSpan("en-US")) {
+ UniquePtr<DateTimePatternGenerator> gen = nullptr;
+ auto dateTimePatternGenerator =
+ DateTimePatternGenerator::TryCreate("en").unwrap();
+ auto dtFormat = DateTimeFormat::TryCreateFromComponents(
+ aLocale, aComponentsIn, dateTimePatternGenerator.get(),
+ Some(MakeStringSpan(u"GMT+3")));
+ if (dtFormat.isErr()) {
+ fprintf(stderr, "Could not create a DateTimeFormat\n");
+ return false;
+ }
+
+ auto result = dtFormat.unwrap()->ResolveComponents();
+ if (result.isErr()) {
+ fprintf(stderr, "Could not resolve the components\n");
+ return false;
+ }
+
+ *aComponentsOut = result.unwrap();
+ return true;
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsDate)
+{
+ DateTimeFormat::ComponentsBag input{};
+ {
+ input.year = Some(DateTimeFormat::Numeric::Numeric);
+ input.month = Some(DateTimeFormat::Month::Numeric);
+ input.day = Some(DateTimeFormat::Numeric::Numeric);
+ }
+
+ DateTimeFormat::ComponentsBag expected = input;
+
+ DateTimeFormat::ComponentsBag resolved{};
+ ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+ ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsAll)
+{
+ DateTimeFormat::ComponentsBag input{};
+ {
+ input.era = Some(DateTimeFormat::Text::Short);
+
+ input.year = Some(DateTimeFormat::Numeric::Numeric);
+ input.month = Some(DateTimeFormat::Month::Numeric);
+ input.day = Some(DateTimeFormat::Numeric::Numeric);
+
+ input.weekday = Some(DateTimeFormat::Text::Short);
+
+ input.hour = Some(DateTimeFormat::Numeric::Numeric);
+ input.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ input.second = Some(DateTimeFormat::Numeric::TwoDigit);
+
+ input.timeZoneName = Some(DateTimeFormat::TimeZoneName::Short);
+ input.hourCycle = Some(DateTimeFormat::HourCycle::H24);
+ input.fractionalSecondDigits = Some(3);
+ }
+
+ DateTimeFormat::ComponentsBag expected = input;
+ {
+ expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
+ expected.hourCycle = Some(DateTimeFormat::HourCycle::H24);
+ expected.hour12 = Some(false);
+ }
+
+ DateTimeFormat::ComponentsBag resolved{};
+ ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+ ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsHourDayPeriod)
+{
+ DateTimeFormat::ComponentsBag input{};
+ {
+ input.hour = Some(DateTimeFormat::Numeric::Numeric);
+ input.minute = Some(DateTimeFormat::Numeric::Numeric);
+ }
+
+ DateTimeFormat::ComponentsBag expected = input;
+ {
+ expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ expected.hourCycle = Some(DateTimeFormat::HourCycle::H12);
+ expected.hour12 = Some(true);
+ }
+
+ DateTimeFormat::ComponentsBag resolved{};
+ ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+ ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, ResolvedComponentsHour12)
+{
+ DateTimeFormat::ComponentsBag input{};
+ {
+ input.hour = Some(DateTimeFormat::Numeric::Numeric);
+ input.minute = Some(DateTimeFormat::Numeric::Numeric);
+ input.hour12 = Some(false);
+ }
+
+ DateTimeFormat::ComponentsBag expected = input;
+ {
+ expected.hour = Some(DateTimeFormat::Numeric::TwoDigit);
+ expected.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ expected.hourCycle = Some(DateTimeFormat::HourCycle::H23);
+ expected.hour12 = Some(false);
+ }
+
+ DateTimeFormat::ComponentsBag resolved{};
+ ASSERT_TRUE(ResolveComponentsBag(input, &resolved));
+ ASSERT_TRUE(VerboseEquals(expected, resolved));
+}
+
+TEST(IntlDateTimeFormat, GetOriginalSkeleton)
+{
+ // Demonstrate that the original skeleton and the resolved skeleton can
+ // differ.
+ DateTimeFormat::ComponentsBag components{};
+ components.month = Some(DateTimeFormat::Month::Narrow);
+ components.day = Some(DateTimeFormat::Numeric::TwoDigit);
+
+ const char* locale = "zh-Hans-CN";
+ auto dateTimePatternGenerator =
+ DateTimePatternGenerator::TryCreate(locale).unwrap();
+
+ auto result = DateTimeFormat::TryCreateFromComponents(
+ MakeStringSpan(locale), components, dateTimePatternGenerator.get(),
+ Some(MakeStringSpan(u"GMT+3")));
+ ASSERT_TRUE(result.isOk());
+ auto dtFormat = result.unwrap();
+
+ TestBuffer<char16_t> originalSkeleton;
+ auto originalSkeletonResult = dtFormat->GetOriginalSkeleton(originalSkeleton);
+ ASSERT_TRUE(originalSkeletonResult.isOk());
+ ASSERT_TRUE(originalSkeleton.verboseMatches(u"MMMMMdd"));
+
+ TestBuffer<char16_t> pattern;
+ auto patternResult = dtFormat->GetPattern(pattern);
+ ASSERT_TRUE(patternResult.isOk());
+ ASSERT_TRUE(pattern.verboseMatches(u"M月dd日"));
+
+ TestBuffer<char16_t> resolvedSkeleton;
+ auto resolvedSkeletonResult = DateTimePatternGenerator::GetSkeleton(
+ Span(pattern.data(), pattern.length()), resolvedSkeleton);
+
+ ASSERT_TRUE(resolvedSkeletonResult.isOk());
+ ASSERT_TRUE(resolvedSkeleton.verboseMatches(u"Mdd"));
+}
+
+TEST(IntlDateTimeFormat, GetAvailableLocales)
+{
+ using namespace std::literals;
+
+ int32_t english = 0;
+ int32_t german = 0;
+ int32_t chinese = 0;
+
+ // Since this list is dependent on ICU, and may change between upgrades, only
+ // test a subset of the available locales.
+ for (const char* locale : DateTimeFormat::GetAvailableLocales()) {
+ if (locale == "en"sv) {
+ english++;
+ } else if (locale == "de"sv) {
+ german++;
+ } else if (locale == "zh"sv) {
+ chinese++;
+ }
+ }
+
+ // Each locale should be found exactly once.
+ ASSERT_EQ(english, 1);
+ ASSERT_EQ(german, 1);
+ ASSERT_EQ(chinese, 1);
+}
+
+TEST(IntlDateTimeFormat, TryFormatToParts)
+{
+ auto dateTimePatternGenerator =
+ DateTimePatternGenerator::TryCreate("en").unwrap();
+
+ DateTimeFormat::ComponentsBag components;
+ components.year = Some(DateTimeFormat::Numeric::Numeric);
+ components.month = Some(DateTimeFormat::Month::TwoDigit);
+ components.day = Some(DateTimeFormat::Numeric::TwoDigit);
+ components.hour = Some(DateTimeFormat::Numeric::TwoDigit);
+ components.minute = Some(DateTimeFormat::Numeric::TwoDigit);
+ components.hour12 = Some(false);
+
+ UniquePtr<DateTimeFormat> dtFormat =
+ DateTimeFormat::TryCreateFromComponents(
+ MakeStringSpan("en-US"), components, dateTimePatternGenerator.get(),
+ Some(MakeStringSpan(u"GMT")))
+ .unwrap();
+
+ TestBuffer<char16_t> buffer;
+ mozilla::intl::DateTimePartVector parts;
+ auto result = dtFormat->TryFormatToParts(DATE, buffer, parts);
+ ASSERT_TRUE(result.isOk());
+
+ std::u16string_view strView = buffer.get_string_view();
+ ASSERT_EQ(strView, u"09/23/2002, 17:07");
+
+ auto getSubStringView = [strView, &parts](size_t index) {
+ size_t pos = index == 0 ? 0 : parts[index - 1].mEndIndex;
+ size_t count = parts[index].mEndIndex - pos;
+ return strView.substr(pos, count);
+ };
+
+ ASSERT_EQ(parts[0].mType, DateTimePartType::Month);
+ ASSERT_EQ(getSubStringView(0), u"09");
+
+ ASSERT_EQ(parts[1].mType, DateTimePartType::Literal);
+ ASSERT_EQ(getSubStringView(1), u"/");
+
+ ASSERT_EQ(parts[2].mType, DateTimePartType::Day);
+ ASSERT_EQ(getSubStringView(2), u"23");
+
+ ASSERT_EQ(parts[3].mType, DateTimePartType::Literal);
+ ASSERT_EQ(getSubStringView(3), u"/");
+
+ ASSERT_EQ(parts[4].mType, DateTimePartType::Year);
+ ASSERT_EQ(getSubStringView(4), u"2002");
+
+ ASSERT_EQ(parts[5].mType, DateTimePartType::Literal);
+ ASSERT_EQ(getSubStringView(5), u", ");
+
+ ASSERT_EQ(parts[6].mType, DateTimePartType::Hour);
+ ASSERT_EQ(getSubStringView(6), u"17");
+
+ ASSERT_EQ(parts[7].mType, DateTimePartType::Literal);
+ ASSERT_EQ(getSubStringView(7), u":");
+
+ ASSERT_EQ(parts[8].mType, DateTimePartType::Minute);
+ ASSERT_EQ(getSubStringView(8), u"07");
+
+ ASSERT_EQ(parts.length(), 9u);
+}
+
+TEST(IntlDateTimeFormat, SetStartTimeIfGregorian)
+{
+ DateTimeFormat::StyleBag style{};
+ style.date = Some(DateTimeFormat::Style::Long);
+
+ auto timeZone = Some(MakeStringSpan(u"UTC"));
+
+ // Beginning of ECMAScript time.
+ constexpr double StartOfTime = -8.64e15;
+
+ // Gregorian change date defaults to October 15, 1582 in ICU. Test with a date
+ // before the default change date, in this case January 1, 1582.
+ constexpr double FirstJanuary1582 = -12244089600000.0;
+
+ // One year expressed in milliseconds.
+ constexpr double oneYear = (365 * 24 * 60 * 60) * 1000.0;
+
+ // Test with and without explicit calendar. The start time of the calendar can
+ // only be adjusted for the Gregorian and the ISO-8601 calendar.
+ for (const char* locale : {
+ "en-US",
+ "en-US-u-ca-gregory",
+ "en-US-u-ca-iso8601",
+ }) {
+ auto gen = DateTimePatternGenerator::TryCreate(locale).unwrap();
+
+ auto dtFormat = DateTimeFormat::TryCreateFromStyle(
+ MakeStringSpan(locale), style, gen.get(), timeZone)
+ .unwrap();
+
+ TestBuffer<char> buffer;
+
+ // Before the default Gregorian change date, so interpreted in the Julian
+ // calendar, which is December 22, 1581.
+ dtFormat->TryFormat(FirstJanuary1582, buffer).unwrap();
+ ASSERT_TRUE(buffer.verboseMatches("December 22, 1581"));
+
+ // After default Gregorian change date, so January 1, 1583.
+ dtFormat->TryFormat(FirstJanuary1582 + oneYear, buffer).unwrap();
+ ASSERT_TRUE(buffer.verboseMatches("January 1, 1583"));
+
+ // Adjust the start time to use a proleptic Gregorian calendar.
+ dtFormat->SetStartTimeIfGregorian(StartOfTime);
+
+ // Now interpreted in proleptic Gregorian calendar at January 1, 1582.
+ dtFormat->TryFormat(FirstJanuary1582, buffer).unwrap();
+ ASSERT_TRUE(buffer.verboseMatches("January 1, 1582"));
+
+ // Still January 1, 1583.
+ dtFormat->TryFormat(FirstJanuary1582 + oneYear, buffer).unwrap();
+ ASSERT_TRUE(buffer.verboseMatches("January 1, 1583"));
+ }
+}
+} // namespace mozilla::intl