2021-11-18 10:30:31 -05:00
/*
2023-01-12 10:22:37 -05:00
* Copyright ( c ) 2021 - 2023 , Tim Flynn < trflynn89 @ serenityos . org >
2021-11-18 10:30:31 -05:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibJS/Runtime/AbstractOperations.h>
2021-11-28 21:16:27 -05:00
# include <LibJS/Runtime/Array.h>
2022-10-14 10:44:31 -04:00
# include <LibJS/Runtime/Date.h>
2021-11-18 10:30:31 -05:00
# include <LibJS/Runtime/GlobalObject.h>
2021-11-28 21:16:27 -05:00
# include <LibJS/Runtime/Intl/AbstractOperations.h>
2023-07-21 22:13:44 -04:00
# include <LibJS/Runtime/Intl/DateTimeFormat.h>
2021-11-18 10:30:31 -05:00
# include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
2022-03-15 10:30:33 -04:00
# include <LibJS/Runtime/Temporal/TimeZone.h>
2022-09-02 12:11:30 -04:00
# include <LibLocale/DateTimeFormat.h>
# include <LibLocale/Locale.h>
2021-11-18 10:30:31 -05:00
namespace JS : : Intl {
2022-03-15 10:30:33 -04:00
// 11.1 The Intl.DateTimeFormat Constructor, https://tc39.es/ecma402/#sec-intl-datetimeformat-constructor
2022-08-16 00:20:49 +01:00
DateTimeFormatConstructor : : DateTimeFormatConstructor ( Realm & realm )
2023-04-13 00:47:15 +02:00
: NativeFunction ( realm . vm ( ) . names . DateTimeFormat . as_string ( ) , realm . intrinsics ( ) . function_prototype ( ) )
2021-11-18 10:30:31 -05:00
{
}
2023-08-07 08:41:28 +02:00
void DateTimeFormatConstructor : : initialize ( Realm & realm )
2021-11-18 10:30:31 -05:00
{
2023-08-07 08:41:28 +02:00
Base : : initialize ( realm ) ;
2021-11-18 10:30:31 -05:00
auto & vm = this - > vm ( ) ;
2022-03-15 10:30:33 -04:00
// 11.2.1 Intl.DateTimeFormat.prototype, https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype
2022-08-27 00:54:55 +01:00
define_direct_property ( vm . names . prototype , realm . intrinsics ( ) . intl_date_time_format_prototype ( ) , 0 ) ;
2021-11-28 21:16:27 -05:00
u8 attr = Attribute : : Writable | Attribute : : Configurable ;
2022-08-22 21:47:35 +01:00
define_native_function ( realm , vm . names . supportedLocalesOf , supported_locales_of , 1 , attr ) ;
2021-11-28 21:16:27 -05:00
2021-11-18 10:30:31 -05:00
define_direct_property ( vm . names . length , Value ( 0 ) , Attribute : : Configurable ) ;
}
2022-03-15 10:30:33 -04:00
// 11.1.1 Intl.DateTimeFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.datetimeformat
2021-11-18 10:30:31 -05:00
ThrowCompletionOr < Value > DateTimeFormatConstructor : : call ( )
{
// 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
return TRY ( construct ( * this ) ) ;
}
2022-03-15 10:30:33 -04:00
// 11.1.1 Intl.DateTimeFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.datetimeformat
2022-12-14 19:18:10 +00:00
ThrowCompletionOr < NonnullGCPtr < Object > > DateTimeFormatConstructor : : construct ( FunctionObject & new_target )
2021-11-18 10:30:31 -05:00
{
2021-11-28 17:55:47 -05:00
auto & vm = this - > vm ( ) ;
2021-11-18 10:30:31 -05:00
2021-11-28 17:55:47 -05:00
auto locales = vm . argument ( 0 ) ;
auto options = vm . argument ( 1 ) ;
2023-07-21 21:33:55 -04:00
// 2. Let dateTimeFormat be ? CreateDateTimeFormat(newTarget, locales, options, any, date).
auto date_time_format = TRY ( create_date_time_format ( vm , new_target , locales , options , OptionRequired : : Any , OptionDefaults : : Date ) ) ;
2021-11-28 17:55:47 -05:00
2023-07-21 21:33:55 -04:00
// 3. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
2021-11-28 17:55:47 -05:00
// a. Let this be the this value.
// b. Return ? ChainDateTimeFormat(dateTimeFormat, NewTarget, this).
2023-07-21 21:33:55 -04:00
// 4. Return dateTimeFormat.
2022-12-14 19:18:10 +00:00
return date_time_format ;
2021-11-18 10:30:31 -05:00
}
2022-03-15 10:30:33 -04:00
// 11.2.2 Intl.DateTimeFormat.supportedLocalesOf ( locales [ , options ] ), https://tc39.es/ecma402/#sec-intl.datetimeformat.supportedlocalesof
2021-11-28 21:16:27 -05:00
JS_DEFINE_NATIVE_FUNCTION ( DateTimeFormatConstructor : : supported_locales_of )
{
auto locales = vm . argument ( 0 ) ;
auto options = vm . argument ( 1 ) ;
// 1. Let availableLocales be %DateTimeFormat%.[[AvailableLocales]].
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
2022-08-20 08:25:24 +01:00
auto requested_locales = TRY ( canonicalize_locale_list ( vm , locales ) ) ;
2021-11-28 21:16:27 -05:00
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
2022-08-20 08:25:24 +01:00
return TRY ( supported_locales ( vm , requested_locales , options ) ) ;
2021-11-28 21:16:27 -05:00
}
2023-07-21 21:33:55 -04:00
// 11.1.2 CreateDateTimeFormat ( newTarget, locales, options, required, defaults ), https://tc39.es/ecma402/#sec-createdatetimeformat
ThrowCompletionOr < NonnullGCPtr < DateTimeFormat > > create_date_time_format ( VM & vm , FunctionObject & new_target , Value locales_value , Value options_value , OptionRequired required , OptionDefaults defaults )
2022-03-15 10:30:33 -04:00
{
2023-07-21 21:33:55 -04:00
// 1. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%DateTimeFormat.prototype%", « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[Weekday]], [[Era]], [[Year]], [[Month]], [[Day]], [[DayPeriod]], [[Hour]], [[Minute]], [[Second]], [[FractionalSecondDigits]], [[TimeZoneName]], [[HourCycle]], [[DateStyle]], [[TimeStyle]], [[Pattern]], [[RangePatterns]], [[BoundFormat]] »).
auto date_time_format = TRY ( ordinary_create_from_constructor < DateTimeFormat > ( vm , new_target , & Intrinsics : : intl_date_time_format_prototype ) ) ;
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
2022-08-20 08:25:24 +01:00
auto requested_locales = TRY ( canonicalize_locale_list ( vm , locales_value ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 3. Set options to ? CoerceOptionsToObject(options).
auto * options = TRY ( coerce_options_to_object ( vm , options_value ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 4. Let opt be a new Record.
2022-03-15 10:30:33 -04:00
LocaleOptions opt { } ;
2023-07-21 22:13:44 -04:00
// 5. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
2022-08-20 08:52:42 +01:00
auto matcher = TRY ( get_option ( vm , * options , vm . names . localeMatcher , OptionType : : String , AK : : Array { " lookup " sv , " best fit " sv } , " best fit " sv ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 6. Set opt.[[localeMatcher]] to matcher.
2022-03-15 10:30:33 -04:00
opt . locale_matcher = matcher ;
2023-07-21 22:13:44 -04:00
// 7. Let calendar be ? GetOption(options, "calendar", string, empty, undefined).
2022-08-20 08:52:42 +01:00
auto calendar = TRY ( get_option ( vm , * options , vm . names . calendar , OptionType : : String , { } , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 8. If calendar is not undefined, then
2022-03-15 10:30:33 -04:00
if ( ! calendar . is_undefined ( ) ) {
2023-07-21 21:33:55 -04:00
// a. If calendar cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
2023-08-08 19:17:55 +02:00
if ( ! : : Locale : : is_type_identifier ( calendar . as_string ( ) . utf8_string_view ( ) ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : OptionIsNotValidValue , calendar , " calendar " sv ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 9. Set opt.[[ca]] to calendar.
2023-08-08 19:17:55 +02:00
opt . ca = calendar . as_string ( ) . utf8_string ( ) ;
2022-03-15 10:30:33 -04:00
}
2023-07-21 22:13:44 -04:00
// 10. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
2022-08-20 08:52:42 +01:00
auto numbering_system = TRY ( get_option ( vm , * options , vm . names . numberingSystem , OptionType : : String , { } , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 11. If numberingSystem is not undefined, then
2022-03-15 10:30:33 -04:00
if ( ! numbering_system . is_undefined ( ) ) {
2023-07-21 21:33:55 -04:00
// a. If numberingSystem cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
2023-08-08 19:17:55 +02:00
if ( ! : : Locale : : is_type_identifier ( numbering_system . as_string ( ) . utf8_string_view ( ) ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : OptionIsNotValidValue , numbering_system , " numberingSystem " sv ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 12. Set opt.[[nu]] to numberingSystem.
2023-08-08 19:17:55 +02:00
opt . nu = numbering_system . as_string ( ) . utf8_string ( ) ;
2022-03-15 10:30:33 -04:00
}
2023-07-21 22:13:44 -04:00
// 13. Let hour12 be ? GetOption(options, "hour12", boolean, empty, undefined).
2022-08-20 08:52:42 +01:00
auto hour12 = TRY ( get_option ( vm , * options , vm . names . hour12 , OptionType : : Boolean , { } , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 14. Let hourCycle be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined).
2022-08-20 08:52:42 +01:00
auto hour_cycle = TRY ( get_option ( vm , * options , vm . names . hourCycle , OptionType : : String , AK : : Array { " h11 " sv , " h12 " sv , " h23 " sv , " h24 " sv } , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 15. If hour12 is not undefined, then
2022-03-15 10:30:33 -04:00
if ( ! hour12 . is_undefined ( ) ) {
2022-03-28 12:29:17 -04:00
// a. Set hourCycle to null.
2022-03-15 10:30:33 -04:00
hour_cycle = js_null ( ) ;
}
2023-07-21 22:13:44 -04:00
// 16. Set opt.[[hc]] to hourCycle.
2022-03-15 10:30:33 -04:00
if ( ! hour_cycle . is_nullish ( ) )
2023-08-08 19:17:55 +02:00
opt . hc = hour_cycle . as_string ( ) . utf8_string ( ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 17. Let localeData be %DateTimeFormat%.[[LocaleData]].
// 18. Let r be ResolveLocale(%DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]], localeData).
2023-08-30 11:08:15 -04:00
auto result = resolve_locale ( requested_locales , opt , DateTimeFormat : : relevant_extension_keys ( ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 19. Set dateTimeFormat.[[Locale]] to r.[[locale]].
2023-07-21 21:33:55 -04:00
date_time_format - > set_locale ( move ( result . locale ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 20. Let resolvedCalendar be r.[[ca]].
// 21. Set dateTimeFormat.[[Calendar]] to resolvedCalendar.
2022-03-15 10:30:33 -04:00
if ( result . ca . has_value ( ) )
2023-07-21 21:33:55 -04:00
date_time_format - > set_calendar ( result . ca . release_value ( ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 22. Set dateTimeFormat.[[NumberingSystem]] to r.[[nu]].
2022-03-15 10:30:33 -04:00
if ( result . nu . has_value ( ) )
2023-07-21 21:33:55 -04:00
date_time_format - > set_numbering_system ( result . nu . release_value ( ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 23. Let dataLocale be r.[[dataLocale]].
2022-03-15 10:30:33 -04:00
auto data_locale = move ( result . data_locale ) ;
// Non-standard, the data locale is needed for LibUnicode lookups while formatting.
2023-07-21 21:33:55 -04:00
date_time_format - > set_data_locale ( data_locale ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 24. Let dataLocaleData be localeData.[[<dataLocale>]].
// 25. Let hcDefault be dataLocaleData.[[hourCycle]].
2023-08-22 16:41:36 -04:00
auto default_hour_cycle = : : Locale : : get_default_regional_hour_cycle ( data_locale ) ;
2022-03-28 10:17:39 -04:00
// Non-standard, default_hour_cycle will be empty if Unicode data generation is disabled.
2022-12-13 10:12:48 -05:00
if ( ! default_hour_cycle . has_value ( ) ) {
2023-10-03 12:14:59 -04:00
date_time_format - > set_time_zone ( MUST ( String : : from_utf8 ( system_time_zone_identifier ( ) ) ) ) ;
2023-07-21 21:33:55 -04:00
return date_time_format ;
2022-12-13 10:12:48 -05:00
}
2022-03-28 10:17:39 -04:00
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : HourCycle > hour_cycle_value ;
2022-03-28 10:17:39 -04:00
2023-07-21 22:13:44 -04:00
// 26. If hour12 is true, then
2022-03-28 10:17:39 -04:00
if ( hour12 . is_boolean ( ) & & hour12 . as_bool ( ) ) {
2022-04-06 19:19:13 -04:00
// a. If hcDefault is "h11" or "h23", let hc be "h11". Otherwise, let hc be "h12".
2022-09-02 12:01:10 -04:00
if ( ( default_hour_cycle = = : : Locale : : HourCycle : : H11 ) | | ( default_hour_cycle = = : : Locale : : HourCycle : : H23 ) )
hour_cycle_value = : : Locale : : HourCycle : : H11 ;
2022-04-06 19:19:13 -04:00
else
2022-09-02 12:01:10 -04:00
hour_cycle_value = : : Locale : : HourCycle : : H12 ;
2022-03-28 10:17:39 -04:00
}
2023-07-21 22:13:44 -04:00
// 27. Else if hour12 is false, then
2022-03-28 10:17:39 -04:00
else if ( hour12 . is_boolean ( ) & & ! hour12 . as_bool ( ) ) {
2022-04-06 19:19:13 -04:00
// a. If hcDefault is "h11" or "h23", let hc be "h23". Otherwise, let hc be "h24".
2022-09-02 12:01:10 -04:00
if ( ( default_hour_cycle = = : : Locale : : HourCycle : : H11 ) | | ( default_hour_cycle = = : : Locale : : HourCycle : : H23 ) )
hour_cycle_value = : : Locale : : HourCycle : : H23 ;
2022-04-06 19:19:13 -04:00
else
2022-09-02 12:01:10 -04:00
hour_cycle_value = : : Locale : : HourCycle : : H24 ;
2022-03-28 10:17:39 -04:00
}
2023-07-21 22:13:44 -04:00
// 28. Else,
2022-03-28 10:17:39 -04:00
else {
2022-04-06 19:19:13 -04:00
// a. Assert: hour12 is undefined.
VERIFY ( hour12 . is_undefined ( ) ) ;
// b. Let hc be r.[[hc]].
2022-03-28 10:17:39 -04:00
if ( result . hc . has_value ( ) )
2022-09-02 12:01:10 -04:00
hour_cycle_value = : : Locale : : hour_cycle_from_string ( * result . hc ) ;
2022-03-28 10:17:39 -04:00
2022-04-06 19:19:13 -04:00
// c. If hc is null, set hc to hcDefault.
if ( ! hour_cycle_value . has_value ( ) )
hour_cycle_value = default_hour_cycle ;
2022-03-28 10:17:39 -04:00
}
2023-07-21 22:13:44 -04:00
// 29. Set dateTimeFormat.[[HourCycle]] to hc.
2022-03-28 10:17:39 -04:00
if ( hour_cycle_value . has_value ( ) )
2023-07-21 21:33:55 -04:00
date_time_format - > set_hour_cycle ( * hour_cycle_value ) ;
2022-03-28 10:17:39 -04:00
2023-07-21 22:13:44 -04:00
// 30. Let timeZone be ? Get(options, "timeZone").
2022-03-15 10:30:33 -04:00
auto time_zone_value = TRY ( options - > get ( vm . names . timeZone ) ) ;
2023-01-26 15:09:59 +00:00
String time_zone ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// 31. If timeZone is undefined, then
2022-03-15 10:30:33 -04:00
if ( time_zone_value . is_undefined ( ) ) {
2022-11-05 11:36:42 -04:00
// a. Set timeZone to DefaultTimeZone().
2023-10-03 12:14:59 -04:00
time_zone = MUST ( String : : from_utf8 ( system_time_zone_identifier ( ) ) ) ;
2022-03-15 10:30:33 -04:00
}
2023-07-21 22:13:44 -04:00
// 32. Else,
2022-03-15 10:30:33 -04:00
else {
2022-03-28 10:17:39 -04:00
// a. Set timeZone to ? ToString(timeZone).
2023-01-26 15:09:59 +00:00
time_zone = TRY ( time_zone_value . to_string ( vm ) ) ;
2023-10-04 15:45:59 -04:00
}
// 33. If IsTimeZoneOffsetString(timeZone) is true, then
if ( is_time_zone_offset_string ( time_zone ) ) {
// a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset).
auto parse_result = Temporal : : parse_iso8601 ( Temporal : : Production : : TimeZoneNumericUTCOffset , time_zone ) ;
// b. Assert: parseResult is a Parse Node.
VERIFY ( parse_result . has_value ( ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// c. If parseResult contains more than one MinuteSecond Parse Node, throw a RangeError exception.
if ( parse_result - > time_zone_utc_offset_second . has_value ( ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : OptionIsNotValidValue , time_zone , vm . names . timeZone ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// d. Let offsetNanoseconds be ParseTimeZoneOffsetString(timeZone).
auto offset_nanoseconds = parse_time_zone_offset_string ( time_zone ) ;
// e. Let offsetMinutes be offsetNanoseconds / (6 × 10^10).
auto offset_minutes = offset_nanoseconds / 60'000'000'000 ;
// f. Assert: offsetMinutes is an integer.
VERIFY ( trunc ( offset_minutes ) = = offset_minutes ) ;
// g. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
time_zone = format_offset_time_zone_identifier ( offset_minutes ) ;
}
// 34. Else if IsValidTimeZoneName(timeZone) is true, then
else if ( Temporal : : is_available_time_zone_name ( time_zone ) ) {
// a. Set timeZone to CanonicalizeTimeZoneName(timeZone).
2023-08-30 11:24:04 -04:00
time_zone = MUST ( Temporal : : canonicalize_time_zone_name ( vm , time_zone ) ) ;
2022-03-15 10:30:33 -04:00
}
2023-10-04 15:45:59 -04:00
// 35. Else,
else {
// a. Throw a RangeError exception.
return vm . throw_completion < RangeError > ( ErrorType : : OptionIsNotValidValue , time_zone , vm . names . timeZone ) ;
}
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 36. Set dateTimeFormat.[[TimeZone]] to timeZone.
2023-07-21 21:33:55 -04:00
date_time_format - > set_time_zone ( move ( time_zone ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 37. Let formatOptions be a new Record.
2022-09-02 12:01:10 -04:00
: : Locale : : CalendarPattern format_options { } ;
2022-04-06 19:19:13 -04:00
2023-10-04 15:45:59 -04:00
// 38. Set formatOptions.[[hourCycle]] to hc.
2022-04-06 19:19:13 -04:00
format_options . hour_cycle = hour_cycle_value ;
2023-10-04 15:45:59 -04:00
// 39. Let hasExplicitFormatComponents be false.
2022-03-28 12:29:17 -04:00
// NOTE: Instead of using a boolean, we track any explicitly provided component name for nicer exception messages.
PropertyKey const * explicit_format_component = nullptr ;
2023-10-04 15:45:59 -04:00
// 40. For each row of Table 6, except the header row, in table order, do
2022-08-20 08:25:24 +01:00
TRY ( for_each_calendar_field ( vm , format_options , [ & ] ( auto & option , auto const & property , auto const & values ) - > ThrowCompletionOr < void > {
2022-03-15 10:30:33 -04:00
using ValueType = typename RemoveReference < decltype ( option ) > : : ValueType ;
// a. Let prop be the name given in the Property column of the row.
// b. If prop is "fractionalSecondDigits", then
if constexpr ( IsIntegral < ValueType > ) {
// i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined).
2022-08-20 08:25:24 +01:00
auto value = TRY ( get_number_option ( vm , * options , property , 1 , 3 , { } ) ) ;
2022-03-15 10:30:33 -04:00
// d. Set formatOptions.[[<prop>]] to value.
2022-03-28 12:29:17 -04:00
if ( value . has_value ( ) ) {
2022-03-15 10:30:33 -04:00
option = static_cast < ValueType > ( value . value ( ) ) ;
2022-03-28 12:29:17 -04:00
// e. If value is not undefined, then
// i. Set hasExplicitFormatComponents to true.
explicit_format_component = & property ;
}
2022-03-15 10:30:33 -04:00
}
// c. Else,
else {
2022-03-28 12:29:17 -04:00
// i. Let values be a List whose elements are the strings given in the Values column of the row.
2023-01-12 10:22:37 -05:00
// ii. Let value be ? GetOption(options, prop, string, values, undefined).
2022-08-20 08:52:42 +01:00
auto value = TRY ( get_option ( vm , * options , property , OptionType : : String , values , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
// d. Set formatOptions.[[<prop>]] to value.
2022-03-28 12:29:17 -04:00
if ( ! value . is_undefined ( ) ) {
2023-08-08 19:17:55 +02:00
option = : : Locale : : calendar_pattern_style_from_string ( value . as_string ( ) . utf8_string_view ( ) ) ;
2022-03-28 12:29:17 -04:00
// e. If value is not undefined, then
// i. Set hasExplicitFormatComponents to true.
explicit_format_component = & property ;
}
2022-03-15 10:30:33 -04:00
}
return { } ;
} ) ) ;
2023-10-04 15:45:59 -04:00
// 41. Let matcher be ? GetOption(options, "formatMatcher", string, « "basic", "best fit" », "best fit").
2022-08-20 08:52:42 +01:00
matcher = TRY ( get_option ( vm , * options , vm . names . formatMatcher , OptionType : : String , AK : : Array { " basic " sv , " best fit " sv } , " best fit " sv ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 42. Let dateStyle be ? GetOption(options, "dateStyle", string, « "full", "long", "medium", "short" », undefined).
2022-08-20 08:52:42 +01:00
auto date_style = TRY ( get_option ( vm , * options , vm . names . dateStyle , OptionType : : String , AK : : Array { " full " sv , " long " sv , " medium " sv , " short " sv } , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 43. Set dateTimeFormat.[[DateStyle]] to dateStyle.
2022-03-15 10:30:33 -04:00
if ( ! date_style . is_undefined ( ) )
2023-08-08 19:17:55 +02:00
date_time_format - > set_date_style ( date_style . as_string ( ) . utf8_string_view ( ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 44. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined).
2022-08-20 08:52:42 +01:00
auto time_style = TRY ( get_option ( vm , * options , vm . names . timeStyle , OptionType : : String , AK : : Array { " full " sv , " long " sv , " medium " sv , " short " sv } , Empty { } ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 45. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
2022-03-15 10:30:33 -04:00
if ( ! time_style . is_undefined ( ) )
2023-08-08 19:17:55 +02:00
date_time_format - > set_time_style ( time_style . as_string ( ) . utf8_string_view ( ) ) ;
2022-03-15 10:30:33 -04:00
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarPattern > best_format { } ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 46. If dateStyle is not undefined or timeStyle is not undefined, then
2023-07-21 21:33:55 -04:00
if ( date_time_format - > has_date_style ( ) | | date_time_format - > has_time_style ( ) ) {
2022-03-28 12:29:17 -04:00
// a. If hasExplicitFormatComponents is true, then
if ( explicit_format_component ! = nullptr ) {
// i. Throw a TypeError exception.
2022-08-16 20:33:17 +01:00
return vm . throw_completion < TypeError > ( ErrorType : : IntlInvalidDateTimeFormatOption , * explicit_format_component , " dateStyle or timeStyle " sv ) ;
2022-03-28 12:29:17 -04:00
}
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// b. If required is date and timeStyle is not undefined, then
if ( required = = OptionRequired : : Date & & ! time_style . is_undefined ( ) ) {
// i. Throw a TypeError exception.
return vm . throw_completion < TypeError > ( ErrorType : : IntlInvalidDateTimeFormatOption , " timeStyle " sv , " date " sv ) ;
}
// c. If required is time and dateStyle is not undefined, then
if ( required = = OptionRequired : : Time & & ! date_style . is_undefined ( ) ) {
// i. Throw a TypeError exception.
return vm . throw_completion < TypeError > ( ErrorType : : IntlInvalidDateTimeFormatOption , " dateStyle " sv , " time " sv ) ;
}
// d. Let styles be dataLocaleData.[[styles]].[[<resolvedCalendar>]].
// e. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles).
2023-08-30 11:24:04 -04:00
best_format = date_time_style_format ( data_locale , date_time_format ) ;
2022-03-15 10:30:33 -04:00
}
2023-10-04 15:45:59 -04:00
// 47. Else,
2022-03-15 10:30:33 -04:00
else {
2023-07-21 22:13:44 -04:00
// a. Let needDefaults be true.
bool needs_defaults = true ;
// b. If required is date or any, then
if ( required = = OptionRequired : : Date | | required = = OptionRequired : : Any ) {
// i. For each property name prop of « "weekday", "year", "month", "day" », do
auto check_property_value = [ & ] ( auto const & value ) {
// 1. Let value be formatOptions.[[<prop>]].
// 2. If value is not undefined, let needDefaults be false.
if ( value . has_value ( ) )
needs_defaults = false ;
} ;
check_property_value ( format_options . weekday ) ;
check_property_value ( format_options . year ) ;
check_property_value ( format_options . month ) ;
check_property_value ( format_options . day ) ;
}
// c. If required is time or any, then
if ( required = = OptionRequired : : Time | | required = = OptionRequired : : Any ) {
// i. For each property name prop of « "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" », do
auto check_property_value = [ & ] ( auto const & value ) {
// 1. Let value be formatOptions.[[<prop>]].
// 2. If value is not undefined, let needDefaults be false.
if ( value . has_value ( ) )
needs_defaults = false ;
} ;
check_property_value ( format_options . day_period ) ;
check_property_value ( format_options . hour ) ;
check_property_value ( format_options . minute ) ;
check_property_value ( format_options . second ) ;
check_property_value ( format_options . fractional_second_digits ) ;
}
// d. If needDefaults is true and defaults is either date or all, then
if ( needs_defaults & & ( defaults = = OptionDefaults : : Date | | defaults = = OptionDefaults : : All ) ) {
// i. For each property name prop of « "year", "month", "day" », do
auto set_property_value = [ & ] ( auto & value ) {
// 1. Set formatOptions.[[<prop>]] to "numeric".
value = : : Locale : : CalendarPatternStyle : : Numeric ;
} ;
set_property_value ( format_options . year ) ;
set_property_value ( format_options . month ) ;
set_property_value ( format_options . day ) ;
}
// e. If needDefaults is true and defaults is either time or all, then
if ( needs_defaults & & ( defaults = = OptionDefaults : : Time | | defaults = = OptionDefaults : : All ) ) {
// i. For each property name prop of « "hour", "minute", "second" », do
auto set_property_value = [ & ] ( auto & value ) {
// 1. Set formatOptions.[[<prop>]] to "numeric".
value = : : Locale : : CalendarPatternStyle : : Numeric ;
} ;
set_property_value ( format_options . hour ) ;
set_property_value ( format_options . minute ) ;
set_property_value ( format_options . second ) ;
}
// f. Let formats be dataLocaleData.[[formats]].[[<resolvedCalendar>]].
2023-08-22 16:41:36 -04:00
auto formats = : : Locale : : get_calendar_available_formats ( data_locale , date_time_format - > calendar ( ) ) ;
2022-03-15 10:30:33 -04:00
2023-07-21 22:13:44 -04:00
// g. If matcher is "basic", then
2023-08-08 19:17:55 +02:00
if ( matcher . as_string ( ) . utf8_string_view ( ) = = " basic " sv ) {
2022-03-15 10:30:33 -04:00
// i. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
best_format = basic_format_matcher ( format_options , move ( formats ) ) ;
}
2023-07-21 22:13:44 -04:00
// h. Else,
2022-03-15 10:30:33 -04:00
else {
// i. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
best_format = best_fit_format_matcher ( format_options , move ( formats ) ) ;
}
}
2023-10-04 15:45:59 -04:00
// 48. For each row in Table 6, except the header row, in table order, do
2023-07-21 21:33:55 -04:00
date_time_format - > for_each_calendar_field_zipped_with ( * best_format , [ & ] ( auto & date_time_format_field , auto const & best_format_field , auto ) {
2022-03-15 10:30:33 -04:00
// 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 ( ) ) {
// i. Let p be bestFormat.[[<prop>]].
// ii. Set dateTimeFormat's internal slot whose name is the Internal Slot column of the row to p.
date_time_format_field = best_format_field ;
}
} ) ;
2023-01-27 10:18:11 -05:00
String pattern ;
2022-09-02 12:01:10 -04:00
Vector < : : Locale : : CalendarRangePattern > range_patterns ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 49. If dateTimeFormat.[[Hour]] is undefined, then
2023-07-21 21:33:55 -04:00
if ( ! date_time_format - > has_hour ( ) ) {
2022-03-15 10:30:33 -04:00
// a. Set dateTimeFormat.[[HourCycle]] to undefined.
2023-07-21 21:33:55 -04:00
date_time_format - > clear_hour_cycle ( ) ;
2022-03-15 10:30:33 -04:00
}
2023-10-04 15:45:59 -04:00
// 50. If dateTimeFormat.[[HourCycle]] is "h11" or "h12", then
2022-09-02 12:01:10 -04:00
if ( ( hour_cycle_value = = : : Locale : : HourCycle : : H11 ) | | ( hour_cycle_value = = : : Locale : : HourCycle : : H12 ) ) {
2022-03-15 10:30:33 -04:00
// 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]].
2023-08-22 16:41:36 -04:00
range_patterns = : : Locale : : get_calendar_range12_formats ( data_locale , date_time_format - > calendar ( ) , best_format - > skeleton ) ;
2022-03-15 10:30:33 -04:00
}
2023-10-04 15:45:59 -04:00
// 51. Else,
2022-03-15 10:30:33 -04:00
else {
// a. Let pattern be bestFormat.[[pattern]].
pattern = move ( best_format - > pattern ) ;
// b. Let rangePatterns be bestFormat.[[rangePatterns]].
2023-08-22 16:41:36 -04:00
range_patterns = : : Locale : : get_calendar_range_formats ( data_locale , date_time_format - > calendar ( ) , best_format - > skeleton ) ;
2022-03-15 10:30:33 -04:00
}
2023-10-04 15:45:59 -04:00
// 52. Set dateTimeFormat.[[Pattern]] to pattern.
2023-07-21 21:33:55 -04:00
date_time_format - > set_pattern ( move ( pattern ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 53. Set dateTimeFormat.[[RangePatterns]] to rangePatterns.
2023-07-21 21:33:55 -04:00
date_time_format - > set_range_patterns ( move ( range_patterns ) ) ;
2022-03-15 10:30:33 -04:00
2023-10-04 15:45:59 -04:00
// 54. Return dateTimeFormat.
2023-07-21 21:33:55 -04:00
return date_time_format ;
2022-03-15 10:30:33 -04:00
}
2023-10-04 15:45:59 -04:00
// 11.1.3 FormatOffsetTimeZoneIdentifier ( offsetMinutes ), https://tc39.es/ecma402/#sec-formatoffsettimezoneidentifier
String format_offset_time_zone_identifier ( double offset_minutes )
{
// 1. If offsetMinutes ≥ 0, let sign be the code unit 0x002B (PLUS SIGN); otherwise, let sign be the code unit 0x002D (HYPHEN-MINUS).
auto sign = offset_minutes > = 0.0 ? ' + ' : ' - ' ;
// 2. Let absoluteMinutes be abs(offsetMinutes).
auto absolute_minutes = fabs ( offset_minutes ) ;
// 3. Let hours be floor(absoluteMinutes / 60).
auto hours = static_cast < i64 > ( floor ( absolute_minutes / 60.0 ) ) ;
// 4. Let minutes be absoluteMinutes modulo 60.
auto minutes = static_cast < i64 > ( modulo ( absolute_minutes , 60.0 ) ) ;
// 5. Return the string-concatenation of sign, ToZeroPaddedDecimalString(hours, 2), the code unit 0x003A (COLON), and ToZeroPaddedDecimalString(minutes, 2).
return MUST ( String : : formatted ( " {}{:02}:{:02} " , sign , hours , minutes ) ) ;
}
2021-11-18 10:30:31 -05:00
}