mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-08 05:27:14 +09:00
274 lines
10 KiB
C++
274 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2021-2025, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/QuickSort.h>
|
|
#include <LibJS/Runtime/Array.h>
|
|
#include <LibJS/Runtime/Intl/Locale.h>
|
|
#include <LibUnicode/DateTimeFormat.h>
|
|
#include <LibUnicode/TimeZone.h>
|
|
#include <LibUnicode/UnicodeKeywords.h>
|
|
|
|
namespace JS::Intl {
|
|
|
|
GC_DEFINE_ALLOCATOR(Locale);
|
|
|
|
GC::Ref<Locale> Locale::create(Realm& realm, GC::Ref<Locale> source_locale, String locale_tag)
|
|
{
|
|
auto locale = realm.create<Locale>(realm.intrinsics().intl_locale_prototype());
|
|
|
|
locale->set_locale(move(locale_tag));
|
|
locale->m_calendar = source_locale->m_calendar;
|
|
locale->m_case_first = source_locale->m_case_first;
|
|
locale->m_collation = source_locale->m_collation;
|
|
locale->m_hour_cycle = source_locale->m_hour_cycle;
|
|
locale->m_numbering_system = source_locale->m_numbering_system;
|
|
locale->m_numeric = source_locale->m_numeric;
|
|
|
|
return locale;
|
|
}
|
|
|
|
// 15 Locale Objects, https://tc39.es/ecma402/#locale-objects
|
|
Locale::Locale(Object& prototype)
|
|
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
|
{
|
|
}
|
|
|
|
Unicode::LocaleID const& Locale::locale_id() const
|
|
{
|
|
if (!m_cached_locale_id.has_value())
|
|
m_cached_locale_id = Unicode::parse_unicode_locale_id(locale());
|
|
return *m_cached_locale_id;
|
|
}
|
|
|
|
// 1.1.1 CreateArrayFromListOrRestricted ( list , restricted )
|
|
static GC::Ref<Array> create_array_from_list_or_restricted(VM& vm, Vector<String> list, Optional<String> restricted)
|
|
{
|
|
auto& realm = *vm.current_realm();
|
|
|
|
// 1. If restricted is not undefined, then
|
|
if (restricted.has_value()) {
|
|
// a. Set list to « restricted ».
|
|
list = { restricted.release_value() };
|
|
}
|
|
|
|
// 2. Return CreateArrayFromList( list ).
|
|
return Array::create_from<String>(realm, list, [&vm](auto value) {
|
|
return PrimitiveString::create(vm, move(value));
|
|
});
|
|
}
|
|
|
|
// 1.1.2 CalendarsOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-calendars-of-locale
|
|
GC::Ref<Array> calendars_of_locale(VM& vm, Locale const& locale_object)
|
|
{
|
|
// 1. Let restricted be loc.[[Calendar]].
|
|
Optional<String> restricted = locale_object.has_calendar() ? locale_object.calendar() : Optional<String> {};
|
|
|
|
// 2. Let locale be loc.[[Locale]].
|
|
auto const& locale = locale_object.locale();
|
|
|
|
// 3. Let list be a List of one or more unique calendar types in canonical form (10), sorted in descending preference
|
|
// of those in common use for date and time formatting in locale.
|
|
auto list = Unicode::available_calendars(locale);
|
|
|
|
// 4. Return CreateArrayFromListOrRestricted( list, restricted ).
|
|
return create_array_from_list_or_restricted(vm, move(list), move(restricted));
|
|
}
|
|
|
|
// 1.1.3 CollationsOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-collations-of-locale
|
|
GC::Ref<Array> collations_of_locale(VM& vm, Locale const& locale_object)
|
|
{
|
|
// 1. Let restricted be loc.[[Collation]].
|
|
Optional<String> restricted = locale_object.has_collation() ? locale_object.collation() : Optional<String> {};
|
|
|
|
// 2. Let locale be loc.[[Locale]].
|
|
auto const& locale = locale_object.locale();
|
|
|
|
// 3. Let list be a List of one or more unique collation types in canonical form (9), of those in common use for
|
|
// string comparison in locale. The values "standard" and "search" must be excluded from list. The list is sorted
|
|
// according to lexicographic code unit order.
|
|
auto list = Unicode::available_collations(locale);
|
|
|
|
// 4. Return CreateArrayFromListOrRestricted( list, restricted ).
|
|
return create_array_from_list_or_restricted(vm, move(list), move(restricted));
|
|
}
|
|
|
|
// 1.1.4 HourCyclesOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-hour-cycles-of-locale
|
|
GC::Ref<Array> hour_cycles_of_locale(VM& vm, Locale const& locale_object)
|
|
{
|
|
// 1. Let restricted be loc.[[HourCycle]].
|
|
Optional<String> restricted = locale_object.has_hour_cycle() ? locale_object.hour_cycle() : Optional<String> {};
|
|
|
|
// 2. Let locale be loc.[[Locale]].
|
|
auto const& locale = locale_object.locale();
|
|
|
|
// 3. Let list be a List of one or more unique hour cycle identifiers, which must be lower case String values
|
|
// indicating either the 12-hour format ("h11", "h12") or the 24-hour format ("h23", "h24"), sorted in descending
|
|
// preference of those in common use for date and time formatting in locale.
|
|
auto list = Unicode::available_hour_cycles(locale);
|
|
|
|
// 4. Return CreateArrayFromListOrRestricted( list, restricted ).
|
|
return create_array_from_list_or_restricted(vm, move(list), move(restricted));
|
|
}
|
|
|
|
// 1.1.5 NumberingSystemsOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-numbering-systems-of-locale
|
|
GC::Ref<Array> numbering_systems_of_locale(VM& vm, Locale const& locale_object)
|
|
{
|
|
// 1. Let restricted be loc.[[NumberingSystem]].
|
|
Optional<String> restricted = locale_object.has_numbering_system() ? locale_object.numbering_system() : Optional<String> {};
|
|
|
|
// 2. Let locale be loc.[[Locale]].
|
|
auto const& locale = locale_object.locale();
|
|
|
|
// 3. Let list be a List of one or more unique numbering system identifiers in canonical form (8), sorted in
|
|
// descending preference of those in common use for formatting numeric values in locale.
|
|
auto list = Unicode::available_number_systems(locale);
|
|
|
|
// 4. Return CreateArrayFromListOrRestricted( list, restricted ).
|
|
return create_array_from_list_or_restricted(vm, move(list), move(restricted));
|
|
}
|
|
|
|
// 1.1.6 TimeZonesOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-time-zones-of-locale
|
|
GC::Ref<Array> time_zones_of_locale(VM& vm, Locale const& locale_object)
|
|
{
|
|
auto& realm = *vm.current_realm();
|
|
|
|
// 1. Let region be GetLocaleRegion(loc.[[Locale]]).
|
|
auto const& region = locale_object.locale_id().language_id.region;
|
|
|
|
// 2. Assert: region is not undefined.
|
|
VERIFY(region.has_value());
|
|
|
|
// 3. Let list be a List of unique canonical time zone identifiers, which must be String values indicating a
|
|
// canonical Zone name of the IANA Time Zone Database, of those in common use in region. The list is empty if no
|
|
// time zones are commonly used in region. The list is sorted according to lexicographic code unit order.
|
|
auto list = Unicode::available_time_zones_in_region(*region);
|
|
|
|
// 4. Return CreateArrayFromList( list ).
|
|
return Array::create_from<String>(realm, list, [&vm](auto value) {
|
|
return PrimitiveString::create(vm, move(value));
|
|
});
|
|
}
|
|
|
|
struct FirstDayStringAndValue {
|
|
StringView weekday;
|
|
StringView string;
|
|
u8 value { 0 };
|
|
};
|
|
|
|
// Table 1: First Day String and Value, https://tc39.es/proposal-intl-locale-info/#table-locale-first-day-option-value
|
|
static constexpr auto first_day_string_and_value_table = to_array<FirstDayStringAndValue>({
|
|
{ "0"sv, "sun"sv, 7 },
|
|
{ "1"sv, "mon"sv, 1 },
|
|
{ "2"sv, "tue"sv, 2 },
|
|
{ "3"sv, "wed"sv, 3 },
|
|
{ "4"sv, "thu"sv, 4 },
|
|
{ "5"sv, "fri"sv, 5 },
|
|
{ "6"sv, "sat"sv, 6 },
|
|
{ "7"sv, "sun"sv, 7 },
|
|
});
|
|
|
|
// 1.1.8 WeekdayToString ( fw ), https://tc39.es/proposal-intl-locale-info/#sec-weekday-to-string
|
|
StringView weekday_to_string(StringView weekday)
|
|
{
|
|
// 1. For each row of Table 1, except the header row, in table order, do
|
|
for (auto const& row : first_day_string_and_value_table) {
|
|
// a. Let w be the name given in the Weekday column of the current row.
|
|
// b. Let s be the name given in the String column of the current row.
|
|
// c. If fw is equal to w, return s.
|
|
if (weekday == row.weekday)
|
|
return row.string;
|
|
}
|
|
|
|
// 2. Return fw.
|
|
return weekday;
|
|
}
|
|
|
|
// 1.1.9 StringToWeekdayValue ( fw ), https://tc39.es/proposal-intl-locale-info/#sec-string-to-weekday-value
|
|
Optional<u8> string_to_weekday_value(StringView weekday)
|
|
{
|
|
// 1. For each row of Table 1, except the header row, in table order, do
|
|
for (auto const& row : first_day_string_and_value_table) {
|
|
// a. Let s be the name given in the String column of the current row.
|
|
// b. Let v be the name given in the Value column of the current row.
|
|
// c. If fw is equal to s, return v.
|
|
if (weekday == row.string)
|
|
return row.value;
|
|
}
|
|
|
|
// 2. Return undefined.
|
|
return {};
|
|
}
|
|
|
|
static u8 weekday_to_integer(Optional<Unicode::Weekday> const& weekday, Unicode::Weekday falllback)
|
|
{
|
|
// NOTE: This fallback will be used if the ICU data lookup failed. Its value should be that of the
|
|
// default region ("001") in the CLDR.
|
|
switch (weekday.value_or(falllback)) {
|
|
case Unicode::Weekday::Monday:
|
|
return 1;
|
|
case Unicode::Weekday::Tuesday:
|
|
return 2;
|
|
case Unicode::Weekday::Wednesday:
|
|
return 3;
|
|
case Unicode::Weekday::Thursday:
|
|
return 4;
|
|
case Unicode::Weekday::Friday:
|
|
return 5;
|
|
case Unicode::Weekday::Saturday:
|
|
return 6;
|
|
case Unicode::Weekday::Sunday:
|
|
return 7;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
static Vector<u8> weekend_of_locale(ReadonlySpan<Unicode::Weekday> const& weekend_days)
|
|
{
|
|
Vector<u8> weekend;
|
|
weekend.ensure_capacity(weekend_days.size());
|
|
|
|
for (auto day : weekend_days)
|
|
weekend.unchecked_append(weekday_to_integer(day, day));
|
|
|
|
quick_sort(weekend);
|
|
return weekend;
|
|
}
|
|
|
|
// 1.1.10 WeekInfoOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-week-info-of-locale
|
|
WeekInfo week_info_of_locale(Locale const& locale_object)
|
|
{
|
|
// 1. Let locale be loc.[[Locale]].
|
|
auto const& locale = locale_object.locale();
|
|
|
|
// 2. Let r be a record whose fields are defined by Table 2, with values based on locale.
|
|
auto locale_week_info = Unicode::week_info_of_locale(locale);
|
|
|
|
WeekInfo week_info {};
|
|
week_info.minimal_days = locale_week_info.minimal_days_in_first_week;
|
|
week_info.first_day = weekday_to_integer(locale_week_info.first_day_of_week, Unicode::Weekday::Monday);
|
|
week_info.weekend = weekend_of_locale(locale_week_info.weekend_days);
|
|
|
|
Optional<u8> first_day_of_week;
|
|
|
|
if (locale_object.has_first_day_of_week()) {
|
|
// 3. Let fws be loc.[[FirstDayOfWeek]].
|
|
auto const& first_day_of_week_string = locale_object.first_day_of_week();
|
|
|
|
// 4. Let fw be StringToWeekdayValue(fws).
|
|
first_day_of_week = string_to_weekday_value(first_day_of_week_string);
|
|
}
|
|
|
|
// 5. If fw is not undefined, then
|
|
if (first_day_of_week.has_value()) {
|
|
// a. Set r.[[FirstDay]] to fw.
|
|
week_info.first_day = *first_day_of_week;
|
|
}
|
|
|
|
// 6. Return r.
|
|
return week_info;
|
|
}
|
|
|
|
}
|