fune/intl/components/gtest/TestDateTimeFormat.cpp

567 lines
19 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/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\u202FPM"));
}
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\u202FPM"));
}
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\u202FPM"));
}
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\u202FPM"));
}
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\u202FPM"));
}
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\u202FPM"));
}
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);
}
} // namespace mozilla::intl