mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-10-26 14:57:54 +03:00
LibJS+LibLocale: Replace date-time formatting with ICU
This uses ICU for the Intl.DateTimeFormat `format` `formatToParts`, `formatRange`, and `formatRangeToParts`. This lets us remove most data from our date-time format generator. All that remains are time zone data and locale week info, which are relied upon still for other interfaces. So they will be removed in a future patch. Note: All of the changes to the test files in this patch are now aligned with other browsers. This includes: * Some very incorrect formatting of Japanese symbols. (Looking at the old results now, it's very obvious they were wrong.) * Old FIXMEs regarding range formatting not including the start/end date when only time fields were requested, but the dates differ. * Day period inconsistencies.
This commit is contained in:
parent
2f5cf8ac20
commit
273694d8de
Notes:
sideshowbarker
2024-07-17 04:01:41 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/273694d8de Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/145
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,8 @@
|
||||
set(TEST_SOURCES
|
||||
TestDateTimeFormat.cpp
|
||||
TestDisplayNames.cpp
|
||||
TestLocale.cpp
|
||||
)
|
||||
|
||||
foreach(source IN LISTS TEST_SOURCES)
|
||||
serenity_test("${source}" LibLocale LIBS LibLocale)
|
||||
|
||||
get_filename_component(target "${source}" NAME_WLE)
|
||||
endforeach()
|
||||
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibLocale/DateTimeFormat.h>
|
||||
|
||||
TEST_CASE(time_zone_name)
|
||||
{
|
||||
struct TestData {
|
||||
StringView locale;
|
||||
Locale::CalendarPatternStyle style;
|
||||
StringView time_zone;
|
||||
StringView expected_result;
|
||||
};
|
||||
|
||||
constexpr auto test_data = Array {
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "UTC"sv, "Coordinated Universal Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "UTC"sv, "UTC"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongGeneric, "UTC"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortGeneric, "UTC"sv, "GMT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "UTC"sv, "التوقيت العالمي المنسق"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "UTC"sv, "UTC"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongGeneric, "UTC"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortGeneric, "UTC"sv, "غرينتش"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "America/Los_Angeles"sv, "Pacific Standard Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "America/Los_Angeles"sv, "PST"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongGeneric, "America/Los_Angeles"sv, "Pacific Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortGeneric, "America/Los_Angeles"sv, "PT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "America/Los_Angeles"sv, "توقيت المحيط الهادي الرسمي"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "America/Los_Angeles"sv, "غرينتش-٨"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongGeneric, "America/Los_Angeles"sv, "توقيت المحيط الهادي"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortGeneric, "America/Los_Angeles"sv, "غرينتش-٨"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "America/Vancouver"sv, "Pacific Standard Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "America/Vancouver"sv, "PST"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongGeneric, "America/Vancouver"sv, "Pacific Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortGeneric, "America/Vancouver"sv, "PT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "America/Vancouver"sv, "توقيت المحيط الهادي الرسمي"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "America/Vancouver"sv, "غرينتش-٨"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongGeneric, "America/Vancouver"sv, "توقيت المحيط الهادي"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortGeneric, "America/Vancouver"sv, "غرينتش-٨"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "Europe/London"sv, "Greenwich Mean Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "Europe/London"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongGeneric, "Europe/London"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortGeneric, "Europe/London"sv, "GMT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "Europe/London"sv, "توقيت غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "Europe/London"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongGeneric, "Europe/London"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortGeneric, "Europe/London"sv, "غرينتش"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "Africa/Accra"sv, "Greenwich Mean Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "Africa/Accra"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongGeneric, "Africa/Accra"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortGeneric, "Africa/Accra"sv, "GMT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "Africa/Accra"sv, "توقيت غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "Africa/Accra"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongGeneric, "Africa/Accra"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortGeneric, "Africa/Accra"sv, "غرينتش"sv },
|
||||
};
|
||||
|
||||
constexpr auto jan_1_2022 = AK::UnixDateTime::from_seconds_since_epoch(1640995200); // Saturday, January 1, 2022 12:00:00 AM
|
||||
|
||||
for (auto const& test : test_data) {
|
||||
auto time_zone = Locale::format_time_zone(test.locale, test.time_zone, test.style, jan_1_2022);
|
||||
EXPECT_EQ(time_zone, test.expected_result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(time_zone_name_dst)
|
||||
{
|
||||
struct TestData {
|
||||
StringView locale;
|
||||
Locale::CalendarPatternStyle style;
|
||||
StringView time_zone;
|
||||
StringView expected_result;
|
||||
};
|
||||
|
||||
constexpr auto test_data = Array {
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "UTC"sv, "Coordinated Universal Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "UTC"sv, "UTC"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "UTC"sv, "التوقيت العالمي المنسق"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "UTC"sv, "UTC"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "America/Los_Angeles"sv, "Pacific Daylight Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "America/Los_Angeles"sv, "PDT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "America/Los_Angeles"sv, "توقيت المحيط الهادي الصيفي"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "America/Los_Angeles"sv, "غرينتش-٧"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "America/Vancouver"sv, "Pacific Daylight Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "America/Vancouver"sv, "PDT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "America/Vancouver"sv, "توقيت المحيط الهادي الصيفي"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "America/Vancouver"sv, "غرينتش-٧"sv },
|
||||
|
||||
// FIXME: This should be "British Summer Time", but the CLDR puts that one name in a section we aren't parsing.
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "Europe/London"sv, "GMT+01:00"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "Europe/London"sv, "GMT+1"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "Europe/London"sv, "غرينتش+٠١:٠٠"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "Europe/London"sv, "غرينتش+١"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Long, "Africa/Accra"sv, "Greenwich Mean Time"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::Short, "Africa/Accra"sv, "GMT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Long, "Africa/Accra"sv, "توقيت غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::Short, "Africa/Accra"sv, "غرينتش"sv },
|
||||
};
|
||||
|
||||
constexpr auto sep_19_2022 = AK::UnixDateTime::from_seconds_since_epoch(1663553728); // Monday, September 19, 2022 2:15:28 AM
|
||||
|
||||
for (auto const& test : test_data) {
|
||||
auto time_zone = Locale::format_time_zone(test.locale, test.time_zone, test.style, sep_19_2022);
|
||||
EXPECT_EQ(time_zone, test.expected_result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(format_time_zone_offset)
|
||||
{
|
||||
constexpr auto jan_1_1833 = AK::UnixDateTime::from_seconds_since_epoch(-4323283200); // Tuesday, January 1, 1833 12:00:00 AM
|
||||
constexpr auto jan_1_2022 = AK::UnixDateTime::from_seconds_since_epoch(1640995200); // Saturday, January 1, 2022 12:00:00 AM
|
||||
|
||||
struct TestData {
|
||||
StringView locale;
|
||||
Locale::CalendarPatternStyle style;
|
||||
AK::UnixDateTime time;
|
||||
StringView time_zone;
|
||||
StringView expected_result;
|
||||
};
|
||||
|
||||
constexpr auto test_data = Array {
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, {}, "UTC"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, {}, "UTC"sv, "GMT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, {}, "UTC"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, {}, "UTC"sv, "غرينتش"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_1833, "America/Los_Angeles"sv, "GMT-7:52:58"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_2022, "America/Los_Angeles"sv, "GMT-8"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_1833, "America/Los_Angeles"sv, "GMT-07:52:58"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_2022, "America/Los_Angeles"sv, "GMT-08:00"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_1833, "America/Los_Angeles"sv, "غرينتش-٧:٥٢:٥٨"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_2022, "America/Los_Angeles"sv, "غرينتش-٨"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_1833, "America/Los_Angeles"sv, "غرينتش-٠٧:٥٢:٥٨"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_2022, "America/Los_Angeles"sv, "غرينتش-٠٨:٠٠"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_1833, "Europe/London"sv, "GMT-0:01:15"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_2022, "Europe/London"sv, "GMT"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_1833, "Europe/London"sv, "GMT-00:01:15"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_2022, "Europe/London"sv, "GMT"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_1833, "Europe/London"sv, "غرينتش-٠:٠١:١٥"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_2022, "Europe/London"sv, "غرينتش"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_1833, "Europe/London"sv, "غرينتش-٠٠:٠١:١٥"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_2022, "Europe/London"sv, "غرينتش"sv },
|
||||
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_1833, "Asia/Kathmandu"sv, "GMT+5:41:16"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_2022, "Asia/Kathmandu"sv, "GMT+5:45"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_1833, "Asia/Kathmandu"sv, "GMT+05:41:16"sv },
|
||||
TestData { "en"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_2022, "Asia/Kathmandu"sv, "GMT+05:45"sv },
|
||||
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_1833, "Asia/Kathmandu"sv, "غرينتش+٥:٤١:١٦"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::ShortOffset, jan_1_2022, "Asia/Kathmandu"sv, "غرينتش+٥:٤٥"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_1833, "Asia/Kathmandu"sv, "غرينتش+٠٥:٤١:١٦"sv },
|
||||
TestData { "ar"sv, Locale::CalendarPatternStyle::LongOffset, jan_1_2022, "Asia/Kathmandu"sv, "غرينتش+٠٥:٤٥"sv },
|
||||
};
|
||||
|
||||
for (auto const& test : test_data) {
|
||||
auto time_zone = Locale::format_time_zone(test.locale, test.time_zone, test.style, test.time);
|
||||
EXPECT_EQ(time_zone, test.expected_result);
|
||||
}
|
||||
}
|
@ -736,15 +736,13 @@ ErrorOr<void> print_intl_date_time_format(JS::PrintContext& print_context, JS::I
|
||||
TRY(print_type(print_context, "Intl.DateTimeFormat"sv));
|
||||
TRY(js_out(print_context, "\n locale: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), date_time_format.locale()), seen_objects));
|
||||
TRY(js_out(print_context, "\n pattern: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), date_time_format.pattern()), seen_objects));
|
||||
TRY(js_out(print_context, "\n calendar: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), date_time_format.calendar()), seen_objects));
|
||||
TRY(js_out(print_context, "\n numberingSystem: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), date_time_format.numbering_system()), seen_objects));
|
||||
if (date_time_format.has_hour_cycle()) {
|
||||
if (date_time_format.hour_cycle.has_value()) {
|
||||
TRY(js_out(print_context, "\n hourCycle: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), date_time_format.hour_cycle_string()), seen_objects));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), ::Locale::hour_cycle_to_string(*date_time_format.hour_cycle)), seen_objects));
|
||||
}
|
||||
TRY(js_out(print_context, "\n timeZone: "));
|
||||
TRY(print_value(print_context, JS::PrimitiveString::create(date_time_format.vm(), date_time_format.time_zone()), seen_objects));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@ -9,12 +9,11 @@
|
||||
#include <AK/Array.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Time.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibLocale/DateTimeFormat.h>
|
||||
|
||||
namespace JS::Intl {
|
||||
@ -28,13 +27,6 @@ class DateTimeFormat final
|
||||
using Patterns = ::Locale::CalendarPattern;
|
||||
|
||||
public:
|
||||
enum class Style {
|
||||
Full,
|
||||
Long,
|
||||
Medium,
|
||||
Short,
|
||||
};
|
||||
|
||||
static constexpr auto relevant_extension_keys()
|
||||
{
|
||||
// 11.2.3 Internal slots, https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots
|
||||
@ -56,130 +48,51 @@ public:
|
||||
String const& numbering_system() const { return m_numbering_system; }
|
||||
void set_numbering_system(String numbering_system) { m_numbering_system = move(numbering_system); }
|
||||
|
||||
bool has_hour_cycle() const { return m_hour_cycle.has_value(); }
|
||||
::Locale::HourCycle hour_cycle() const { return *m_hour_cycle; }
|
||||
StringView hour_cycle_string() const { return ::Locale::hour_cycle_to_string(*m_hour_cycle); }
|
||||
void set_hour_cycle(::Locale::HourCycle hour_cycle) { m_hour_cycle = hour_cycle; }
|
||||
void clear_hour_cycle() { m_hour_cycle.clear(); }
|
||||
|
||||
String const& time_zone() const { return m_time_zone; }
|
||||
void set_time_zone(String time_zone) { m_time_zone = move(time_zone); }
|
||||
|
||||
bool has_date_style() const { return m_date_style.has_value(); }
|
||||
Style date_style() const { return *m_date_style; }
|
||||
StringView date_style_string() const { return style_to_string(*m_date_style); }
|
||||
void set_date_style(StringView style) { m_date_style = style_from_string(style); }
|
||||
Optional<::Locale::DateTimeStyle> const& date_style() const { return m_date_style; }
|
||||
StringView date_style_string() const { return ::Locale::date_time_style_to_string(*m_date_style); }
|
||||
void set_date_style(StringView style) { m_date_style = ::Locale::date_time_style_from_string(style); }
|
||||
|
||||
bool has_time_style() const { return m_time_style.has_value(); }
|
||||
Style time_style() const { return *m_time_style; }
|
||||
StringView time_style_string() const { return style_to_string(*m_time_style); }
|
||||
void set_time_style(StringView style) { m_time_style = style_from_string(style); }
|
||||
|
||||
String const& pattern() const { return Patterns::pattern; }
|
||||
void set_pattern(String pattern) { Patterns::pattern = move(pattern); }
|
||||
|
||||
ReadonlySpan<::Locale::CalendarRangePattern> range_patterns() const { return m_range_patterns.span(); }
|
||||
void set_range_patterns(Vector<::Locale::CalendarRangePattern> range_patterns) { m_range_patterns = move(range_patterns); }
|
||||
|
||||
bool has_era() const { return Patterns::era.has_value(); }
|
||||
::Locale::CalendarPatternStyle era() const { return *Patterns::era; }
|
||||
StringView era_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::era); }
|
||||
|
||||
bool has_year() const { return Patterns::year.has_value(); }
|
||||
::Locale::CalendarPatternStyle year() const { return *Patterns::year; }
|
||||
StringView year_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::year); }
|
||||
|
||||
bool has_month() const { return Patterns::month.has_value(); }
|
||||
::Locale::CalendarPatternStyle month() const { return *Patterns::month; }
|
||||
StringView month_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::month); }
|
||||
|
||||
bool has_weekday() const { return Patterns::weekday.has_value(); }
|
||||
::Locale::CalendarPatternStyle weekday() const { return *Patterns::weekday; }
|
||||
StringView weekday_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::weekday); }
|
||||
|
||||
bool has_day() const { return Patterns::day.has_value(); }
|
||||
::Locale::CalendarPatternStyle day() const { return *Patterns::day; }
|
||||
StringView day_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::day); }
|
||||
|
||||
bool has_day_period() const { return Patterns::day_period.has_value(); }
|
||||
::Locale::CalendarPatternStyle day_period() const { return *Patterns::day_period; }
|
||||
StringView day_period_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::day_period); }
|
||||
|
||||
bool has_hour() const { return Patterns::hour.has_value(); }
|
||||
::Locale::CalendarPatternStyle hour() const { return *Patterns::hour; }
|
||||
StringView hour_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::hour); }
|
||||
|
||||
bool has_minute() const { return Patterns::minute.has_value(); }
|
||||
::Locale::CalendarPatternStyle minute() const { return *Patterns::minute; }
|
||||
StringView minute_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::minute); }
|
||||
|
||||
bool has_second() const { return Patterns::second.has_value(); }
|
||||
::Locale::CalendarPatternStyle second() const { return *Patterns::second; }
|
||||
StringView second_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::second); }
|
||||
|
||||
bool has_fractional_second_digits() const { return Patterns::fractional_second_digits.has_value(); }
|
||||
u8 fractional_second_digits() const { return *Patterns::fractional_second_digits; }
|
||||
|
||||
bool has_time_zone_name() const { return Patterns::time_zone_name.has_value(); }
|
||||
::Locale::CalendarPatternStyle time_zone_name() const { return *Patterns::time_zone_name; }
|
||||
StringView time_zone_name_string() const { return ::Locale::calendar_pattern_style_to_string(*Patterns::time_zone_name); }
|
||||
Optional<::Locale::DateTimeStyle> const& time_style() const { return m_time_style; }
|
||||
StringView time_style_string() const { return ::Locale::date_time_style_to_string(*m_time_style); }
|
||||
void set_time_style(StringView style) { m_time_style = ::Locale::date_time_style_from_string(style); }
|
||||
|
||||
NativeFunction* bound_format() const { return m_bound_format; }
|
||||
void set_bound_format(NativeFunction* bound_format) { m_bound_format = bound_format; }
|
||||
|
||||
::Locale::DateTimeFormat const& formatter() const { return *m_formatter; }
|
||||
void set_formatter(NonnullOwnPtr<::Locale::DateTimeFormat> formatter) { m_formatter = move(formatter); }
|
||||
|
||||
private:
|
||||
DateTimeFormat(Object& prototype);
|
||||
|
||||
static Style style_from_string(StringView style);
|
||||
static StringView style_to_string(Style style);
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
String m_locale; // [[Locale]]
|
||||
String m_calendar; // [[Calendar]]
|
||||
String m_numbering_system; // [[NumberingSystem]]
|
||||
Optional<::Locale::HourCycle> m_hour_cycle; // [[HourCycle]]
|
||||
String m_time_zone; // [[TimeZone]]
|
||||
Optional<Style> m_date_style; // [[DateStyle]]
|
||||
Optional<Style> m_time_style; // [[TimeStyle]]
|
||||
Vector<::Locale::CalendarRangePattern> m_range_patterns; // [[RangePatterns]]
|
||||
GCPtr<NativeFunction> m_bound_format; // [[BoundFormat]]
|
||||
String m_locale; // [[Locale]]
|
||||
String m_calendar; // [[Calendar]]
|
||||
String m_numbering_system; // [[NumberingSystem]]
|
||||
String m_time_zone; // [[TimeZone]]
|
||||
Optional<::Locale::DateTimeStyle> m_date_style; // [[DateStyle]]
|
||||
Optional<::Locale::DateTimeStyle> m_time_style; // [[TimeStyle]]
|
||||
GCPtr<NativeFunction> m_bound_format; // [[BoundFormat]]
|
||||
|
||||
String m_data_locale;
|
||||
|
||||
// Non-standard. Stores the ICU date-time formatter for the Intl object's formatting options.
|
||||
OwnPtr<::Locale::DateTimeFormat> m_formatter;
|
||||
};
|
||||
|
||||
// Table 8: Record returned by ToLocalTime, https://tc39.es/ecma402/#table-datetimeformat-tolocaltime-record
|
||||
// Note: [[InDST]] is not included here - it is handled by LibUnicode / LibTimeZone.
|
||||
struct LocalTime {
|
||||
AK::UnixDateTime time_since_epoch() const
|
||||
{
|
||||
return AK::UnixDateTime::from_unix_time_parts(year, month + 1, day + 1, hour, minute, second, millisecond);
|
||||
}
|
||||
|
||||
int weekday { 0 }; // [[Weekday]]
|
||||
::Locale::Era era {}; // [[Era]]
|
||||
i32 year { 0 }; // [[Year]]
|
||||
Value related_year {}; // [[RelatedYear]]
|
||||
Value year_name {}; // [[YearName]]
|
||||
u8 month { 0 }; // [[Month]]
|
||||
u8 day { 0 }; // [[Day]]
|
||||
u8 hour { 0 }; // [[Hour]]
|
||||
u8 minute { 0 }; // [[Minute]]
|
||||
u8 second { 0 }; // [[Second]]
|
||||
u16 millisecond { 0 }; // [[Millisecond]]
|
||||
};
|
||||
|
||||
Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_locale, DateTimeFormat& date_time_format);
|
||||
Optional<::Locale::CalendarPattern> basic_format_matcher(::Locale::CalendarPattern const& options, Vector<::Locale::CalendarPattern> formats);
|
||||
Optional<::Locale::CalendarPattern> best_fit_format_matcher(::Locale::CalendarPattern const& options, Vector<::Locale::CalendarPattern> formats);
|
||||
ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(VM&, DateTimeFormat&, Vector<PatternPartition> pattern_parts, double time, ::Locale::CalendarPattern const* range_format_options);
|
||||
ThrowCompletionOr<Vector<PatternPartition>> partition_date_time_pattern(VM&, DateTimeFormat&, double time);
|
||||
ThrowCompletionOr<Vector<::Locale::DateTimeFormat::Partition>> format_date_time_pattern(VM&, DateTimeFormat&, double time);
|
||||
ThrowCompletionOr<Vector<::Locale::DateTimeFormat::Partition>> partition_date_time_pattern(VM&, DateTimeFormat&, double time);
|
||||
ThrowCompletionOr<String> format_date_time(VM&, DateTimeFormat&, double time);
|
||||
ThrowCompletionOr<NonnullGCPtr<Array>> format_date_time_to_parts(VM&, DateTimeFormat&, double time);
|
||||
ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_pattern(VM&, DateTimeFormat&, double start, double end);
|
||||
ThrowCompletionOr<Vector<::Locale::DateTimeFormat::Partition>> partition_date_time_range_pattern(VM&, DateTimeFormat&, double start, double end);
|
||||
ThrowCompletionOr<String> format_date_time_range(VM&, DateTimeFormat&, double start, double end);
|
||||
ThrowCompletionOr<NonnullGCPtr<Array>> format_date_time_range_to_parts(VM&, DateTimeFormat&, double start, double end);
|
||||
ThrowCompletionOr<LocalTime> to_local_time(VM&, Crypto::SignedBigInteger const& epoch_ns, StringView calendar, StringView time_zone);
|
||||
|
||||
template<typename Callback>
|
||||
ThrowCompletionOr<void> for_each_calendar_field(VM& vm, ::Locale::CalendarPattern& pattern, Callback&& callback)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@ -168,30 +168,15 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
|
||||
// 24. Let dataLocaleData be localeData.[[<dataLocale>]].
|
||||
Optional<::Locale::HourCycle> hour_cycle_value;
|
||||
|
||||
auto set_locale_hour_cycle = [&](auto... candidate_hour_cycles) {
|
||||
// Locale hour cycles (parsed from timeData.json) are stored in preference order. Use the
|
||||
// first hour cycle that matches any of the provided candidates. There may be no matches if
|
||||
// e.g. Unicode data generation is disabled.
|
||||
auto locale_hour_cycles = ::Locale::get_locale_hour_cycles(data_locale);
|
||||
|
||||
auto index = locale_hour_cycles.find_first_index_if([&](auto hour_cycle) {
|
||||
return ((hour_cycle == candidate_hour_cycles) || ...);
|
||||
});
|
||||
|
||||
if (index.has_value())
|
||||
hour_cycle_value = locale_hour_cycles[*index];
|
||||
};
|
||||
Optional<bool> hour12_value;
|
||||
|
||||
// 25. If hour12 is true, then
|
||||
if (hour12.is_boolean() && hour12.as_bool()) {
|
||||
// a. Let hc be dataLocaleData.[[hourCycle12]].
|
||||
set_locale_hour_cycle(::Locale::HourCycle::H11, ::Locale::HourCycle::H12);
|
||||
}
|
||||
// a. Let hc be dataLocaleData.[[hourCycle12]].
|
||||
// 26. Else if hour12 is false, then
|
||||
else if (hour12.is_boolean() && !hour12.as_bool()) {
|
||||
// a. Let hc be dataLocaleData.[[hourCycle24]].
|
||||
set_locale_hour_cycle(::Locale::HourCycle::H23, ::Locale::HourCycle::H24);
|
||||
// a. Let hc be dataLocaleData.[[hourCycle24]].
|
||||
if (hour12.is_boolean()) {
|
||||
// NOTE: We let LibLocale figure out the appropriate hour cycle.
|
||||
hour12_value = hour12.as_bool();
|
||||
}
|
||||
// 27. Else,
|
||||
else {
|
||||
@ -208,8 +193,7 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
}
|
||||
|
||||
// 28. Set dateTimeFormat.[[HourCycle]] to hc.
|
||||
if (hour_cycle_value.has_value())
|
||||
date_time_format->set_hour_cycle(*hour_cycle_value);
|
||||
date_time_format->hour_cycle = hour_cycle_value;
|
||||
|
||||
// 29. Let timeZone be ? Get(options, "timeZone").
|
||||
auto time_zone_value = TRY(options->get(vm.names.timeZone));
|
||||
@ -227,7 +211,9 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
}
|
||||
|
||||
// 32. If IsTimeZoneOffsetString(timeZone) is true, then
|
||||
if (is_time_zone_offset_string(time_zone)) {
|
||||
bool is_time_zone_offset_string = JS::is_time_zone_offset_string(time_zone);
|
||||
|
||||
if (is_time_zone_offset_string) {
|
||||
// a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset).
|
||||
auto parse_result = Temporal::parse_iso8601(Temporal::Production::TimeZoneNumericUTCOffset, time_zone);
|
||||
|
||||
@ -262,13 +248,18 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
}
|
||||
|
||||
// 35. Set dateTimeFormat.[[TimeZone]] to timeZone.
|
||||
date_time_format->set_time_zone(move(time_zone));
|
||||
date_time_format->set_time_zone(time_zone);
|
||||
|
||||
// NOTE: ICU requires time zone offset strings to be of the form "GMT+00:00"
|
||||
if (is_time_zone_offset_string)
|
||||
time_zone = MUST(String::formatted("GMT{}", time_zone));
|
||||
|
||||
// 36. Let formatOptions be a new Record.
|
||||
::Locale::CalendarPattern format_options {};
|
||||
|
||||
// 37. Set formatOptions.[[hourCycle]] to hc.
|
||||
format_options.hour_cycle = hour_cycle_value;
|
||||
format_options.hour12 = hour12_value;
|
||||
|
||||
// 38. Let hasExplicitFormatComponents be false.
|
||||
// NOTE: Instead of using a boolean, we track any explicitly provided component name for nicer exception messages.
|
||||
@ -330,8 +321,6 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
if (!time_style.is_undefined())
|
||||
date_time_format->set_time_style(time_style.as_string().utf8_string_view());
|
||||
|
||||
Optional<::Locale::CalendarPattern> best_format {};
|
||||
|
||||
// 45. If dateStyle is not undefined or timeStyle is not undefined, then
|
||||
if (date_time_format->has_date_style() || date_time_format->has_time_style()) {
|
||||
// a. If hasExplicitFormatComponents is true, then
|
||||
@ -354,7 +343,14 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
|
||||
// d. Let styles be dataLocaleData.[[styles]].[[<resolvedCalendar>]].
|
||||
// e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles).
|
||||
best_format = date_time_style_format(data_locale, date_time_format);
|
||||
auto formatter = ::Locale::DateTimeFormat::create_for_date_and_time_style(
|
||||
date_time_format->data_locale(),
|
||||
time_zone,
|
||||
format_options.hour_cycle,
|
||||
format_options.hour12,
|
||||
date_time_format->date_style(),
|
||||
date_time_format->time_style());
|
||||
date_time_format->set_formatter(move(formatter));
|
||||
}
|
||||
// 46. Else,
|
||||
else {
|
||||
@ -421,22 +417,19 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
}
|
||||
|
||||
// f. Let formats be dataLocaleData.[[formats]].[[<resolvedCalendar>]].
|
||||
auto formats = ::Locale::get_calendar_available_formats(data_locale, date_time_format->calendar());
|
||||
|
||||
// g. If matcher is "basic", then
|
||||
if (matcher.as_string().utf8_string_view() == "basic"sv) {
|
||||
// i. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
|
||||
best_format = basic_format_matcher(format_options, move(formats));
|
||||
}
|
||||
// i. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
|
||||
// h. Else,
|
||||
else {
|
||||
// i. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
|
||||
best_format = best_fit_format_matcher(format_options, move(formats));
|
||||
}
|
||||
// i. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
|
||||
auto formatter = ::Locale::DateTimeFormat::create_for_pattern_options(
|
||||
date_time_format->data_locale(),
|
||||
time_zone,
|
||||
format_options);
|
||||
date_time_format->set_formatter(move(formatter));
|
||||
}
|
||||
|
||||
// 47. For each row in Table 6, except the header row, in table order, do
|
||||
date_time_format->for_each_calendar_field_zipped_with(*best_format, [&](auto& date_time_format_field, auto const& best_format_field, auto) {
|
||||
date_time_format->for_each_calendar_field_zipped_with(date_time_format->formatter().chosen_pattern(), [&](auto& date_time_format_field, auto const& best_format_field) {
|
||||
// a. Let prop be the name given in the Property column of the row.
|
||||
// b. If bestFormat has a field [[<prop>]], then
|
||||
if (best_format_field.has_value()) {
|
||||
@ -446,43 +439,20 @@ ThrowCompletionOr<NonnullGCPtr<DateTimeFormat>> create_date_time_format(VM& vm,
|
||||
}
|
||||
});
|
||||
|
||||
String pattern;
|
||||
Vector<::Locale::CalendarRangePattern> range_patterns;
|
||||
|
||||
// 48. If dateTimeFormat.[[Hour]] is undefined, then
|
||||
if (!date_time_format->has_hour()) {
|
||||
if (!date_time_format->hour.has_value()) {
|
||||
// a. Set dateTimeFormat.[[HourCycle]] to undefined.
|
||||
date_time_format->clear_hour_cycle();
|
||||
date_time_format->hour_cycle.clear();
|
||||
}
|
||||
|
||||
// 49. If dateTimeFormat.[[HourCycle]] is "h11" or "h12", then
|
||||
if ((hour_cycle_value == ::Locale::HourCycle::H11) || (hour_cycle_value == ::Locale::HourCycle::H12)) {
|
||||
// a. Let pattern be bestFormat.[[pattern12]].
|
||||
if (best_format->pattern12.has_value()) {
|
||||
pattern = best_format->pattern12.release_value();
|
||||
} else {
|
||||
// Non-standard, LibUnicode only provides [[pattern12]] when [[pattern]] has a day
|
||||
// period. Other implementations provide [[pattern12]] as a copy of [[pattern]].
|
||||
pattern = move(best_format->pattern);
|
||||
}
|
||||
|
||||
// b. Let rangePatterns be bestFormat.[[rangePatterns12]].
|
||||
range_patterns = ::Locale::get_calendar_range12_formats(data_locale, date_time_format->calendar(), best_format->skeleton);
|
||||
}
|
||||
// a. Let pattern be bestFormat.[[pattern12]].
|
||||
// b. Let rangePatterns be bestFormat.[[rangePatterns12]].
|
||||
// 50. Else,
|
||||
else {
|
||||
// a. Let pattern be bestFormat.[[pattern]].
|
||||
pattern = move(best_format->pattern);
|
||||
|
||||
// b. Let rangePatterns be bestFormat.[[rangePatterns]].
|
||||
range_patterns = ::Locale::get_calendar_range_formats(data_locale, date_time_format->calendar(), best_format->skeleton);
|
||||
}
|
||||
|
||||
// a. Let pattern be bestFormat.[[pattern]].
|
||||
// b. Let rangePatterns be bestFormat.[[rangePatterns]].
|
||||
// 51. Set dateTimeFormat.[[Pattern]] to pattern.
|
||||
date_time_format->set_pattern(move(pattern));
|
||||
|
||||
// 52. Set dateTimeFormat.[[RangePatterns]] to rangePatterns.
|
||||
date_time_format->set_range_patterns(move(range_patterns));
|
||||
|
||||
// 53. Return dateTimeFormat.
|
||||
return date_time_format;
|
||||
|
@ -179,10 +179,10 @@ JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatPrototype::resolved_options)
|
||||
MUST(options->create_data_property_or_throw(vm.names.numberingSystem, PrimitiveString::create(vm, date_time_format->numbering_system())));
|
||||
MUST(options->create_data_property_or_throw(vm.names.timeZone, PrimitiveString::create(vm, date_time_format->time_zone())));
|
||||
|
||||
if (date_time_format->has_hour_cycle()) {
|
||||
MUST(options->create_data_property_or_throw(vm.names.hourCycle, PrimitiveString::create(vm, date_time_format->hour_cycle_string())));
|
||||
if (date_time_format->hour_cycle.has_value()) {
|
||||
MUST(options->create_data_property_or_throw(vm.names.hourCycle, PrimitiveString::create(vm, ::Locale::hour_cycle_to_string(*date_time_format->hour_cycle))));
|
||||
|
||||
switch (date_time_format->hour_cycle()) {
|
||||
switch (*date_time_format->hour_cycle) {
|
||||
case ::Locale::HourCycle::H11:
|
||||
case ::Locale::HourCycle::H12:
|
||||
MUST(options->create_data_property_or_throw(vm.names.hour12, Value(true)));
|
||||
|
@ -145,9 +145,9 @@ describe("weekday", () => {
|
||||
describe("era", () => {
|
||||
// prettier-ignore
|
||||
const data = [
|
||||
{ era: "narrow", en0: "12/7/2021 A", en1: "1/23/1989 A", ar0: "٧ ١٢ ٢٠٢١ م", ar1: "٢٣ ١ ١٩٨٩ م" },
|
||||
{ era: "short", en0: "12/7/2021 AD", en1: "1/23/1989 AD", ar0: "٧ ١٢ ٢٠٢١ م", ar1: "٢٣ ١ ١٩٨٩ م" },
|
||||
{ era: "long", en0: "12/7/2021 Anno Domini", en1: "1/23/1989 Anno Domini", ar0: "٧ ١٢ ٢٠٢١ ميلادي", ar1: "٢٣ ١ ١٩٨٩ ميلادي" },
|
||||
{ era: "narrow", en0: "12/7/2021 A", en1: "1/23/1989 A", ar0: "٠٧-١٢-٢٠٢١ م", ar1: "٢٣-٠١-١٩٨٩ م" },
|
||||
{ era: "short", en0: "12/7/2021 AD", en1: "1/23/1989 AD", ar0: "٠٧-١٢-٢٠٢١ م", ar1: "٢٣-٠١-١٩٨٩ م" },
|
||||
{ era: "long", en0: "12/7/2021 Anno Domini", en1: "1/23/1989 Anno Domini", ar0: "٠٧-١٢-٢٠٢١ ميلادي", ar1: "٢٣-٠١-١٩٨٩ ميلادي" },
|
||||
];
|
||||
|
||||
test("all", () => {
|
||||
@ -170,7 +170,7 @@ describe("era", () => {
|
||||
expect(en.format(year1BC)).toBe("1/1/1 BC");
|
||||
|
||||
const ar = new Intl.DateTimeFormat("ar", { era: "short", timeZone: "UTC" });
|
||||
expect(ar.format(year1BC)).toBe("١ ١ ١ ق.م");
|
||||
expect(ar.format(year1BC)).toBe("٠١-٠١-١ ق.م");
|
||||
});
|
||||
});
|
||||
|
||||
@ -300,7 +300,7 @@ describe("dayPeriod", () => {
|
||||
{ minute: undefined, second: undefined, fractionalSecondDigits: undefined, en0: "12 noon", en1: "12 noon", en2: "12 noon", en3: "12 noon", ar0: "١٢ ظهرًا", ar1: "١٢ ظهرًا", ar2: "١٢ ظهرًا", ar3: "١٢ ظهرًا" },
|
||||
{ minute: "numeric", second: undefined, fractionalSecondDigits: undefined, en0: "12:00 noon", en1: "12:01 in the afternoon", en2: "12:00 noon", en3: "12:00 noon", ar0: "١٢:٠٠ ظهرًا", ar1: "١٢:٠١ ظهرًا", ar2: "١٢:٠٠ ظهرًا", ar3: "١٢:٠٠ ظهرًا" },
|
||||
{ minute: "numeric", second: "numeric", fractionalSecondDigits: undefined, en0: "12:00:00 noon", en1: "12:01:00 in the afternoon", en2: "12:00:01 in the afternoon", en3: "12:00:00 noon", ar0: "١٢:٠٠:٠٠ ظهرًا", ar1: "١٢:٠١:٠٠ ظهرًا", ar2: "١٢:٠٠:٠١ ظهرًا", ar3: "١٢:٠٠:٠٠ ظهرًا" },
|
||||
{ minute: "numeric", second: "numeric", fractionalSecondDigits: 1, en0: "12:00:00.0 noon", en1: "12:01:00.0 in the afternoon", en2: "12:00:01.0 in the afternoon", en3: "12:00:00.5 in the afternoon", ar0: "١٢:٠٠:٠٠٫٠ ظهرًا", ar1: "١٢:٠١:٠٠٫٠ ظهرًا", ar2: "١٢:٠٠:٠١٫٠ ظهرًا", ar3: "١٢:٠٠:٠٠٫٥ ظهرًا" },
|
||||
{ minute: "numeric", second: "numeric", fractionalSecondDigits: 1, en0: "12:00:00.0 noon", en1: "12:01:00.0 in the afternoon", en2: "12:00:01.0 in the afternoon", en3: "12:00:00.5 noon", ar0: "١٢:٠٠:٠٠٫٠ ظهرًا", ar1: "١٢:٠١:٠٠٫٠ ظهرًا", ar2: "١٢:٠٠:٠١٫٠ ظهرًا", ar3: "١٢:٠٠:٠٠٫٥ ظهرًا" },
|
||||
];
|
||||
|
||||
// The en locale includes the "noon" fixed day period, whereas the ar locale does not.
|
||||
@ -368,10 +368,8 @@ describe("dayPeriod", () => {
|
||||
|
||||
describe("hour", () => {
|
||||
// prettier-ignore
|
||||
// FIXME: The 2-digit results are supposed to include {ampm}. These results are achieved from the "HH"
|
||||
// pattern, which should only be applied to 24-hour cycles.
|
||||
const data = [
|
||||
{ hour: "2-digit", en0: "05", en1: "07", ar0: "٠٥", ar1: "٠٧" },
|
||||
{ hour: "2-digit", en0: "05\u202fPM", en1: "07\u202fAM", ar0: "٠٥ م", ar1: "٠٧ ص" },
|
||||
{ hour: "numeric", en0: "5\u202fPM", en1: "7\u202fAM", ar0: "٥ م", ar1: "٧ ص" },
|
||||
];
|
||||
|
||||
@ -478,47 +476,47 @@ describe("fractionalSecondDigits", () => {
|
||||
describe("timeZoneName", () => {
|
||||
// prettier-ignore
|
||||
const data = [
|
||||
{ timeZone: "UTC", timeZoneName: "short", en0: "12/7/2021, 5:40\u202fPM UTC", en1: "1/23/1989, 7:08\u202fAM UTC", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م UTC", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص UTC" },
|
||||
{ timeZone: "UTC", timeZoneName: "long", en0: "12/7/2021, 5:40\u202fPM Coordinated Universal Time", en1: "1/23/1989, 7:08\u202fAM Coordinated Universal Time", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م التوقيت العالمي المنسق", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص التوقيت العالمي المنسق" },
|
||||
{ timeZone: "UTC", timeZoneName: "shortOffset", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "longOffset", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "shortGeneric", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "longGeneric", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "short", en0: "12/7/2021, UTC", en1: "1/23/1989, UTC", ar0: "٧/١٢/٢٠٢١، UTC", ar1: "٢٣/١/١٩٨٩، UTC" },
|
||||
{ timeZone: "UTC", timeZoneName: "long", en0: "12/7/2021, Coordinated Universal Time", en1: "1/23/1989, Coordinated Universal Time", ar0: "٧/١٢/٢٠٢١، التوقيت العالمي المنسق", ar1: "٢٣/١/١٩٨٩، التوقيت العالمي المنسق" },
|
||||
{ timeZone: "UTC", timeZoneName: "shortOffset", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "longOffset", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "shortGeneric", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
{ timeZone: "UTC", timeZoneName: "longGeneric", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
|
||||
{ timeZone: "America/New_York", timeZoneName: "short", en0: "12/7/2021, 12:40\u202fPM EST", en1: "1/23/1989, 2:08\u202fAM EST", ar0: "٧/١٢/٢٠٢١، ١٢:٤٠ م غرينتش-٥", ar1: "٢٣/١/١٩٨٩، ٢:٠٨ ص غرينتش-٥" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "long", en0: "12/7/2021, 12:40\u202fPM Eastern Standard Time", en1: "1/23/1989, 2:08\u202fAM Eastern Standard Time", ar0: "٧/١٢/٢٠٢١، ١٢:٤٠ م التوقيت الرسمي الشرقي لأمريكا الشمالية", ar1: "٢٣/١/١٩٨٩، ٢:٠٨ ص التوقيت الرسمي الشرقي لأمريكا الشمالية" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "shortOffset", en0: "12/7/2021, 12:40\u202fPM GMT-5", en1: "1/23/1989, 2:08\u202fAM GMT-5", ar0: "٧/١٢/٢٠٢١، ١٢:٤٠ م غرينتش-٥", ar1: "٢٣/١/١٩٨٩، ٢:٠٨ ص غرينتش-٥" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "longOffset", en0: "12/7/2021, 12:40\u202fPM GMT-05:00", en1: "1/23/1989, 2:08\u202fAM GMT-05:00", ar0: "٧/١٢/٢٠٢١، ١٢:٤٠ م غرينتش-٠٥:٠٠", ar1: "٢٣/١/١٩٨٩، ٢:٠٨ ص غرينتش-٠٥:٠٠" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "shortGeneric", en0: "12/7/2021, 12:40\u202fPM ET", en1: "1/23/1989, 2:08\u202fAM ET", ar0: "٧/١٢/٢٠٢١، ١٢:٤٠ م غرينتش-٥", ar1: "٢٣/١/١٩٨٩، ٢:٠٨ ص غرينتش-٥" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "longGeneric", en0: "12/7/2021, 12:40\u202fPM Eastern Time", en1: "1/23/1989, 2:08\u202fAM Eastern Time", ar0: "٧/١٢/٢٠٢١، ١٢:٤٠ م التوقيت الشرقي لأمريكا الشمالية", ar1: "٢٣/١/١٩٨٩، ٢:٠٨ ص التوقيت الشرقي لأمريكا الشمالية" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "short", en0: "12/7/2021, EST", en1: "1/23/1989, EST", ar0: "٧/١٢/٢٠٢١، غرينتش-٥", ar1: "٢٣/١/١٩٨٩، غرينتش-٥" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "long", en0: "12/7/2021, Eastern Standard Time", en1: "1/23/1989, Eastern Standard Time", ar0: "٧/١٢/٢٠٢١، التوقيت الرسمي الشرقي لأمريكا الشمالية", ar1: "٢٣/١/١٩٨٩، التوقيت الرسمي الشرقي لأمريكا الشمالية" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "shortOffset", en0: "12/7/2021, GMT-5", en1: "1/23/1989, GMT-5", ar0: "٧/١٢/٢٠٢١، غرينتش-٥", ar1: "٢٣/١/١٩٨٩، غرينتش-٥" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "longOffset", en0: "12/7/2021, GMT-05:00", en1: "1/23/1989, GMT-05:00", ar0: "٧/١٢/٢٠٢١، غرينتش-٠٥:٠٠", ar1: "٢٣/١/١٩٨٩، غرينتش-٠٥:٠٠" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "shortGeneric", en0: "12/7/2021, ET", en1: "1/23/1989, ET", ar0: "٧/١٢/٢٠٢١، توقيت نيويورك", ar1: "٢٣/١/١٩٨٩، توقيت نيويورك" },
|
||||
{ timeZone: "America/New_York", timeZoneName: "longGeneric", en0: "12/7/2021, Eastern Time", en1: "1/23/1989, Eastern Time", ar0: "٧/١٢/٢٠٢١، التوقيت الشرقي لأمريكا الشمالية", ar1: "٢٣/١/١٩٨٩، التوقيت الشرقي لأمريكا الشمالية" },
|
||||
|
||||
{ timeZone: "Europe/London", timeZoneName: "short", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "long", en0: "12/7/2021, 5:40\u202fPM Greenwich Mean Time", en1: "1/23/1989, 7:08\u202fAM Greenwich Mean Time", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م توقيت غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص توقيت غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "shortOffset", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "longOffset", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "shortGeneric", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "longGeneric", en0: "12/7/2021, 5:40\u202fPM GMT", en1: "1/23/1989, 7:08\u202fAM GMT", ar0: "٧/١٢/٢٠٢١، ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩، ٧:٠٨ ص غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "short", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "long", en0: "12/7/2021, Greenwich Mean Time", en1: "1/23/1989, Greenwich Mean Time", ar0: "٧/١٢/٢٠٢١، توقيت غرينتش", ar1: "٢٣/١/١٩٨٩، توقيت غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "shortOffset", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "longOffset", en0: "12/7/2021, GMT", en1: "1/23/1989, GMT", ar0: "٧/١٢/٢٠٢١، غرينتش", ar1: "٢٣/١/١٩٨٩، غرينتش" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "shortGeneric", en0: "12/7/2021, United Kingdom Time", en1: "1/23/1989, United Kingdom Time", ar0: "٧/١٢/٢٠٢١، توقيت المملكة المتحدة", ar1: "٢٣/١/١٩٨٩، توقيت المملكة المتحدة" },
|
||||
{ timeZone: "Europe/London", timeZoneName: "longGeneric", en0: "12/7/2021, United Kingdom Time", en1: "1/23/1989, United Kingdom Time", ar0: "٧/١٢/٢٠٢١، توقيت المملكة المتحدة", ar1: "٢٣/١/١٩٨٩، توقيت المملكة المتحدة" },
|
||||
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "short", en0: "12/7/2021, 9:40\u202fAM PST", en1: "1/22/1989, 11:08\u202fPM PST", ar0: "٧/١٢/٢٠٢١، ٩:٤٠ ص غرينتش-٨", ar1: "٢٢/١/١٩٨٩، ١١:٠٨ م غرينتش-٨" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "long", en0: "12/7/2021, 9:40\u202fAM Pacific Standard Time", en1: "1/22/1989, 11:08\u202fPM Pacific Standard Time", ar0: "٧/١٢/٢٠٢١، ٩:٤٠ ص توقيت المحيط الهادي الرسمي", ar1: "٢٢/١/١٩٨٩، ١١:٠٨ م توقيت المحيط الهادي الرسمي" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "shortOffset", en0: "12/7/2021, 9:40\u202fAM GMT-8", en1: "1/22/1989, 11:08\u202fPM GMT-8", ar0: "٧/١٢/٢٠٢١، ٩:٤٠ ص غرينتش-٨", ar1: "٢٢/١/١٩٨٩، ١١:٠٨ م غرينتش-٨" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "longOffset", en0: "12/7/2021, 9:40\u202fAM GMT-08:00", en1: "1/22/1989, 11:08\u202fPM GMT-08:00", ar0: "٧/١٢/٢٠٢١، ٩:٤٠ ص غرينتش-٠٨:٠٠", ar1: "٢٢/١/١٩٨٩، ١١:٠٨ م غرينتش-٠٨:٠٠" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "shortGeneric", en0: "12/7/2021, 9:40\u202fAM PT", en1: "1/22/1989, 11:08\u202fPM PT", ar0: "٧/١٢/٢٠٢١، ٩:٤٠ ص غرينتش-٨", ar1: "٢٢/١/١٩٨٩، ١١:٠٨ م غرينتش-٨" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "longGeneric", en0: "12/7/2021, 9:40\u202fAM Pacific Time", en1: "1/22/1989, 11:08\u202fPM Pacific Time", ar0: "٧/١٢/٢٠٢١، ٩:٤٠ ص توقيت المحيط الهادي", ar1: "٢٢/١/١٩٨٩، ١١:٠٨ م توقيت المحيط الهادي" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "short", en0: "12/7/2021, PST", en1: "1/22/1989, PST", ar0: "٧/١٢/٢٠٢١، غرينتش-٨", ar1: "٢٢/١/١٩٨٩، غرينتش-٨" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "long", en0: "12/7/2021, Pacific Standard Time", en1: "1/22/1989, Pacific Standard Time", ar0: "٧/١٢/٢٠٢١، توقيت المحيط الهادي الرسمي", ar1: "٢٢/١/١٩٨٩، توقيت المحيط الهادي الرسمي" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "shortOffset", en0: "12/7/2021, GMT-8", en1: "1/22/1989, GMT-8", ar0: "٧/١٢/٢٠٢١، غرينتش-٨", ar1: "٢٢/١/١٩٨٩، غرينتش-٨" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "longOffset", en0: "12/7/2021, GMT-08:00", en1: "1/22/1989, GMT-08:00", ar0: "٧/١٢/٢٠٢١، غرينتش-٠٨:٠٠", ar1: "٢٢/١/١٩٨٩، غرينتش-٠٨:٠٠" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "shortGeneric", en0: "12/7/2021, PT", en1: "1/22/1989, PT", ar0: "٧/١٢/٢٠٢١، توقيت لوس انجلوس", ar1: "٢٢/١/١٩٨٩، توقيت لوس انجلوس" },
|
||||
{ timeZone: "America/Los_Angeles", timeZoneName: "longGeneric", en0: "12/7/2021, Pacific Time", en1: "1/22/1989, Pacific Time", ar0: "٧/١٢/٢٠٢١، توقيت المحيط الهادي", ar1: "٢٢/١/١٩٨٩، توقيت المحيط الهادي" },
|
||||
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "short", en0: "12/7/2021, 11:25\u202fPM GMT+5:45", en1: "1/23/1989, 12:53\u202fPM GMT+5:45", ar0: "٧/١٢/٢٠٢١، ١١:٢٥ م غرينتش+٥:٤٥", ar1: "٢٣/١/١٩٨٩، ١٢:٥٣ م غرينتش+٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "long", en0: "12/7/2021, 11:25\u202fPM Nepal Time", en1: "1/23/1989, 12:53\u202fPM Nepal Time", ar0: "٧/١٢/٢٠٢١، ١١:٢٥ م توقيت نيبال", ar1: "٢٣/١/١٩٨٩، ١٢:٥٣ م توقيت نيبال" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "shortOffset", en0: "12/7/2021, 11:25\u202fPM GMT+5:45", en1: "1/23/1989, 12:53\u202fPM GMT+5:45", ar0: "٧/١٢/٢٠٢١، ١١:٢٥ م غرينتش+٥:٤٥", ar1: "٢٣/١/١٩٨٩، ١٢:٥٣ م غرينتش+٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "longOffset", en0: "12/7/2021, 11:25\u202fPM GMT+05:45", en1: "1/23/1989, 12:53\u202fPM GMT+05:45", ar0: "٧/١٢/٢٠٢١، ١١:٢٥ م غرينتش+٠٥:٤٥", ar1: "٢٣/١/١٩٨٩، ١٢:٥٣ م غرينتش+٠٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "shortGeneric", en0: "12/7/2021, 11:25\u202fPM GMT+5:45", en1: "1/23/1989, 12:53\u202fPM GMT+5:45", ar0: "٧/١٢/٢٠٢١، ١١:٢٥ م غرينتش+٥:٤٥", ar1: "٢٣/١/١٩٨٩، ١٢:٥٣ م غرينتش+٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "longGeneric", en0: "12/7/2021, 11:25\u202fPM GMT+05:45", en1: "1/23/1989, 12:53\u202fPM GMT+05:45", ar0: "٧/١٢/٢٠٢١، ١١:٢٥ م غرينتش+٠٥:٤٥", ar1: "٢٣/١/١٩٨٩، ١٢:٥٣ م غرينتش+٠٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "short", en0: "12/7/2021, GMT+5:45", en1: "1/23/1989, GMT+5:45", ar0: "٧/١٢/٢٠٢١، غرينتش+٥:٤٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "long", en0: "12/7/2021, Nepal Time", en1: "1/23/1989, Nepal Time", ar0: "٧/١٢/٢٠٢١، توقيت نيبال", ar1: "٢٣/١/١٩٨٩، توقيت نيبال" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "shortOffset", en0: "12/7/2021, GMT+5:45", en1: "1/23/1989, GMT+5:45", ar0: "٧/١٢/٢٠٢١، غرينتش+٥:٤٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "longOffset", en0: "12/7/2021, GMT+05:45", en1: "1/23/1989, GMT+05:45", ar0: "٧/١٢/٢٠٢١، غرينتش+٠٥:٤٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٠٥:٤٥" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "shortGeneric", en0: "12/7/2021, Nepal Time", en1: "1/23/1989, Nepal Time", ar0: "٧/١٢/٢٠٢١، توقيت نيبال", ar1: "٢٣/١/١٩٨٩، توقيت نيبال" },
|
||||
{ timeZone: "Asia/Kathmandu", timeZoneName: "longGeneric", en0: "12/7/2021, Nepal Time", en1: "1/23/1989, Nepal Time", ar0: "٧/١٢/٢٠٢١، توقيت نيبال", ar1: "٢٣/١/١٩٨٩، توقيت نيبال" },
|
||||
|
||||
{ timeZone: "+04:15", timeZoneName: "short", en0: "12/7/2021, 9:55\u202fPM +04:15", en1: "1/23/1989, 11:23\u202fAM +04:15", ar0: "٧/١٢/٢٠٢١، ٩:٥٥ م +04:15", ar1: "٢٣/١/١٩٨٩، ١١:٢٣ ص +04:15" },
|
||||
{ timeZone: "+04:15", timeZoneName: "long", en0: "12/7/2021, 9:55\u202fPM +04:15", en1: "1/23/1989, 11:23\u202fAM +04:15", ar0: "٧/١٢/٢٠٢١، ٩:٥٥ م +04:15", ar1: "٢٣/١/١٩٨٩، ١١:٢٣ ص +04:15" },
|
||||
{ timeZone: "+04:15", timeZoneName: "shortOffset", en0: "12/7/2021, 9:55\u202fPM +04:15", en1: "1/23/1989, 11:23\u202fAM +04:15", ar0: "٧/١٢/٢٠٢١، ٩:٥٥ م +04:15", ar1: "٢٣/١/١٩٨٩، ١١:٢٣ ص +04:15" },
|
||||
{ timeZone: "+04:15", timeZoneName: "longOffset", en0: "12/7/2021, 9:55\u202fPM +04:15", en1: "1/23/1989, 11:23\u202fAM +04:15", ar0: "٧/١٢/٢٠٢١، ٩:٥٥ م +04:15", ar1: "٢٣/١/١٩٨٩، ١١:٢٣ ص +04:15" },
|
||||
{ timeZone: "+04:15", timeZoneName: "shortGeneric", en0: "12/7/2021, 9:55\u202fPM +04:15", en1: "1/23/1989, 11:23\u202fAM +04:15", ar0: "٧/١٢/٢٠٢١، ٩:٥٥ م +04:15", ar1: "٢٣/١/١٩٨٩، ١١:٢٣ ص +04:15" },
|
||||
{ timeZone: "+04:15", timeZoneName: "longGeneric", en0: "12/7/2021, 9:55\u202fPM +04:15", en1: "1/23/1989, 11:23\u202fAM +04:15", ar0: "٧/١٢/٢٠٢١، ٩:٥٥ م +04:15", ar1: "٢٣/١/١٩٨٩، ١١:٢٣ ص +04:15" },
|
||||
{ timeZone: "+04:15", timeZoneName: "short", en0: "12/7/2021, GMT+4:15", en1: "1/23/1989, GMT+4:15", ar0: "٧/١٢/٢٠٢١، غرينتش+٤:١٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٤:١٥" },
|
||||
{ timeZone: "+04:15", timeZoneName: "long", en0: "12/7/2021, GMT+04:15", en1: "1/23/1989, GMT+04:15", ar0: "٧/١٢/٢٠٢١، غرينتش+٠٤:١٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٠٤:١٥" },
|
||||
{ timeZone: "+04:15", timeZoneName: "shortOffset", en0: "12/7/2021, GMT+4:15", en1: "1/23/1989, GMT+4:15", ar0: "٧/١٢/٢٠٢١، غرينتش+٤:١٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٤:١٥" },
|
||||
{ timeZone: "+04:15", timeZoneName: "longOffset", en0: "12/7/2021, GMT+04:15", en1: "1/23/1989, GMT+04:15", ar0: "٧/١٢/٢٠٢١، غرينتش+٠٤:١٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٠٤:١٥" },
|
||||
{ timeZone: "+04:15", timeZoneName: "shortGeneric", en0: "12/7/2021, GMT+4:15", en1: "1/23/1989, GMT+4:15", ar0: "٧/١٢/٢٠٢١، غرينتش+٤:١٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٤:١٥" },
|
||||
{ timeZone: "+04:15", timeZoneName: "longGeneric", en0: "12/7/2021, GMT+04:15", en1: "1/23/1989, GMT+04:15", ar0: "٧/١٢/٢٠٢١، غرينتش+٠٤:١٥", ar1: "٢٣/١/١٩٨٩، غرينتش+٠٤:١٥" },
|
||||
];
|
||||
|
||||
test("all", () => {
|
||||
|
@ -58,8 +58,8 @@ describe("equal dates are squashed", () => {
|
||||
day: "2-digit",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
expect(ja.formatRange(d0, d0)).toBe("1989/1月/23");
|
||||
expect(ja.formatRange(d1, d1)).toBe("2021/12月/07");
|
||||
expect(ja.formatRange(d0, d0)).toBe("1989年1月23日");
|
||||
expect(ja.formatRange(d1, d1)).toBe("2021年12月07日");
|
||||
});
|
||||
|
||||
test("with time fields", () => {
|
||||
@ -104,8 +104,8 @@ describe("equal dates are squashed", () => {
|
||||
second: "2-digit",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
expect(ja.formatRange(d0, d0)).toBe("1989/1月/23 7:08:09");
|
||||
expect(ja.formatRange(d1, d1)).toBe("2021/12月/07 17:40:50");
|
||||
expect(ja.formatRange(d0, d0)).toBe("1989年1月23日 7:08:09");
|
||||
expect(ja.formatRange(d1, d1)).toBe("2021年12月07日 17:40:50");
|
||||
});
|
||||
|
||||
test("with date/time style fields", () => {
|
||||
@ -130,7 +130,7 @@ describe("equal dates are squashed", () => {
|
||||
describe("dateStyle", () => {
|
||||
// prettier-ignore
|
||||
const data = [
|
||||
{ date: "full", en: "Monday, January 23, 1989\u2009–\u2009Tuesday, December 7, 2021", ja: "1989年1月23日月曜日~2021年12月7日火曜日" },
|
||||
{ date: "full", en: "Monday, January 23, 1989\u2009–\u2009Tuesday, December 7, 2021", ja: "1989/01/23(月曜日)~2021/12/07(火曜日)" },
|
||||
{ date: "long", en: "January 23, 1989\u2009–\u2009December 7, 2021", ja: "1989/01/23~2021/12/07" },
|
||||
{ date: "medium", en: "Jan 23, 1989\u2009–\u2009Dec 7, 2021", ja: "1989/01/23~2021/12/07" },
|
||||
{ date: "short", en: "1/23/89\u2009–\u200912/7/21", ja: "1989/01/23~2021/12/07" },
|
||||
@ -159,19 +159,17 @@ describe("dateStyle", () => {
|
||||
);
|
||||
|
||||
const ja = new Intl.DateTimeFormat("ja", { dateStyle: "full", timeZone: "UTC" });
|
||||
expect(ja.formatRange(d1, d0)).toBe("2021年12月7日火曜日~1989年1月23日月曜日");
|
||||
expect(ja.formatRange(d1, d0)).toBe("2021/12/07(火曜日)~1989/01/23(月曜日)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("timeStyle", () => {
|
||||
// prettier-ignore
|
||||
const data = [
|
||||
// FIXME: These results should include the date, even though it isn't requested, because the start/end dates
|
||||
// are more than just hours apart. See the FIXME in PartitionDateTimeRangePattern.
|
||||
{ time: "full", en: "7:08:09\u202fAM Coordinated Universal Time\u2009–\u20095:40:50\u202fPM Coordinated Universal Time", ja: "7時08分09秒 協定世界時~17時40分50秒 協定世界時" },
|
||||
{ time: "long", en: "7:08:09\u202fAM UTC\u2009–\u20095:40:50\u202fPM UTC", ja: "7:08:09 UTC~17:40:50 UTC" },
|
||||
{ time: "medium", en: "7:08:09\u202fAM\u2009–\u20095:40:50\u202fPM", ja: "7:08:09~17:40:50" },
|
||||
{ time: "short", en: "7:08\u202fAM\u2009–\u20095:40\u202fPM", ja: "7:08~17:40" },
|
||||
{ time: "full", en: "1/23/1989, 7:08:09\u202fAM Coordinated Universal Time\u2009–\u200912/7/2021, 5:40:50\u202fPM Coordinated Universal Time", ja: "1989/1/23 7時08分09秒 協定世界時~2021/12/7 17時40分50秒 協定世界時" },
|
||||
{ time: "long", en: "1/23/1989, 7:08:09\u202fAM UTC\u2009–\u200912/7/2021, 5:40:50\u202fPM UTC", ja: "1989/1/23 7:08:09 UTC~2021/12/7 17:40:50 UTC" },
|
||||
{ time: "medium", en: "1/23/1989, 7:08:09\u202fAM\u2009–\u200912/7/2021, 5:40:50\u202fPM", ja: "1989/1/23 7:08:09~2021/12/7 17:40:50" },
|
||||
{ time: "short", en: "1/23/1989, 7:08\u202fAM\u2009–\u200912/7/2021, 5:40\u202fPM", ja: "1989/1/23 7:08~2021/12/7 17:40" },
|
||||
];
|
||||
|
||||
test("all", () => {
|
||||
@ -188,14 +186,14 @@ describe("timeStyle", () => {
|
||||
describe("dateStyle + timeStyle", () => {
|
||||
// prettier-ignore
|
||||
const data = [
|
||||
{ date: "full", time: "full", en: "Monday, January 23, 1989 at 7:08:09\u202fAM Coordinated Universal Time\u2009–\u2009Tuesday, December 7, 2021 at 5:40:50\u202fPM Coordinated Universal Time", ja: "1989年1月23日月曜日 7時08分09秒 協定世界時~2021年12月7日火曜日 17時40分50秒 協定世界時" },
|
||||
{ date: "full", time: "long", en: "Monday, January 23, 1989 at 7:08:09\u202fAM UTC\u2009–\u2009Tuesday, December 7, 2021 at 5:40:50\u202fPM UTC", ja: "1989年1月23日月曜日 7:08:09 UTC~2021年12月7日火曜日 17:40:50 UTC" },
|
||||
{ date: "full", time: "medium", en: "Monday, January 23, 1989 at 7:08:09\u202fAM\u2009–\u2009Tuesday, December 7, 2021 at 5:40:50\u202fPM", ja: "1989年1月23日月曜日 7:08:09~2021年12月7日火曜日 17:40:50" },
|
||||
{ date: "full", time: "short", en: "Monday, January 23, 1989 at 7:08\u202fAM\u2009–\u2009Tuesday, December 7, 2021 at 5:40\u202fPM", ja: "1989年1月23日月曜日 7:08~2021年12月7日火曜日 17:40" },
|
||||
{ date: "long", time: "full", en: "January 23, 1989 at 7:08:09\u202fAM Coordinated Universal Time\u2009–\u2009December 7, 2021 at 5:40:50\u202fPM Coordinated Universal Time", ja: "1989年1月23日 7時08分09秒 協定世界時~2021年12月7日 17時40分50秒 協定世界時" },
|
||||
{ date: "long", time: "long", en: "January 23, 1989 at 7:08:09\u202fAM UTC\u2009–\u2009December 7, 2021 at 5:40:50\u202fPM UTC", ja: "1989年1月23日 7:08:09 UTC~2021年12月7日 17:40:50 UTC" },
|
||||
{ date: "long", time: "medium", en: "January 23, 1989 at 7:08:09\u202fAM\u2009–\u2009December 7, 2021 at 5:40:50\u202fPM", ja: "1989年1月23日 7:08:09~2021年12月7日 17:40:50" },
|
||||
{ date: "long", time: "short", en: "January 23, 1989 at 7:08\u202fAM\u2009–\u2009December 7, 2021 at 5:40\u202fPM", ja: "1989年1月23日 7:08~2021年12月7日 17:40" },
|
||||
{ date: "full", time: "full", en: "Monday, January 23, 1989 at 7:08:09\u202fAM Coordinated Universal Time\u2009–\u2009Tuesday, December 7, 2021 at 5:40:50\u202fPM Coordinated Universal Time", ja: "1989/1/23月曜日 7時08分09秒 協定世界時~2021/12/7火曜日 17時40分50秒 協定世界時" },
|
||||
{ date: "full", time: "long", en: "Monday, January 23, 1989 at 7:08:09\u202fAM UTC\u2009–\u2009Tuesday, December 7, 2021 at 5:40:50\u202fPM UTC", ja: "1989/1/23月曜日 7:08:09 UTC~2021/12/7火曜日 17:40:50 UTC" },
|
||||
{ date: "full", time: "medium", en: "Monday, January 23, 1989 at 7:08:09\u202fAM\u2009–\u2009Tuesday, December 7, 2021 at 5:40:50\u202fPM", ja: "1989/1/23月曜日 7:08:09~2021/12/7火曜日 17:40:50" },
|
||||
{ date: "full", time: "short", en: "Monday, January 23, 1989 at 7:08\u202fAM\u2009–\u2009Tuesday, December 7, 2021 at 5:40\u202fPM", ja: "1989/1/23月曜日 7:08~2021/12/7火曜日 17:40" },
|
||||
{ date: "long", time: "full", en: "January 23, 1989 at 7:08:09\u202fAM Coordinated Universal Time\u2009–\u2009December 7, 2021 at 5:40:50\u202fPM Coordinated Universal Time", ja: "1989/1/23 7時08分09秒 協定世界時~2021/12/7 17時40分50秒 協定世界時" },
|
||||
{ date: "long", time: "long", en: "January 23, 1989 at 7:08:09\u202fAM UTC\u2009–\u2009December 7, 2021 at 5:40:50\u202fPM UTC", ja: "1989/1/23 7:08:09 UTC~2021/12/7 17:40:50 UTC" },
|
||||
{ date: "long", time: "medium", en: "January 23, 1989 at 7:08:09\u202fAM\u2009–\u2009December 7, 2021 at 5:40:50\u202fPM", ja: "1989/1/23 7:08:09~2021/12/7 17:40:50" },
|
||||
{ date: "long", time: "short", en: "January 23, 1989 at 7:08\u202fAM\u2009–\u2009December 7, 2021 at 5:40\u202fPM", ja: "1989/1/23 7:08~2021/12/7 17:40" },
|
||||
{ date: "medium", time: "full", en: "Jan 23, 1989, 7:08:09\u202fAM Coordinated Universal Time\u2009–\u2009Dec 7, 2021, 5:40:50\u202fPM Coordinated Universal Time", ja: "1989/01/23 7時08分09秒 協定世界時~2021/12/07 17時40分50秒 協定世界時" },
|
||||
{ date: "medium", time: "long", en: "Jan 23, 1989, 7:08:09\u202fAM UTC\u2009–\u2009Dec 7, 2021, 5:40:50\u202fPM UTC", ja: "1989/01/23 7:08:09 UTC~2021/12/07 17:40:50 UTC" },
|
||||
{ date: "medium", time: "medium", en: "Jan 23, 1989, 7:08:09\u202fAM\u2009–\u2009Dec 7, 2021, 5:40:50\u202fPM", ja: "1989/01/23 7:08:09~2021/12/07 17:40:50" },
|
||||
|
@ -65,10 +65,11 @@ describe("equal dates are squashed", () => {
|
||||
});
|
||||
expect(ja.formatRangeToParts(d0, d0)).toEqual([
|
||||
{ type: "year", value: "1989", source: "shared" },
|
||||
{ type: "literal", value: "/", source: "shared" },
|
||||
{ type: "month", value: "1月", source: "shared" },
|
||||
{ type: "literal", value: "/", source: "shared" },
|
||||
{ type: "literal", value: "年", source: "shared" },
|
||||
{ type: "month", value: "1", source: "shared" },
|
||||
{ type: "literal", value: "月", source: "shared" },
|
||||
{ type: "day", value: "23", source: "shared" },
|
||||
{ type: "literal", value: "日", source: "shared" },
|
||||
]);
|
||||
});
|
||||
|
||||
@ -141,11 +142,11 @@ describe("equal dates are squashed", () => {
|
||||
});
|
||||
expect(ja.formatRangeToParts(d0, d0)).toEqual([
|
||||
{ type: "year", value: "1989", source: "shared" },
|
||||
{ type: "literal", value: "/", source: "shared" },
|
||||
{ type: "month", value: "1月", source: "shared" },
|
||||
{ type: "literal", value: "/", source: "shared" },
|
||||
{ type: "literal", value: "年", source: "shared" },
|
||||
{ type: "month", value: "1", source: "shared" },
|
||||
{ type: "literal", value: "月", source: "shared" },
|
||||
{ type: "day", value: "23", source: "shared" },
|
||||
{ type: "literal", value: " ", source: "shared" },
|
||||
{ type: "literal", value: "日 ", source: "shared" },
|
||||
{ type: "hour", value: "7", source: "shared" },
|
||||
{ type: "literal", value: ":", source: "shared" },
|
||||
{ type: "minute", value: "08", source: "shared" },
|
||||
@ -212,7 +213,7 @@ describe("dateStyle", () => {
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: ", ", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "\u2009–\u2009", source: "shared" },
|
||||
{ type: "literal", value: " – ", source: "shared" },
|
||||
{ type: "weekday", value: "Tuesday", source: "endRange" },
|
||||
{ type: "literal", value: ", ", source: "endRange" },
|
||||
{ type: "month", value: "December", source: "endRange" },
|
||||
@ -225,20 +226,21 @@ describe("dateStyle", () => {
|
||||
const ja = new Intl.DateTimeFormat("ja", { dateStyle: "full", timeZone: "UTC" });
|
||||
expect(ja.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "年", source: "startRange" },
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "月", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "01", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "日", source: "startRange" },
|
||||
{ type: "literal", value: "(", source: "startRange" },
|
||||
{ type: "weekday", value: "月曜日", source: "startRange" },
|
||||
{ type: "literal", value: "~", source: "shared" },
|
||||
{ type: "literal", value: ")~", source: "shared" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: "年", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "月", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: "日", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "07", source: "endRange" },
|
||||
{ type: "literal", value: "(", source: "endRange" },
|
||||
{ type: "weekday", value: "火曜日", source: "endRange" },
|
||||
{ type: "literal", value: ")", source: "shared" },
|
||||
]);
|
||||
});
|
||||
|
||||
@ -361,20 +363,21 @@ describe("dateStyle", () => {
|
||||
const ja = new Intl.DateTimeFormat("ja", { dateStyle: "full", timeZone: "UTC" });
|
||||
expect(ja.formatRangeToParts(d1, d0)).toEqual([
|
||||
{ type: "year", value: "2021", source: "startRange" },
|
||||
{ type: "literal", value: "年", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "12", source: "startRange" },
|
||||
{ type: "literal", value: "月", source: "startRange" },
|
||||
{ type: "day", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: "日", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "07", source: "startRange" },
|
||||
{ type: "literal", value: "(", source: "startRange" },
|
||||
{ type: "weekday", value: "火曜日", source: "startRange" },
|
||||
{ type: "literal", value: "~", source: "shared" },
|
||||
{ type: "literal", value: ")~", source: "shared" },
|
||||
{ type: "year", value: "1989", source: "endRange" },
|
||||
{ type: "literal", value: "年", source: "endRange" },
|
||||
{ type: "month", value: "1", source: "endRange" },
|
||||
{ type: "literal", value: "月", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "month", value: "01", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "23", source: "endRange" },
|
||||
{ type: "literal", value: "日", source: "endRange" },
|
||||
{ type: "literal", value: "(", source: "endRange" },
|
||||
{ type: "weekday", value: "月曜日", source: "endRange" },
|
||||
{ type: "literal", value: ")", source: "shared" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -385,6 +388,12 @@ describe("timeStyle", () => {
|
||||
test("full", () => {
|
||||
const en = new Intl.DateTimeFormat("en", { timeStyle: "full", timeZone: "UTC" });
|
||||
expect(en.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: ", ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
@ -395,6 +404,12 @@ describe("timeStyle", () => {
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "timeZoneName", value: "Coordinated Universal Time", source: "startRange" },
|
||||
{ type: "literal", value: "\u2009–\u2009", source: "shared" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: ", ", source: "endRange" },
|
||||
{ type: "hour", value: "5", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -408,6 +423,12 @@ describe("timeStyle", () => {
|
||||
|
||||
const ja = new Intl.DateTimeFormat("ja", { timeStyle: "full", timeZone: "UTC" });
|
||||
expect(ja.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: "時", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
@ -416,6 +437,12 @@ describe("timeStyle", () => {
|
||||
{ type: "literal", value: "秒 ", source: "startRange" },
|
||||
{ type: "timeZoneName", value: "協定世界時", source: "startRange" },
|
||||
{ type: "literal", value: "~", source: "shared" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: " ", source: "endRange" },
|
||||
{ type: "hour", value: "17", source: "endRange" },
|
||||
{ type: "literal", value: "時", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -429,6 +456,12 @@ describe("timeStyle", () => {
|
||||
test("long", () => {
|
||||
const en = new Intl.DateTimeFormat("en", { timeStyle: "long", timeZone: "UTC" });
|
||||
expect(en.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: ", ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
@ -439,6 +472,12 @@ describe("timeStyle", () => {
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "timeZoneName", value: "UTC", source: "startRange" },
|
||||
{ type: "literal", value: "\u2009–\u2009", source: "shared" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: ", ", source: "endRange" },
|
||||
{ type: "hour", value: "5", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -452,6 +491,12 @@ describe("timeStyle", () => {
|
||||
|
||||
const ja = new Intl.DateTimeFormat("ja", { timeStyle: "long", timeZone: "UTC" });
|
||||
expect(ja.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
@ -460,6 +505,12 @@ describe("timeStyle", () => {
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "timeZoneName", value: "UTC", source: "startRange" },
|
||||
{ type: "literal", value: "~", source: "shared" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: " ", source: "endRange" },
|
||||
{ type: "hour", value: "17", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -473,6 +524,12 @@ describe("timeStyle", () => {
|
||||
test("medium", () => {
|
||||
const en = new Intl.DateTimeFormat("en", { timeStyle: "medium", timeZone: "UTC" });
|
||||
expect(en.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: ", ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
@ -481,6 +538,12 @@ describe("timeStyle", () => {
|
||||
{ type: "literal", value: "\u202f", source: "startRange" },
|
||||
{ type: "dayPeriod", value: "AM", source: "startRange" },
|
||||
{ type: "literal", value: "\u2009–\u2009", source: "shared" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: ", ", source: "endRange" },
|
||||
{ type: "hour", value: "5", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -492,12 +555,24 @@ describe("timeStyle", () => {
|
||||
|
||||
const ja = new Intl.DateTimeFormat("ja", { timeStyle: "medium", timeZone: "UTC" });
|
||||
expect(ja.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "second", value: "09", source: "startRange" },
|
||||
{ type: "literal", value: "~", source: "shared" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: " ", source: "endRange" },
|
||||
{ type: "hour", value: "17", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -509,12 +584,24 @@ describe("timeStyle", () => {
|
||||
test("short", () => {
|
||||
const en = new Intl.DateTimeFormat("en", { timeStyle: "short", timeZone: "UTC" });
|
||||
expect(en.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: ", ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
{ type: "literal", value: "\u202f", source: "startRange" },
|
||||
{ type: "dayPeriod", value: "AM", source: "startRange" },
|
||||
{ type: "literal", value: "\u2009–\u2009", source: "shared" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: ", ", source: "endRange" },
|
||||
{ type: "hour", value: "5", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
@ -524,10 +611,22 @@ describe("timeStyle", () => {
|
||||
|
||||
const ja = new Intl.DateTimeFormat("ja", { timeStyle: "short", timeZone: "UTC" });
|
||||
expect(ja.formatRangeToParts(d0, d1)).toEqual([
|
||||
{ type: "year", value: "1989", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "month", value: "1", source: "startRange" },
|
||||
{ type: "literal", value: "/", source: "startRange" },
|
||||
{ type: "day", value: "23", source: "startRange" },
|
||||
{ type: "literal", value: " ", source: "startRange" },
|
||||
{ type: "hour", value: "7", source: "startRange" },
|
||||
{ type: "literal", value: ":", source: "startRange" },
|
||||
{ type: "minute", value: "08", source: "startRange" },
|
||||
{ type: "literal", value: "~", source: "shared" },
|
||||
{ type: "year", value: "2021", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "month", value: "12", source: "endRange" },
|
||||
{ type: "literal", value: "/", source: "endRange" },
|
||||
{ type: "day", value: "7", source: "endRange" },
|
||||
{ type: "literal", value: " ", source: "endRange" },
|
||||
{ type: "hour", value: "17", source: "endRange" },
|
||||
{ type: "literal", value: ":", source: "endRange" },
|
||||
{ type: "minute", value: "40", source: "endRange" },
|
||||
|
@ -211,14 +211,14 @@ describe("correct behavior", () => {
|
||||
test("minute", () => {
|
||||
["2-digit", "numeric"].forEach(minute => {
|
||||
const en = new Intl.DateTimeFormat("en", { minute: minute });
|
||||
expect(en.resolvedOptions().minute).toBe("2-digit");
|
||||
expect(en.resolvedOptions().minute).toBe(minute);
|
||||
});
|
||||
});
|
||||
|
||||
test("second", () => {
|
||||
["2-digit", "numeric"].forEach(second => {
|
||||
const en = new Intl.DateTimeFormat("en", { second: second });
|
||||
expect(en.resolvedOptions().second).toBe("2-digit");
|
||||
expect(en.resolvedOptions().second).toBe(second);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,18 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#define AK_DONT_REPLACE_STD
|
||||
|
||||
#include <AK/AllOf.h>
|
||||
#include <AK/Array.h>
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibLocale/DateTimeFormat.h>
|
||||
#include <LibLocale/ICU.h>
|
||||
#include <LibLocale/Locale.h>
|
||||
#include <LibLocale/NumberFormat.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <unicode/calendar.h>
|
||||
#include <unicode/datefmt.h>
|
||||
#include <unicode/dtitvfmt.h>
|
||||
#include <unicode/dtptngen.h>
|
||||
#include <unicode/gregocal.h>
|
||||
#include <unicode/smpdtfmt.h>
|
||||
#include <unicode/timezone.h>
|
||||
|
||||
namespace Locale {
|
||||
|
||||
DateTimeStyle date_time_style_from_string(StringView style)
|
||||
{
|
||||
if (style == "full"sv)
|
||||
return DateTimeStyle::Full;
|
||||
if (style == "long"sv)
|
||||
return DateTimeStyle::Long;
|
||||
if (style == "medium"sv)
|
||||
return DateTimeStyle::Medium;
|
||||
if (style == "short"sv)
|
||||
return DateTimeStyle::Short;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView date_time_style_to_string(DateTimeStyle style)
|
||||
{
|
||||
switch (style) {
|
||||
case DateTimeStyle::Full:
|
||||
return "full"sv;
|
||||
case DateTimeStyle::Long:
|
||||
return "long"sv;
|
||||
case DateTimeStyle::Medium:
|
||||
return "medium"sv;
|
||||
case DateTimeStyle::Short:
|
||||
return "short"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr icu::DateFormat::EStyle icu_date_time_style(DateTimeStyle style)
|
||||
{
|
||||
switch (style) {
|
||||
case DateTimeStyle::Full:
|
||||
return icu::DateFormat::EStyle::kFull;
|
||||
case DateTimeStyle::Long:
|
||||
return icu::DateFormat::EStyle::kLong;
|
||||
case DateTimeStyle::Medium:
|
||||
return icu::DateFormat::EStyle::kMedium;
|
||||
case DateTimeStyle::Short:
|
||||
return icu::DateFormat::EStyle::kShort;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
HourCycle hour_cycle_from_string(StringView hour_cycle)
|
||||
{
|
||||
if (hour_cycle == "h11"sv)
|
||||
@ -37,9 +94,28 @@ StringView hour_cycle_to_string(HourCycle hour_cycle)
|
||||
return "h23"sv;
|
||||
case HourCycle::H24:
|
||||
return "h24"sv;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr char icu_hour_cycle(Optional<HourCycle> const& hour_cycle, Optional<bool> const& hour12)
|
||||
{
|
||||
if (hour12.has_value())
|
||||
return *hour12 ? 'h' : 'H';
|
||||
if (!hour_cycle.has_value())
|
||||
return 'j';
|
||||
|
||||
switch (*hour_cycle) {
|
||||
case HourCycle::H11:
|
||||
return 'K';
|
||||
case HourCycle::H12:
|
||||
return 'h';
|
||||
case HourCycle::H23:
|
||||
return 'H';
|
||||
case HourCycle::H24:
|
||||
return 'k';
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CalendarPatternStyle calendar_pattern_style_from_string(StringView style)
|
||||
@ -86,9 +162,314 @@ StringView calendar_pattern_style_to_string(CalendarPatternStyle style)
|
||||
return "shortGeneric"sv;
|
||||
case CalendarPatternStyle::LongGeneric:
|
||||
return "longGeneric"sv;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
|
||||
String CalendarPattern::to_pattern() const
|
||||
{
|
||||
// What we refer to as Narrow, Short, and Long, TR-35 refers to as Narrow, Abbreviated, and Wide.
|
||||
StringBuilder builder;
|
||||
|
||||
if (era.has_value()) {
|
||||
switch (*era) {
|
||||
case CalendarPatternStyle::Narrow:
|
||||
builder.append("GGGGG"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Short:
|
||||
builder.append("G"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Long:
|
||||
builder.append("GGGG"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (year.has_value()) {
|
||||
switch (*year) {
|
||||
case CalendarPatternStyle::Numeric:
|
||||
builder.append("y"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::TwoDigit:
|
||||
builder.append("yy"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (month.has_value()) {
|
||||
switch (*month) {
|
||||
case CalendarPatternStyle::Numeric:
|
||||
builder.append("M"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::TwoDigit:
|
||||
builder.append("MM"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Narrow:
|
||||
builder.append("MMMMM"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Short:
|
||||
builder.append("MMM"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Long:
|
||||
builder.append("MMMM"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (weekday.has_value()) {
|
||||
switch (*weekday) {
|
||||
case CalendarPatternStyle::Narrow:
|
||||
builder.append("EEEEE"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Short:
|
||||
builder.append("E"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Long:
|
||||
builder.append("EEEE"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (day.has_value()) {
|
||||
switch (*day) {
|
||||
case CalendarPatternStyle::Numeric:
|
||||
builder.append("d"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::TwoDigit:
|
||||
builder.append("dd"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (day_period.has_value()) {
|
||||
switch (*day_period) {
|
||||
case CalendarPatternStyle::Narrow:
|
||||
builder.append("BBBBB"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Short:
|
||||
builder.append("B"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Long:
|
||||
builder.append("BBBB"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hour.has_value()) {
|
||||
auto hour_cycle_symbol = icu_hour_cycle(hour_cycle, hour12);
|
||||
|
||||
switch (*hour) {
|
||||
case CalendarPatternStyle::Numeric:
|
||||
builder.append(hour_cycle_symbol);
|
||||
break;
|
||||
case CalendarPatternStyle::TwoDigit:
|
||||
builder.append_repeated(hour_cycle_symbol, 2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (minute.has_value()) {
|
||||
switch (*minute) {
|
||||
case CalendarPatternStyle::Numeric:
|
||||
builder.append("m"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::TwoDigit:
|
||||
builder.append("mm"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (second.has_value()) {
|
||||
switch (*second) {
|
||||
case CalendarPatternStyle::Numeric:
|
||||
builder.append("s"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::TwoDigit:
|
||||
builder.append("ss"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fractional_second_digits.has_value()) {
|
||||
for (u8 i = 0; i < *fractional_second_digits; ++i)
|
||||
builder.append("S"sv);
|
||||
}
|
||||
if (time_zone_name.has_value()) {
|
||||
switch (*time_zone_name) {
|
||||
case CalendarPatternStyle::Short:
|
||||
builder.append("z"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::Long:
|
||||
builder.append("zzzz"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::ShortOffset:
|
||||
builder.append("O"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::LongOffset:
|
||||
builder.append("OOOO"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::ShortGeneric:
|
||||
builder.append("v"sv);
|
||||
break;
|
||||
case CalendarPatternStyle::LongGeneric:
|
||||
builder.append("vvvv"sv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
|
||||
CalendarPattern CalendarPattern::create_from_pattern(StringView pattern)
|
||||
{
|
||||
GenericLexer lexer { pattern };
|
||||
CalendarPattern format {};
|
||||
|
||||
while (!lexer.is_eof()) {
|
||||
if (lexer.next_is(is_quote)) {
|
||||
lexer.consume_quoted_string();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto starting_char = lexer.peek();
|
||||
auto segment = lexer.consume_while([&](char ch) { return ch == starting_char; });
|
||||
|
||||
// Era
|
||||
if (all_of(segment, is_any_of("G"sv))) {
|
||||
if (segment.length() <= 3)
|
||||
format.era = CalendarPatternStyle::Short;
|
||||
else if (segment.length() == 4)
|
||||
format.era = CalendarPatternStyle::Long;
|
||||
else
|
||||
format.era = CalendarPatternStyle::Narrow;
|
||||
}
|
||||
|
||||
// Year
|
||||
else if (all_of(segment, is_any_of("yYuUr"sv))) {
|
||||
if (segment.length() == 2)
|
||||
format.year = CalendarPatternStyle::TwoDigit;
|
||||
else
|
||||
format.year = CalendarPatternStyle::Numeric;
|
||||
}
|
||||
|
||||
// Month
|
||||
else if (all_of(segment, is_any_of("ML"sv))) {
|
||||
if (segment.length() == 1)
|
||||
format.month = CalendarPatternStyle::Numeric;
|
||||
else if (segment.length() == 2)
|
||||
format.month = CalendarPatternStyle::TwoDigit;
|
||||
else if (segment.length() == 3)
|
||||
format.month = CalendarPatternStyle::Short;
|
||||
else if (segment.length() == 4)
|
||||
format.month = CalendarPatternStyle::Long;
|
||||
else if (segment.length() == 5)
|
||||
format.month = CalendarPatternStyle::Narrow;
|
||||
}
|
||||
|
||||
// Weekday
|
||||
else if (all_of(segment, is_any_of("ecE"sv))) {
|
||||
if (segment.length() == 4)
|
||||
format.weekday = CalendarPatternStyle::Long;
|
||||
else if (segment.length() == 5)
|
||||
format.weekday = CalendarPatternStyle::Narrow;
|
||||
else
|
||||
format.weekday = CalendarPatternStyle::Short;
|
||||
}
|
||||
|
||||
// Day
|
||||
else if (all_of(segment, is_any_of("d"sv))) {
|
||||
if (segment.length() == 1)
|
||||
format.day = CalendarPatternStyle::Numeric;
|
||||
else
|
||||
format.day = CalendarPatternStyle::TwoDigit;
|
||||
} else if (all_of(segment, is_any_of("DFg"sv))) {
|
||||
format.day = CalendarPatternStyle::Numeric;
|
||||
}
|
||||
|
||||
// Day period
|
||||
else if (all_of(segment, is_any_of("B"sv))) {
|
||||
if (segment.length() == 4)
|
||||
format.day_period = CalendarPatternStyle::Long;
|
||||
else if (segment.length() == 5)
|
||||
format.day_period = CalendarPatternStyle::Narrow;
|
||||
else
|
||||
format.day_period = CalendarPatternStyle::Short;
|
||||
}
|
||||
|
||||
// Hour
|
||||
else if (all_of(segment, is_any_of("hHKk"sv))) {
|
||||
switch (starting_char) {
|
||||
case 'K':
|
||||
format.hour_cycle = HourCycle::H11;
|
||||
break;
|
||||
case 'h':
|
||||
format.hour_cycle = HourCycle::H12;
|
||||
break;
|
||||
case 'H':
|
||||
format.hour_cycle = HourCycle::H23;
|
||||
break;
|
||||
case 'k':
|
||||
format.hour_cycle = HourCycle::H24;
|
||||
break;
|
||||
}
|
||||
|
||||
if (segment.length() == 1)
|
||||
format.hour = CalendarPatternStyle::Numeric;
|
||||
else
|
||||
format.hour = CalendarPatternStyle::TwoDigit;
|
||||
}
|
||||
|
||||
// Minute
|
||||
else if (all_of(segment, is_any_of("m"sv))) {
|
||||
if (segment.length() == 1)
|
||||
format.minute = CalendarPatternStyle::Numeric;
|
||||
else
|
||||
format.minute = CalendarPatternStyle::TwoDigit;
|
||||
}
|
||||
|
||||
// Second
|
||||
else if (all_of(segment, is_any_of("s"sv))) {
|
||||
if (segment.length() == 1)
|
||||
format.second = CalendarPatternStyle::Numeric;
|
||||
else
|
||||
format.second = CalendarPatternStyle::TwoDigit;
|
||||
} else if (all_of(segment, is_any_of("S"sv))) {
|
||||
format.fractional_second_digits = static_cast<u8>(segment.length());
|
||||
}
|
||||
|
||||
// Zone
|
||||
else if (all_of(segment, is_any_of("zV"sv))) {
|
||||
if (segment.length() < 4)
|
||||
format.time_zone_name = CalendarPatternStyle::Short;
|
||||
else
|
||||
format.time_zone_name = CalendarPatternStyle::Long;
|
||||
} else if (all_of(segment, is_any_of("ZOXx"sv))) {
|
||||
if (segment.length() < 4)
|
||||
format.time_zone_name = CalendarPatternStyle::ShortOffset;
|
||||
else
|
||||
format.time_zone_name = CalendarPatternStyle::LongOffset;
|
||||
} else if (all_of(segment, is_any_of("v"sv))) {
|
||||
if (segment.length() < 4)
|
||||
format.time_zone_name = CalendarPatternStyle::ShortGeneric;
|
||||
else
|
||||
format.time_zone_name = CalendarPatternStyle::LongGeneric;
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
Optional<HourCycleRegion> __attribute__((weak)) hour_cycle_region_from_string(StringView) { return {}; }
|
||||
@ -172,171 +553,406 @@ Optional<Weekday> get_locale_weekend_end(StringView locale)
|
||||
return find_regional_values_for_locale<Optional<Weekday>>(locale, get_regional_weekend_end);
|
||||
}
|
||||
|
||||
String combine_skeletons(StringView first, StringView second)
|
||||
{
|
||||
// https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
|
||||
constexpr auto field_order = Array {
|
||||
"G"sv, // Era
|
||||
"yYuUr"sv, // Year
|
||||
"ML"sv, // Month
|
||||
"dDFg"sv, // Day
|
||||
"Eec"sv, // Weekday
|
||||
"abB"sv, // Period
|
||||
"hHKk"sv, // Hour
|
||||
"m"sv, // Minute
|
||||
"sSA"sv, // Second
|
||||
"zZOvVXx"sv, // Zone
|
||||
};
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
auto append_from_skeleton = [&](auto skeleton, auto ch) {
|
||||
auto first_index = skeleton.find(ch);
|
||||
if (!first_index.has_value())
|
||||
return false;
|
||||
|
||||
auto last_index = skeleton.find_last(ch);
|
||||
|
||||
builder.append(skeleton.substring_view(*first_index, *last_index - *first_index + 1));
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto fields : field_order) {
|
||||
for (auto ch : fields) {
|
||||
if (append_from_skeleton(first, ch))
|
||||
break;
|
||||
if (append_from_skeleton(second, ch))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return MUST(builder.to_string());
|
||||
}
|
||||
|
||||
Optional<CalendarFormat> __attribute__((weak)) get_calendar_date_format(StringView, StringView) { return {}; }
|
||||
Optional<CalendarFormat> __attribute__((weak)) get_calendar_time_format(StringView, StringView) { return {}; }
|
||||
Optional<CalendarFormat> __attribute__((weak)) get_calendar_date_time_format(StringView, StringView) { return {}; }
|
||||
|
||||
Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type)
|
||||
{
|
||||
switch (type) {
|
||||
case CalendarFormatType::Date:
|
||||
return get_calendar_date_format(locale, calendar);
|
||||
case CalendarFormatType::Time:
|
||||
return get_calendar_time_format(locale, calendar);
|
||||
case CalendarFormatType::DateTime:
|
||||
return get_calendar_date_time_format(locale, calendar);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
Vector<CalendarPattern> __attribute__((weak)) get_calendar_available_formats(StringView, StringView) { return {}; }
|
||||
Optional<CalendarRangePattern> __attribute__((weak)) get_calendar_default_range_format(StringView, StringView) { return {}; }
|
||||
Vector<CalendarRangePattern> __attribute__((weak)) get_calendar_range_formats(StringView, StringView, StringView) { return {}; }
|
||||
Vector<CalendarRangePattern> __attribute__((weak)) get_calendar_range12_formats(StringView, StringView, StringView) { return {}; }
|
||||
Optional<StringView> __attribute__((weak)) get_calendar_era_symbol(StringView, StringView, CalendarPatternStyle, Era) { return {}; }
|
||||
Optional<StringView> __attribute__((weak)) get_calendar_month_symbol(StringView, StringView, CalendarPatternStyle, Month) { return {}; }
|
||||
Optional<StringView> __attribute__((weak)) get_calendar_weekday_symbol(StringView, StringView, CalendarPatternStyle, Weekday) { return {}; }
|
||||
Optional<StringView> __attribute__((weak)) get_calendar_day_period_symbol(StringView, StringView, CalendarPatternStyle, DayPeriod) { return {}; }
|
||||
Optional<StringView> __attribute__((weak)) get_calendar_day_period_symbol_for_hour(StringView, StringView, CalendarPatternStyle, u8) { return {}; }
|
||||
|
||||
Optional<StringView> __attribute__((weak)) get_time_zone_name(StringView, StringView, CalendarPatternStyle, TimeZone::InDST) { return {}; }
|
||||
Optional<TimeZoneFormat> __attribute__((weak)) get_time_zone_format(StringView) { return {}; }
|
||||
|
||||
static Optional<String> format_time_zone_offset(StringView locale, CalendarPatternStyle style, i64 offset_seconds)
|
||||
// ICU does not contain a field enumeration for "literal" partitions. Define a custom field so that we may provide a
|
||||
// type for those partitions.
|
||||
static constexpr i32 LITERAL_FIELD = -1;
|
||||
|
||||
static constexpr StringView icu_date_time_format_field_to_string(i32 field)
|
||||
{
|
||||
auto formats = get_time_zone_format(locale);
|
||||
if (!formats.has_value())
|
||||
return {};
|
||||
switch (field) {
|
||||
case LITERAL_FIELD:
|
||||
return "literal"sv;
|
||||
case UDAT_ERA_FIELD:
|
||||
return "era"sv;
|
||||
case UDAT_YEAR_FIELD:
|
||||
case UDAT_EXTENDED_YEAR_FIELD:
|
||||
return "year"sv;
|
||||
case UDAT_YEAR_NAME_FIELD:
|
||||
return "yearName"sv;
|
||||
case UDAT_RELATED_YEAR_FIELD:
|
||||
return "relatedYear"sv;
|
||||
case UDAT_MONTH_FIELD:
|
||||
case UDAT_STANDALONE_MONTH_FIELD:
|
||||
return "month"sv;
|
||||
case UDAT_DAY_OF_WEEK_FIELD:
|
||||
case UDAT_DOW_LOCAL_FIELD:
|
||||
case UDAT_STANDALONE_DAY_FIELD:
|
||||
return "weekday"sv;
|
||||
case UDAT_DATE_FIELD:
|
||||
return "day"sv;
|
||||
case UDAT_AM_PM_FIELD:
|
||||
case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
|
||||
case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
|
||||
return "dayPeriod"sv;
|
||||
case UDAT_HOUR_OF_DAY1_FIELD:
|
||||
case UDAT_HOUR_OF_DAY0_FIELD:
|
||||
case UDAT_HOUR1_FIELD:
|
||||
case UDAT_HOUR0_FIELD:
|
||||
return "hour"sv;
|
||||
case UDAT_MINUTE_FIELD:
|
||||
return "minute"sv;
|
||||
case UDAT_SECOND_FIELD:
|
||||
return "second"sv;
|
||||
case UDAT_FRACTIONAL_SECOND_FIELD:
|
||||
return "fractionalSecond"sv;
|
||||
case UDAT_TIMEZONE_FIELD:
|
||||
case UDAT_TIMEZONE_RFC_FIELD:
|
||||
case UDAT_TIMEZONE_GENERIC_FIELD:
|
||||
case UDAT_TIMEZONE_SPECIAL_FIELD:
|
||||
case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
|
||||
case UDAT_TIMEZONE_ISO_FIELD:
|
||||
case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
|
||||
return "timeZoneName"sv;
|
||||
default:
|
||||
return "unknown"sv;
|
||||
}
|
||||
}
|
||||
|
||||
auto number_system = get_preferred_keyword_value_for_locale(locale, "nu"sv);
|
||||
if (!number_system.has_value())
|
||||
return {};
|
||||
static bool apply_hour_cycle_to_skeleton(icu::UnicodeString& skeleton, Optional<HourCycle> const& hour_cycle, Optional<bool> const& hour12)
|
||||
{
|
||||
auto hour_cycle_symbol = icu_hour_cycle(hour_cycle, hour12);
|
||||
if (hour_cycle_symbol == 'j')
|
||||
return false;
|
||||
|
||||
if (offset_seconds == 0)
|
||||
return MUST(String::from_utf8(formats->gmt_zero_format));
|
||||
bool changed_hour_cycle = false;
|
||||
bool inside_quote = false;
|
||||
|
||||
auto sign = offset_seconds > 0 ? formats->symbol_ahead_sign : formats->symbol_behind_sign;
|
||||
auto separator = offset_seconds > 0 ? formats->symbol_ahead_separator : formats->symbol_behind_separator;
|
||||
offset_seconds = llabs(offset_seconds);
|
||||
for (i32 i = 0; i < skeleton.length(); ++i) {
|
||||
switch (skeleton[i]) {
|
||||
case '\'':
|
||||
inside_quote = !inside_quote;
|
||||
break;
|
||||
|
||||
auto offset_hours = offset_seconds / 3'600;
|
||||
offset_seconds %= 3'600;
|
||||
case 'h':
|
||||
case 'H':
|
||||
case 'k':
|
||||
case 'K':
|
||||
if (!inside_quote && static_cast<char>(skeleton[i]) != hour_cycle_symbol) {
|
||||
skeleton.setCharAt(i, hour_cycle_symbol);
|
||||
changed_hour_cycle = true;
|
||||
}
|
||||
break;
|
||||
|
||||
auto offset_minutes = offset_seconds / 60;
|
||||
offset_seconds %= 60;
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append(sign);
|
||||
|
||||
switch (style) {
|
||||
// The long format always uses 2-digit hours field and minutes field, with optional 2-digit seconds field.
|
||||
case CalendarPatternStyle::LongOffset:
|
||||
builder.appendff("{:02}{}{:02}", offset_hours, separator, offset_minutes);
|
||||
if (offset_seconds > 0)
|
||||
builder.appendff("{}{:02}", separator, offset_seconds);
|
||||
break;
|
||||
|
||||
// The short format is intended for the shortest representation and uses hour fields without leading zero, with optional 2-digit minutes and seconds fields.
|
||||
case CalendarPatternStyle::ShortOffset:
|
||||
builder.appendff("{}", offset_hours);
|
||||
if (offset_minutes > 0) {
|
||||
builder.appendff("{}{:02}", separator, offset_minutes);
|
||||
if (offset_seconds > 0)
|
||||
builder.appendff("{}{:02}", separator, offset_seconds);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// The digits used for hours, minutes and seconds fields in this format are the locale's default decimal digits.
|
||||
auto result = replace_digits_for_number_system(*number_system, builder.string_view());
|
||||
return MUST(MUST(String::from_utf8(formats->gmt_format)).replace("{0}"sv, result, ReplaceMode::FirstOnly));
|
||||
return changed_hour_cycle;
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr35/tr35-dates.html#Time_Zone_Format_Terminology
|
||||
String format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::UnixDateTime time)
|
||||
static void apply_time_zone_to_formatter(icu::SimpleDateFormat& formatter, icu::Locale const& locale, StringView time_zone_identifier)
|
||||
{
|
||||
auto offset = TimeZone::get_time_zone_offset(time_zone, time);
|
||||
if (!offset.has_value())
|
||||
return MUST(String::from_utf8(time_zone));
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
switch (style) {
|
||||
case CalendarPatternStyle::Short:
|
||||
case CalendarPatternStyle::Long:
|
||||
case CalendarPatternStyle::ShortGeneric:
|
||||
case CalendarPatternStyle::LongGeneric:
|
||||
if (auto name = get_time_zone_name(locale, time_zone, style, offset->in_dst); name.has_value())
|
||||
return MUST(String::from_utf8(*name));
|
||||
break;
|
||||
auto* time_zone = icu::TimeZone::createTimeZone(icu_string(time_zone_identifier));
|
||||
|
||||
case CalendarPatternStyle::ShortOffset:
|
||||
case CalendarPatternStyle::LongOffset:
|
||||
if (auto formatted_offset = format_time_zone_offset(locale, style, offset->seconds); formatted_offset.has_value())
|
||||
return formatted_offset.release_value();
|
||||
return MUST(String::from_utf8(time_zone));
|
||||
auto* calendar = icu::Calendar::createInstance(time_zone, locale, status);
|
||||
VERIFY(icu_success(status));
|
||||
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
if (calendar->getDynamicClassID() == icu::GregorianCalendar::getStaticClassID()) {
|
||||
// https://tc39.es/ecma262/#sec-time-values-and-time-range
|
||||
// A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds.
|
||||
static constexpr double ECMA_262_MINIMUM_TIME = -8.64E15;
|
||||
|
||||
auto* gregorian_calendar = static_cast<icu::GregorianCalendar*>(calendar);
|
||||
gregorian_calendar->setGregorianChange(ECMA_262_MINIMUM_TIME, status);
|
||||
VERIFY(icu_success(status));
|
||||
}
|
||||
|
||||
// If more styles are added, consult the following table to ensure always falling back to GMT offset is still correct:
|
||||
// https://unicode.org/reports/tr35/tr35-dates.html#dfst-zone
|
||||
switch (style) {
|
||||
case CalendarPatternStyle::Short:
|
||||
case CalendarPatternStyle::ShortGeneric:
|
||||
return format_time_zone(locale, time_zone, CalendarPatternStyle::ShortOffset, time);
|
||||
formatter.adoptCalendar(calendar);
|
||||
}
|
||||
|
||||
case CalendarPatternStyle::Long:
|
||||
case CalendarPatternStyle::LongGeneric:
|
||||
return format_time_zone(locale, time_zone, CalendarPatternStyle::LongOffset, time);
|
||||
static bool is_formatted_range_actually_a_range(icu::FormattedDateInterval const& formatted)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
auto result = formatted.toTempString(status);
|
||||
if (icu_failure(status))
|
||||
return false;
|
||||
|
||||
icu::ConstrainedFieldPosition position;
|
||||
position.constrainCategory(UFIELD_CATEGORY_DATE_INTERVAL_SPAN);
|
||||
|
||||
auto has_range = static_cast<bool>(formatted.nextPosition(position, status));
|
||||
if (icu_failure(status))
|
||||
return false;
|
||||
|
||||
return has_range;
|
||||
}
|
||||
|
||||
struct Range {
|
||||
constexpr bool contains(i32 position) const
|
||||
{
|
||||
return start <= position && position < end;
|
||||
}
|
||||
|
||||
i32 start { 0 };
|
||||
i32 end { 0 };
|
||||
};
|
||||
|
||||
class DateTimeFormatImpl : public DateTimeFormat {
|
||||
public:
|
||||
DateTimeFormatImpl(icu::Locale& locale, icu::UnicodeString const& pattern, StringView time_zone_identifier, NonnullOwnPtr<icu::SimpleDateFormat> formatter)
|
||||
: m_locale(locale)
|
||||
, m_pattern(CalendarPattern::create_from_pattern(icu_string_to_string(pattern)))
|
||||
, m_formatter(move(formatter))
|
||||
{
|
||||
apply_time_zone_to_formatter(*m_formatter, m_locale, time_zone_identifier);
|
||||
}
|
||||
|
||||
virtual ~DateTimeFormatImpl() override = default;
|
||||
|
||||
virtual CalendarPattern const& chosen_pattern() const override { return m_pattern; }
|
||||
|
||||
virtual String format(double time) const override
|
||||
{
|
||||
auto formatted_time = format_impl(time);
|
||||
if (!formatted_time.has_value())
|
||||
return {};
|
||||
|
||||
return icu_string_to_string(*formatted_time);
|
||||
}
|
||||
|
||||
virtual Vector<Partition> format_to_parts(double time) const override
|
||||
{
|
||||
icu::FieldPositionIterator iterator;
|
||||
|
||||
auto formatted_time = format_impl(time, &iterator);
|
||||
if (!formatted_time.has_value())
|
||||
return {};
|
||||
|
||||
Vector<Partition> result;
|
||||
|
||||
auto create_partition = [&](i32 field, i32 begin, i32 end) {
|
||||
Partition partition;
|
||||
partition.type = icu_date_time_format_field_to_string(field);
|
||||
partition.value = icu_string_to_string(formatted_time->tempSubStringBetween(begin, end));
|
||||
partition.source = "shared"sv;
|
||||
result.append(move(partition));
|
||||
};
|
||||
|
||||
icu::FieldPosition position;
|
||||
i32 previous_end_index = 0;
|
||||
|
||||
while (static_cast<bool>(iterator.next(position))) {
|
||||
if (previous_end_index < position.getBeginIndex())
|
||||
create_partition(LITERAL_FIELD, previous_end_index, position.getBeginIndex());
|
||||
if (position.getField() >= 0)
|
||||
create_partition(position.getField(), position.getBeginIndex(), position.getEndIndex());
|
||||
|
||||
previous_end_index = position.getEndIndex();
|
||||
}
|
||||
|
||||
if (previous_end_index < formatted_time->length())
|
||||
create_partition(LITERAL_FIELD, previous_end_index, formatted_time->length());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual String format_range(double start, double end) const override
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto formatted = format_range_impl(start, end);
|
||||
if (!formatted.has_value())
|
||||
return {};
|
||||
|
||||
if (!is_formatted_range_actually_a_range(*formatted))
|
||||
return format(start);
|
||||
|
||||
auto formatted_time = formatted->toTempString(status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
return icu_string_to_string(formatted_time);
|
||||
}
|
||||
|
||||
virtual Vector<Partition> format_range_to_parts(double start, double end) const override
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto formatted = format_range_impl(start, end);
|
||||
if (!formatted.has_value())
|
||||
return {};
|
||||
|
||||
if (!is_formatted_range_actually_a_range(*formatted))
|
||||
return format_to_parts(start);
|
||||
|
||||
auto formatted_time = formatted->toTempString(status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
icu::ConstrainedFieldPosition position;
|
||||
i32 previous_end_index = 0;
|
||||
|
||||
Vector<Partition> result;
|
||||
Optional<Range> start_range;
|
||||
Optional<Range> end_range;
|
||||
|
||||
auto create_partition = [&](i32 field, i32 begin, i32 end) {
|
||||
Partition partition;
|
||||
partition.type = icu_date_time_format_field_to_string(field);
|
||||
partition.value = icu_string_to_string(formatted_time.tempSubStringBetween(begin, end));
|
||||
|
||||
if (start_range.has_value() && start_range->contains(begin))
|
||||
partition.source = "startRange"sv;
|
||||
else if (end_range.has_value() && end_range->contains(begin))
|
||||
partition.source = "endRange"sv;
|
||||
else
|
||||
partition.source = "shared"sv;
|
||||
|
||||
result.append(move(partition));
|
||||
};
|
||||
|
||||
while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
|
||||
if (previous_end_index < position.getStart())
|
||||
create_partition(LITERAL_FIELD, previous_end_index, position.getStart());
|
||||
|
||||
if (position.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
|
||||
auto& range = position.getField() == 0 ? start_range : end_range;
|
||||
range = Range { position.getStart(), position.getLimit() };
|
||||
} else if (position.getCategory() == UFIELD_CATEGORY_DATE) {
|
||||
create_partition(position.getField(), position.getStart(), position.getLimit());
|
||||
}
|
||||
|
||||
previous_end_index = position.getLimit();
|
||||
}
|
||||
|
||||
if (previous_end_index < formatted_time.length())
|
||||
create_partition(LITERAL_FIELD, previous_end_index, formatted_time.length());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
Optional<icu::UnicodeString> format_impl(double time, icu::FieldPositionIterator* iterator = nullptr) const
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
icu::UnicodeString formatted_time;
|
||||
|
||||
m_formatter->format(time, formatted_time, iterator, status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
return formatted_time;
|
||||
}
|
||||
|
||||
Optional<icu::FormattedDateInterval> format_range_impl(double start, double end) const
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
if (!m_range_formatter) {
|
||||
icu::UnicodeString pattern;
|
||||
m_formatter->toPattern(pattern);
|
||||
|
||||
auto skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
auto* formatter = icu::DateIntervalFormat::createInstance(skeleton, m_locale, status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
m_range_formatter = adopt_own(*formatter);
|
||||
m_range_formatter->setTimeZone(m_formatter->getTimeZone());
|
||||
}
|
||||
|
||||
auto start_calendar = adopt_own(*m_formatter->getCalendar()->clone());
|
||||
start_calendar->setTime(start, status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
auto end_calendar = adopt_own(*m_formatter->getCalendar()->clone());
|
||||
end_calendar->setTime(end, status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
auto formatted = m_range_formatter->formatToValue(*start_calendar, *end_calendar, status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
icu::Locale& m_locale;
|
||||
CalendarPattern m_pattern;
|
||||
|
||||
NonnullOwnPtr<icu::SimpleDateFormat> m_formatter;
|
||||
mutable OwnPtr<icu::DateIntervalFormat> m_range_formatter;
|
||||
};
|
||||
|
||||
NonnullOwnPtr<DateTimeFormat> DateTimeFormat::create_for_date_and_time_style(
|
||||
StringView locale,
|
||||
StringView time_zone_identifier,
|
||||
Optional<HourCycle> const& hour_cycle,
|
||||
Optional<bool> const& hour12,
|
||||
Optional<DateTimeStyle> const& date_style,
|
||||
Optional<DateTimeStyle> const& time_style)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto locale_data = LocaleData::for_locale(locale);
|
||||
VERIFY(locale_data.has_value());
|
||||
|
||||
auto formatter = adopt_own(*verify_cast<icu::SimpleDateFormat>([&]() {
|
||||
if (date_style.has_value() && time_style.has_value()) {
|
||||
return icu::DateFormat::createDateTimeInstance(
|
||||
icu_date_time_style(*date_style), icu_date_time_style(*time_style), locale_data->locale());
|
||||
}
|
||||
if (date_style.has_value()) {
|
||||
return icu::DateFormat::createDateInstance(
|
||||
icu_date_time_style(*date_style), locale_data->locale());
|
||||
}
|
||||
if (time_style.has_value()) {
|
||||
return icu::DateFormat::createTimeInstance(
|
||||
icu_date_time_style(*time_style), locale_data->locale());
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}()));
|
||||
|
||||
icu::UnicodeString pattern;
|
||||
formatter->toPattern(pattern);
|
||||
|
||||
auto skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
|
||||
VERIFY(icu_success(status));
|
||||
|
||||
if (apply_hour_cycle_to_skeleton(skeleton, hour_cycle, hour12)) {
|
||||
pattern = locale_data->date_time_pattern_generator().getBestPattern(skeleton, UDATPG_MATCH_ALL_FIELDS_LENGTH, status);
|
||||
VERIFY(icu_success(status));
|
||||
|
||||
apply_hour_cycle_to_skeleton(pattern, hour_cycle, hour12);
|
||||
|
||||
formatter = adopt_own(*new icu::SimpleDateFormat(pattern, locale_data->locale(), status));
|
||||
VERIFY(icu_success(status));
|
||||
}
|
||||
|
||||
return adopt_own(*new DateTimeFormatImpl(locale_data->locale(), pattern, time_zone_identifier, move(formatter)));
|
||||
}
|
||||
|
||||
NonnullOwnPtr<DateTimeFormat> DateTimeFormat::create_for_pattern_options(
|
||||
StringView locale,
|
||||
StringView time_zone_identifier,
|
||||
CalendarPattern const& options)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto locale_data = LocaleData::for_locale(locale);
|
||||
VERIFY(locale_data.has_value());
|
||||
|
||||
auto skeleton = icu_string(options.to_pattern());
|
||||
auto pattern = locale_data->date_time_pattern_generator().getBestPattern(skeleton, UDATPG_MATCH_ALL_FIELDS_LENGTH, status);
|
||||
VERIFY(icu_success(status));
|
||||
|
||||
apply_hour_cycle_to_skeleton(pattern, options.hour_cycle, {});
|
||||
|
||||
auto formatter = adopt_own(*new icu::SimpleDateFormat(pattern, locale_data->locale(), status));
|
||||
VERIFY(icu_success(status));
|
||||
|
||||
return adopt_own(*new DateTimeFormatImpl(locale_data->locale(), pattern, time_zone_identifier, move(formatter)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@ -17,25 +17,14 @@
|
||||
|
||||
namespace Locale {
|
||||
|
||||
enum class Era : u8 {
|
||||
BC,
|
||||
AD,
|
||||
};
|
||||
|
||||
enum class Month : u8 {
|
||||
January,
|
||||
February,
|
||||
March,
|
||||
April,
|
||||
May,
|
||||
June,
|
||||
July,
|
||||
August,
|
||||
September,
|
||||
October,
|
||||
November,
|
||||
December,
|
||||
enum class DateTimeStyle {
|
||||
Full,
|
||||
Long,
|
||||
Medium,
|
||||
Short,
|
||||
};
|
||||
DateTimeStyle date_time_style_from_string(StringView);
|
||||
StringView date_time_style_to_string(DateTimeStyle);
|
||||
|
||||
enum class Weekday : u8 {
|
||||
Sunday,
|
||||
@ -47,26 +36,14 @@ enum class Weekday : u8 {
|
||||
Saturday,
|
||||
};
|
||||
|
||||
enum class DayPeriod : u8 {
|
||||
AM,
|
||||
PM,
|
||||
Noon,
|
||||
Morning1,
|
||||
Morning2,
|
||||
Afternoon1,
|
||||
Afternoon2,
|
||||
Evening1,
|
||||
Evening2,
|
||||
Night1,
|
||||
Night2,
|
||||
};
|
||||
|
||||
enum class HourCycle : u8 {
|
||||
H11,
|
||||
H12,
|
||||
H23,
|
||||
H24,
|
||||
};
|
||||
HourCycle hour_cycle_from_string(StringView hour_cycle);
|
||||
StringView hour_cycle_to_string(HourCycle hour_cycle);
|
||||
|
||||
enum class CalendarPatternStyle : u8 {
|
||||
Narrow,
|
||||
@ -79,95 +56,45 @@ enum class CalendarPatternStyle : u8 {
|
||||
ShortGeneric,
|
||||
LongGeneric,
|
||||
};
|
||||
CalendarPatternStyle calendar_pattern_style_from_string(StringView style);
|
||||
StringView calendar_pattern_style_to_string(CalendarPatternStyle style);
|
||||
|
||||
struct CalendarPattern {
|
||||
enum class Field {
|
||||
Era,
|
||||
Year,
|
||||
Month,
|
||||
Weekday,
|
||||
Day,
|
||||
DayPeriod,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
FractionalSecondDigits,
|
||||
TimeZoneName,
|
||||
};
|
||||
static CalendarPattern create_from_pattern(StringView);
|
||||
String to_pattern() const;
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_calendar_field_zipped_with(CalendarPattern const& other, Callback&& callback)
|
||||
{
|
||||
callback(era, other.era, Field::Era);
|
||||
callback(year, other.year, Field::Year);
|
||||
callback(month, other.month, Field::Month);
|
||||
callback(weekday, other.weekday, Field::Weekday);
|
||||
callback(day, other.day, Field::Day);
|
||||
callback(day_period, other.day_period, Field::DayPeriod);
|
||||
callback(hour, other.hour, Field::Hour);
|
||||
callback(minute, other.minute, Field::Minute);
|
||||
callback(second, other.second, Field::Second);
|
||||
callback(fractional_second_digits, other.fractional_second_digits, Field::FractionalSecondDigits);
|
||||
callback(time_zone_name, other.time_zone_name, Field::TimeZoneName);
|
||||
callback(hour_cycle, other.hour_cycle);
|
||||
callback(era, other.era);
|
||||
callback(year, other.year);
|
||||
callback(month, other.month);
|
||||
callback(weekday, other.weekday);
|
||||
callback(day, other.day);
|
||||
callback(day_period, other.day_period);
|
||||
callback(hour, other.hour);
|
||||
callback(minute, other.minute);
|
||||
callback(second, other.second);
|
||||
callback(fractional_second_digits, other.fractional_second_digits);
|
||||
callback(time_zone_name, other.time_zone_name);
|
||||
}
|
||||
|
||||
String skeleton {};
|
||||
String pattern {};
|
||||
Optional<String> pattern12 {};
|
||||
Optional<HourCycle> hour_cycle {};
|
||||
Optional<HourCycle> hour_cycle;
|
||||
Optional<bool> hour12;
|
||||
|
||||
// https://unicode.org/reports/tr35/tr35-dates.html#Calendar_Fields
|
||||
Optional<CalendarPatternStyle> era {};
|
||||
Optional<CalendarPatternStyle> year {};
|
||||
Optional<CalendarPatternStyle> month {};
|
||||
Optional<CalendarPatternStyle> weekday {};
|
||||
Optional<CalendarPatternStyle> day {};
|
||||
Optional<CalendarPatternStyle> day_period {};
|
||||
Optional<CalendarPatternStyle> hour {};
|
||||
Optional<CalendarPatternStyle> minute {};
|
||||
Optional<CalendarPatternStyle> second {};
|
||||
Optional<u8> fractional_second_digits {};
|
||||
Optional<CalendarPatternStyle> time_zone_name {};
|
||||
};
|
||||
|
||||
struct CalendarRangePattern : public CalendarPattern {
|
||||
enum class Field {
|
||||
Era,
|
||||
Year,
|
||||
Month,
|
||||
Day,
|
||||
AmPm,
|
||||
DayPeriod,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
FractionalSecondDigits,
|
||||
};
|
||||
|
||||
Optional<Field> field {};
|
||||
String start_range {};
|
||||
StringView separator {};
|
||||
String end_range {};
|
||||
};
|
||||
|
||||
enum class CalendarFormatType : u8 {
|
||||
Date,
|
||||
Time,
|
||||
DateTime,
|
||||
};
|
||||
|
||||
struct CalendarFormat {
|
||||
CalendarPattern full_format {};
|
||||
CalendarPattern long_format {};
|
||||
CalendarPattern medium_format {};
|
||||
CalendarPattern short_format {};
|
||||
};
|
||||
|
||||
enum class CalendarSymbol : u8 {
|
||||
DayPeriod,
|
||||
Era,
|
||||
Month,
|
||||
Weekday,
|
||||
Optional<CalendarPatternStyle> era;
|
||||
Optional<CalendarPatternStyle> year;
|
||||
Optional<CalendarPatternStyle> month;
|
||||
Optional<CalendarPatternStyle> weekday;
|
||||
Optional<CalendarPatternStyle> day;
|
||||
Optional<CalendarPatternStyle> day_period;
|
||||
Optional<CalendarPatternStyle> hour;
|
||||
Optional<CalendarPatternStyle> minute;
|
||||
Optional<CalendarPatternStyle> second;
|
||||
Optional<u8> fractional_second_digits;
|
||||
Optional<CalendarPatternStyle> time_zone_name;
|
||||
};
|
||||
|
||||
struct TimeZoneFormat {
|
||||
@ -181,12 +108,6 @@ struct TimeZoneFormat {
|
||||
StringView gmt_zero_format {};
|
||||
};
|
||||
|
||||
HourCycle hour_cycle_from_string(StringView hour_cycle);
|
||||
StringView hour_cycle_to_string(HourCycle hour_cycle);
|
||||
|
||||
CalendarPatternStyle calendar_pattern_style_from_string(StringView style);
|
||||
StringView calendar_pattern_style_to_string(CalendarPatternStyle style);
|
||||
|
||||
Optional<HourCycleRegion> hour_cycle_region_from_string(StringView hour_cycle_region);
|
||||
Vector<HourCycle> get_regional_hour_cycles(StringView region);
|
||||
Vector<HourCycle> get_locale_hour_cycles(StringView locale);
|
||||
@ -208,25 +129,42 @@ Optional<WeekendEndRegion> weekend_end_region_from_string(StringView weekend_end
|
||||
Optional<Weekday> get_regional_weekend_end(StringView region);
|
||||
Optional<Weekday> get_locale_weekend_end(StringView locale);
|
||||
|
||||
String combine_skeletons(StringView first, StringView second);
|
||||
|
||||
Optional<CalendarFormat> get_calendar_date_format(StringView locale, StringView calendar);
|
||||
Optional<CalendarFormat> get_calendar_time_format(StringView locale, StringView calendar);
|
||||
Optional<CalendarFormat> get_calendar_date_time_format(StringView locale, StringView calendar);
|
||||
Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type);
|
||||
Vector<CalendarPattern> get_calendar_available_formats(StringView locale, StringView calendar);
|
||||
Optional<CalendarRangePattern> get_calendar_default_range_format(StringView locale, StringView calendar);
|
||||
Vector<CalendarRangePattern> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton);
|
||||
Vector<CalendarRangePattern> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton);
|
||||
|
||||
Optional<StringView> get_calendar_era_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Era value);
|
||||
Optional<StringView> get_calendar_month_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Month value);
|
||||
Optional<StringView> get_calendar_weekday_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Weekday value);
|
||||
Optional<StringView> get_calendar_day_period_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, DayPeriod value);
|
||||
Optional<StringView> get_calendar_day_period_symbol_for_hour(StringView locale, StringView calendar, CalendarPatternStyle style, u8 hour);
|
||||
|
||||
String format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::UnixDateTime time);
|
||||
Optional<StringView> get_time_zone_name(StringView locale, StringView time_zone, CalendarPatternStyle style, TimeZone::InDST in_dst);
|
||||
Optional<TimeZoneFormat> get_time_zone_format(StringView locale);
|
||||
|
||||
class DateTimeFormat {
|
||||
public:
|
||||
static NonnullOwnPtr<DateTimeFormat> create_for_date_and_time_style(
|
||||
StringView locale,
|
||||
StringView time_zone_identifier,
|
||||
Optional<HourCycle> const& hour_cycle,
|
||||
Optional<bool> const& hour12,
|
||||
Optional<DateTimeStyle> const& date_style,
|
||||
Optional<DateTimeStyle> const& time_style);
|
||||
|
||||
static NonnullOwnPtr<DateTimeFormat> create_for_pattern_options(
|
||||
StringView locale,
|
||||
StringView time_zone_identifier,
|
||||
CalendarPattern const&);
|
||||
|
||||
virtual ~DateTimeFormat() = default;
|
||||
|
||||
struct Partition {
|
||||
StringView type;
|
||||
String value;
|
||||
StringView source;
|
||||
};
|
||||
|
||||
virtual CalendarPattern const& chosen_pattern() const = 0;
|
||||
|
||||
virtual String format(double) const = 0;
|
||||
virtual Vector<Partition> format_to_parts(double) const = 0;
|
||||
|
||||
virtual String format_range(double, double) const = 0;
|
||||
virtual Vector<Partition> format_range_to_parts(double, double) const = 0;
|
||||
|
||||
protected:
|
||||
DateTimeFormat() = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -10,11 +10,7 @@
|
||||
|
||||
namespace Locale {
|
||||
|
||||
enum class CalendarFormatType : u8;
|
||||
enum class CalendarPatternStyle : u8;
|
||||
enum class CalendarSymbol : u8;
|
||||
enum class DayPeriod : u8;
|
||||
enum class Era : u8;
|
||||
enum class FirstDayRegion : u8;
|
||||
enum class HourCycle : u8;
|
||||
enum class HourCycleRegion : u16;
|
||||
@ -27,7 +23,6 @@ enum class KeywordHours : u8;
|
||||
enum class KeywordNumbers : u8;
|
||||
enum class Locale : u16;
|
||||
enum class MinimumDaysRegion : u8;
|
||||
enum class Month : u8;
|
||||
enum class NumericSymbol : u8;
|
||||
enum class PluralCategory : u8;
|
||||
enum class Style : u8;
|
||||
@ -37,9 +32,7 @@ enum class WeekendStartRegion : u8;
|
||||
|
||||
class NumberFormat;
|
||||
|
||||
struct CalendarFormat;
|
||||
struct CalendarPattern;
|
||||
struct CalendarRangePattern;
|
||||
struct Keyword;
|
||||
struct LanguageID;
|
||||
struct ListFormatPart;
|
||||
|
@ -85,6 +85,11 @@ icu::DateTimePatternGenerator& LocaleData::date_time_pattern_generator()
|
||||
return *m_date_time_pattern_generator;
|
||||
}
|
||||
|
||||
icu::UnicodeString icu_string(StringView string)
|
||||
{
|
||||
return icu::UnicodeString::fromUTF8(icu_string_piece(string));
|
||||
}
|
||||
|
||||
icu::StringPiece icu_string_piece(StringView string)
|
||||
{
|
||||
return { string.characters_without_null_termination(), static_cast<i32>(string.length()) };
|
||||
|
@ -60,6 +60,7 @@ static constexpr bool icu_failure(UErrorCode code)
|
||||
return static_cast<bool>(U_FAILURE(code));
|
||||
}
|
||||
|
||||
icu::UnicodeString icu_string(StringView string);
|
||||
icu::StringPiece icu_string_piece(StringView string);
|
||||
|
||||
Vector<icu::UnicodeString> icu_string_list(ReadonlySpan<String> strings);
|
||||
|
Loading…
Reference in New Issue
Block a user