fune/intl/components/gtest/TestDateTimeFormat.cpp
André Bargull 1919267c6d Bug 1693576 - Part 2: Add DateTimeFormat::GetAllowedHourCycles(). r=platform-i18n-reviewers,gregtatum
ICU's public API only provides a function to return the preferred hour cycle
(`udatpg_getDefaultHourCycle()`), whereas for `Intl.Locale` we want to be able
to determine all allowed hour cycles. So we have to do it the hard way and
directly read ICU resource bundles to get the data we need for this feature.

Differential Revision: https://phabricator.services.mozilla.com/D125571
2021-09-21 08:51:20 +00:00

502 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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/DateTimePatternGenerator.h"
#include "mozilla/Span.h"
#include "TestBuffer.h"
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, Skeleton_enUS_utf8_in)
{
UniquePtr<DateTimePatternGenerator> gen = nullptr;
auto dateTimePatternGenerator =
DateTimePatternGenerator::TryCreate("en").unwrap();
UniquePtr<DateTimeFormat> dtFormat =
DateTimeFormat::TryCreateFromSkeleton(
"en-US", MakeStringSpan("yMdhhmmss"), dateTimePatternGenerator.get(),
Nothing(), Some(MakeStringSpan("GMT+3")))
.unwrap();
TestBuffer<char> buffer;
dtFormat->TryFormat(DATE, buffer).unwrap();
ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 08:07:30 PM"));
}
TEST(IntlDateTimeFormat, Skeleton_enUS_utf16_in)
{
UniquePtr<DateTimePatternGenerator> gen = nullptr;
auto dateTimePatternGenerator =
DateTimePatternGenerator::TryCreate("en").unwrap();
UniquePtr<DateTimeFormat> dtFormat =
DateTimeFormat::TryCreateFromSkeleton(
"en-US", MakeStringSpan(u"yMdhhmmss"), dateTimePatternGenerator.get(),
Nothing(), Some(MakeStringSpan(u"GMT+3")))
.unwrap();
TestBuffer<char> buffer;
dtFormat->TryFormat(DATE, buffer).unwrap();
ASSERT_TRUE(buffer.verboseMatches("9/23/2002, 08: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"));
}
// A utility function to help test the DateTimeFormat::ComponentsBag.
[[nodiscard]] bool FormatComponents(TestBuffer<char16_t>& aBuffer,
DateTimeFormat::ComponentsBag& aComponents,
Span<const char> aLocale = "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, "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 = "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"));
}
} // namespace mozilla::intl