2021-09-02 08:32:43 -04:00
/*
2025-02-28 08:54:42 -05:00
* Copyright ( c ) 2021 - 2025 , Tim Flynn < trflynn89 @ ladybird . org >
2021-09-02 08:32:43 -04:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-07-05 16:20:36 -04:00
# include <AK/QuickSort.h>
2022-07-05 12:10:24 -04:00
# include <LibJS/Runtime/Array.h>
2021-09-02 08:32:43 -04:00
# include <LibJS/Runtime/Intl/Locale.h>
2024-06-23 09:14:27 -04:00
# include <LibUnicode/DateTimeFormat.h>
2024-06-25 11:33:26 -04:00
# include <LibUnicode/TimeZone.h>
2024-06-23 09:14:27 -04:00
# include <LibUnicode/UnicodeKeywords.h>
2021-09-02 08:32:43 -04:00
namespace JS : : Intl {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( Locale ) ;
2023-11-19 09:45:05 +01:00
2024-11-15 04:01:23 +13:00
GC : : Ref < Locale > Locale : : create ( Realm & realm , GC : : Ref < Locale > source_locale , String locale_tag )
2021-09-02 18:23:39 -04:00
{
2024-11-14 05:50:17 +13:00
auto locale = realm . create < Locale > ( realm . intrinsics ( ) . intl_locale_prototype ( ) ) ;
2024-06-08 13:23:26 -04:00
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 ;
2023-01-19 13:13:57 -05:00
return locale ;
}
2025-02-28 08:54:42 -05:00
// 15 Locale Objects, https://tc39.es/ecma402/#locale-objects
2023-01-19 13:13:57 -05:00
Locale : : Locale ( Object & prototype )
: Object ( ConstructWithPrototypeTag : : Tag , prototype )
{
2021-09-02 18:23:39 -04:00
}
2025-06-04 09:34:38 -04:00
Unicode : : LocaleID const & Locale : : locale_id ( ) const
{
2025-09-17 15:48:22 +02:00
return m_cached_locale_id . ensure ( [ & ] { return Unicode : : parse_unicode_locale_id ( locale ( ) ) ; } ) ;
2025-06-04 09:34:38 -04:00
}
2025-06-04 10:20:10 -04:00
// 15.5.5 GetLocaleVariants ( locale ), https://tc39.es/ecma402/#sec-getlocalevariants
Optional < String > get_locale_variants ( Unicode : : LocaleID const & locale )
{
// 1. Let baseName be GetLocaleBaseName(locale).
auto const & base_name = locale . language_id ;
// 2. NOTE: Each subtag in baseName that is preceded by "-" is either a unicode_script_subtag, unicode_region_subtag,
// or unicode_variant_subtag, but any substring matched by unicode_variant_subtag is strictly longer than any
// prefix thereof which could also be matched by one of the other productions.
// 3. Let variants be the longest suffix of baseName that starts with a "-" followed by a substring that is matched
// by the unicode_variant_subtag Unicode locale nonterminal. If there is no such suffix, return undefined.
if ( base_name . variants . is_empty ( ) )
return { } ;
2026-03-13 11:21:43 -04:00
// 4. Return the substring of variants from 1.
2025-06-04 10:20:10 -04:00
return MUST ( String : : join ( " - " sv , base_name . variants ) ) ;
}
2026-03-13 11:21:43 -04:00
// 15.5.9 CalendarsOfLocale ( loc ), https://tc39.es/ecma402/#sec-calendarsoflocale
GC : : Ref < Array > calendars_of_locale ( VM & vm , Locale const & locale_object )
2022-07-05 12:10:24 -04:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2022-07-05 12:10:24 -04:00
2026-03-13 11:21:43 -04:00
// 1. If loc.[[Calendar]] is not undefined, then
if ( locale_object . has_calendar ( ) ) {
// a. Return CreateArrayFromList(« loc.[[Calendar]] »).
return Array : : create_from ( realm , { PrimitiveString : : create ( vm , locale_object . calendar ( ) ) } ) ;
2022-07-05 12:10:24 -04:00
}
2026-03-13 11:21:43 -04:00
// 2. Let preference be RegionPreference(loc.[[Locale]]).
// 3. Let region be preference.[[Region]].
// 4. Let regionOverride be preference.[[RegionOverride]].
// 5. If regionOverride is not undefined and calendar preference data for regionOverride are available, then
// a. Let lookupRegion be regionOverride.
// 6. Else,
// a. Let lookupRegion be region.
// 7. Let list be a List of unique calendar types in canonical form (6.9), sorted in descending preference of those
// in common use for date and time formatting in lookupRegion. The list is empty if no calendar preference data
// for lookupRegion is available.
// 8. If list is empty, set list to « "gregory" ».
auto list = Unicode : : available_calendars ( locale_object . locale ( ) ) ;
// 9. Return CreateArrayFromList(list).
return Array : : create_from < String > ( realm , list , [ & vm ] ( auto const & value ) {
return PrimitiveString : : create ( vm , value ) ;
2022-07-05 12:10:24 -04:00
} ) ;
}
2026-03-13 11:21:43 -04:00
// 15.5.10 CollationsOfLocale ( loc ), https://tc39.es/ecma402/#sec-collationsoflocale
2024-11-15 04:01:23 +13:00
GC : : Ref < Array > collations_of_locale ( VM & vm , Locale const & locale_object )
2022-07-05 12:28:21 -04:00
{
2026-03-13 11:21:43 -04:00
auto & realm = * vm . current_realm ( ) ;
2022-07-05 12:28:21 -04:00
2026-03-13 11:21:43 -04:00
// 1. If loc.[[Collation]] is not undefined, then
if ( locale_object . has_collation ( ) ) {
// a. Return CreateArrayFromList(« loc.[[Collation]] »).
return Array : : create_from ( realm , { PrimitiveString : : create ( vm , locale_object . collation ( ) ) } ) ;
}
2022-07-05 12:28:21 -04:00
2026-03-13 11:21:43 -04:00
// 2. Let language be GetLocaleLanguage(loc.[[Locale]]).
// 3. If language is not "und", then
// a. Let r be LookupMatchingLocaleByPrefix(%Intl.Collator%.[[AvailableLocales]], « loc.[[Locale]] »).
// b. If r is not undefined, then
// i. Let foundLocale be r.[[locale]].
// c. Else,
// i. Let foundLocale be DefaultLocale().
// d. Let foundLocaleData be %Intl.Collator%.[[SortLocaleData]].[[<foundLocale>]].
// e. Let list be a copy of foundLocaleData.[[co]].
// f. Assert: list[0] is null.
// g. Remove the first element from list.
// 4. Else,
// a. Let list be « "emoji", "eor" ».
// 5. Let sorted be a copy of list, sorted according to lexicographic code unit order.
auto list = Unicode : : available_collations ( locale_object . locale ( ) ) ;
// 6. Return CreateArrayFromList(sorted).
return Array : : create_from < String > ( realm , list , [ & vm ] ( auto const & value ) {
return PrimitiveString : : create ( vm , value ) ;
} ) ;
2022-07-05 12:28:21 -04:00
}
2026-03-13 11:21:43 -04:00
// 15.5.11 HourCyclesOfLocale ( loc ), https://tc39.es/ecma402/#sec-hourcyclesoflocale
2024-11-15 04:01:23 +13:00
GC : : Ref < Array > hour_cycles_of_locale ( VM & vm , Locale const & locale_object )
2022-07-05 13:41:08 -04:00
{
2026-03-13 11:21:43 -04:00
auto & realm = * vm . current_realm ( ) ;
2022-07-05 13:41:08 -04:00
2026-03-13 11:21:43 -04:00
// 1. If loc.[[HourCycle]] is not undefined, then
if ( locale_object . has_hour_cycle ( ) ) {
// a. Return CreateArrayFromList(« loc.[[HourCycle]] »).
return Array : : create_from ( realm , { PrimitiveString : : create ( vm , locale_object . hour_cycle ( ) ) } ) ;
}
2022-07-05 13:41:08 -04:00
2026-03-13 11:21:43 -04:00
// 2. Let preference be RegionPreference(loc.[[Locale]]).
// 3. Let region be preference.[[Region]].
// 4. Let regionOverride be preference.[[RegionOverride]].
// 5. If regionOverride is not undefined and time data for regionOverride are available, then
// a. Let lookupRegion be regionOverride.
// 6. Else,
// a. Let lookupRegion be region.
// 7. Let list be a List of 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 lookupRegion. The list is empty if no time data for lookupRegion is available.
// 8. If list is empty, set list to « "h23" ».
auto list = Unicode : : available_hour_cycles ( locale_object . locale ( ) ) ;
// 9. Return CreateArrayFromList(list).
return Array : : create_from < String > ( realm , list , [ & vm ] ( auto const & value ) {
return PrimitiveString : : create ( vm , value ) ;
} ) ;
2022-07-05 13:41:08 -04:00
}
2026-03-13 11:21:43 -04:00
// 15.5.12 NumberingSystemsOfLocale ( loc ), https://tc39.es/ecma402/#sec-numberingsystemsoflocale
2024-11-15 04:01:23 +13:00
GC : : Ref < Array > numbering_systems_of_locale ( VM & vm , Locale const & locale_object )
2022-07-05 14:19:23 -04:00
{
2026-03-13 11:21:43 -04:00
auto & realm = * vm . current_realm ( ) ;
2022-07-05 14:19:23 -04:00
2026-03-13 11:21:43 -04:00
// 1. If loc.[[NumberingSystem]] is not undefined, then
if ( locale_object . has_numbering_system ( ) ) {
// a. Return CreateArrayFromList(« loc.[[NumberingSystem]] »).
return Array : : create_from ( realm , { PrimitiveString : : create ( vm , locale_object . numbering_system ( ) ) } ) ;
}
2022-07-05 14:19:23 -04:00
2026-03-13 11:21:43 -04:00
// 2. Let r be LookupMatchingLocaleByPrefix(%Intl.NumberFormat%.[[AvailableLocales]], « loc.[[Locale]] »).
// 3. If r is not undefined, then
// a. Let foundLocale be r.[[locale]].
// b. Let foundLocaleData be %Intl.NumberFormat%.[[LocaleData]].[[<foundLocale>]].
// c. Let numberingSystems be foundLocaleData.[[nu]].
// d. Let list be « numberingSystems[0] ».
// 4. Else,
// a. Let list be « "latn" ».
auto list = Unicode : : available_number_systems ( locale_object . locale ( ) ) ;
// 5. Return CreateArrayFromList(list).
return Array : : create_from < String > ( realm , list , [ & vm ] ( auto const & value ) {
return PrimitiveString : : create ( vm , value ) ;
} ) ;
2022-07-05 14:19:23 -04:00
}
2026-03-13 11:21:43 -04:00
// 15.5.13 TimeZonesOfLocale ( loc ), https://tc39.es/ecma402/#sec-timezonesoflocale
Value time_zones_of_locale ( VM & vm , Locale const & locale_object )
2022-07-05 16:20:36 -04:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2022-07-05 16:20:36 -04:00
2025-06-04 09:34:38 -04:00
// 1. Let region be GetLocaleRegion(loc.[[Locale]]).
auto const & region = locale_object . locale_id ( ) . language_id . region ;
2022-07-05 16:20:36 -04:00
2026-03-13 11:21:43 -04:00
// 2. If region is undefined, return undefined.
if ( ! region . has_value ( ) )
return js_undefined ( ) ;
2022-07-05 16:20:36 -04:00
2025-06-04 09:34:38 -04:00
// 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 ).
2026-03-13 11:21:43 -04:00
return Array : : create_from < String > ( realm , list , [ & vm ] ( auto const & value ) {
return PrimitiveString : : create ( vm , value ) ;
2022-07-05 16:20:36 -04:00
} ) ;
}
2026-03-13 11:21:43 -04:00
// 15.5.14 TextDirectionOfLocale ( loc ), https://tc39.es/ecma402/#sec-textdirectionoflocale
StringView text_direction_of_locale ( Locale const & locale_object )
{
// 1. Let locale be loc.[[Locale]].
auto const & locale = locale_object . locale ( ) ;
// 2. Let script be GetLocaleScript(locale).
// 3. If script is undefined, then
// a. Let maximal be the result of the Add Likely Subtags algorithm applied to locale. If an error is signaled, return undefined.
// b. Set script to GetLocaleScript(maximal).
// c. If script is undefined, return undefined.
// NB: ICU handles maximizing the locale if there is no script.
// 4. If the default general ordering of characters within a line in script is right-to-left, return "rtl".
// 5. If the default general ordering of characters within a line in script is left-to-right, return "ltr".
// 6. Return undefined.
// FIXME: ICU does not provide a method to determine if a locale is neither rtl nor ltr.
return Unicode : : is_locale_character_ordering_right_to_left ( locale ) ? " rtl " sv : " ltr " sv ;
}
2024-07-11 09:29:06 -04:00
struct FirstDayStringAndValue {
StringView weekday ;
StringView string ;
u8 value { 0 } ;
} ;
2026-03-13 11:21:43 -04:00
// Table 26: Weekday String and Value, https://tc39.es/ecma402/#table-locale-weekday-string-value
static constexpr auto WEEKDAY_STRING_AND_VALUE = to_array < FirstDayStringAndValue > ( {
2024-07-11 09:29:06 -04:00
{ " 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 } ,
} ) ;
2026-03-13 11:21:43 -04:00
// 15.5.15 WeekdayToUValue ( fw ), https://tc39.es/ecma402/#sec-weekdaytouvalue
StringView weekday_to_u_value ( StringView weekday )
2023-10-03 10:18:18 -04:00
{
2026-03-13 11:21:43 -04:00
// 1. For each row of Table 26, except the header row, in table order, do
for ( auto const & row : WEEKDAY_STRING_AND_VALUE ) {
// a. Let w be the Weekday value of the current row.
// b. Let s be the String value of the current row.
2024-07-11 09:29:06 -04:00
// c. If fw is equal to w, return s.
if ( weekday = = row . weekday )
return row . string ;
2023-10-03 10:18:18 -04:00
}
2024-07-11 09:29:06 -04:00
// 2. Return fw.
return weekday ;
2023-10-03 10:18:18 -04:00
}
2026-03-13 11:21:43 -04:00
// 15.5.16 WeekdayUValueToNumber ( fw ), https://tc39.es/ecma402/#sec-weekdayuvaluetonumber
Optional < u8 > weekday_u_value_to_number ( StringView weekday )
2023-10-03 10:18:18 -04:00
{
2026-03-13 11:21:43 -04:00
// 1. For each row of Table 26, except the header row, in table order, do
for ( auto const & row : WEEKDAY_STRING_AND_VALUE ) {
// a. Let s be the String value of the current row.
// b. Let v be the Value value of the current row.
2024-07-11 09:29:06 -04:00
// c. If fw is equal to s, return v.
if ( weekday = = row . string )
return row . value ;
2023-10-03 10:18:18 -04:00
}
2024-07-11 09:29:06 -04:00
// 2. Return undefined.
return { } ;
2023-10-03 10:18:18 -04:00
}
2024-06-23 09:14:27 -04:00
static u8 weekday_to_integer ( Optional < Unicode : : Weekday > const & weekday , Unicode : : Weekday falllback )
2022-07-06 08:30:53 -04:00
{
2026-03-13 11:21:43 -04:00
// NB: This fallback will be used if the ICU data lookup failed. Its value should be that of the default region
// ("001") in the CLDR.
2022-07-06 08:30:53 -04:00
switch ( weekday . value_or ( falllback ) ) {
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Monday :
2022-07-06 08:30:53 -04:00
return 1 ;
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Tuesday :
2022-07-06 08:30:53 -04:00
return 2 ;
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Wednesday :
2022-07-06 08:30:53 -04:00
return 3 ;
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Thursday :
2022-07-06 08:30:53 -04:00
return 4 ;
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Friday :
2022-07-06 08:30:53 -04:00
return 5 ;
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Saturday :
2022-07-06 08:30:53 -04:00
return 6 ;
2024-06-23 09:14:27 -04:00
case Unicode : : Weekday : : Sunday :
2022-07-06 08:30:53 -04:00
return 7 ;
}
VERIFY_NOT_REACHED ( ) ;
}
2024-06-23 09:14:27 -04:00
static Vector < u8 > weekend_of_locale ( ReadonlySpan < Unicode : : Weekday > const & weekend_days )
2022-07-06 08:30:53 -04:00
{
Vector < u8 > weekend ;
2024-06-12 17:00:45 -04:00
weekend . ensure_capacity ( weekend_days . size ( ) ) ;
2022-07-06 08:30:53 -04:00
2024-06-12 17:00:45 -04:00
for ( auto day : weekend_days )
weekend . unchecked_append ( weekday_to_integer ( day , day ) ) ;
2022-07-06 08:30:53 -04:00
2024-06-12 17:00:45 -04:00
quick_sort ( weekend ) ;
2022-07-06 08:30:53 -04:00
return weekend ;
}
2026-03-13 11:21:43 -04:00
// 15.5.17 WeekInfoOfLocale ( loc ), https://tc39.es/ecma402/#sec-weekinfooflocale
2023-08-30 11:50:02 -04:00
WeekInfo week_info_of_locale ( Locale const & locale_object )
2022-07-06 08:30:53 -04:00
{
// 1. Let locale be loc.[[Locale]].
auto const & locale = locale_object . locale ( ) ;
2026-03-13 11:21:43 -04:00
// 2. Let r be a Record whose fields are defined by Table 27, with values based on locale.
2024-06-23 09:14:27 -04:00
auto locale_week_info = Unicode : : week_info_of_locale ( locale ) ;
2024-06-12 17:00:45 -04:00
2022-07-06 08:30:53 -04:00
WeekInfo week_info { } ;
2024-06-23 09:14:27 -04:00
week_info . first_day = weekday_to_integer ( locale_week_info . first_day_of_week , Unicode : : Weekday : : Monday ) ;
2024-06-12 17:00:45 -04:00
week_info . weekend = weekend_of_locale ( locale_week_info . weekend_days ) ;
2022-07-06 08:30:53 -04:00
2024-07-11 09:29:06 -04:00
Optional < u8 > first_day_of_week ;
2023-10-03 10:18:18 -04:00
if ( locale_object . has_first_day_of_week ( ) ) {
2025-06-04 09:34:38 -04:00
// 3. Let fws be loc.[[FirstDayOfWeek]].
2024-07-11 09:29:06 -04:00
auto const & first_day_of_week_string = locale_object . first_day_of_week ( ) ;
2026-03-13 11:21:43 -04:00
// 4. Let fw be WeekdayUValueToNumber(fws).
first_day_of_week = weekday_u_value_to_number ( first_day_of_week_string ) ;
2024-07-11 09:29:06 -04:00
}
2025-06-04 09:34:38 -04:00
// 5. If fw is not undefined, then
2024-07-11 09:29:06 -04:00
if ( first_day_of_week . has_value ( ) ) {
2023-10-03 10:18:18 -04:00
// a. Set r.[[FirstDay]] to fw.
2024-07-11 09:29:06 -04:00
week_info . first_day = * first_day_of_week ;
2023-10-03 10:18:18 -04:00
}
2025-06-04 09:34:38 -04:00
// 6. Return r.
2022-07-06 08:30:53 -04:00
return week_info ;
}
2021-09-02 08:32:43 -04:00
}