From a357874c77963bfbee5f3af427a3132a4642eb75 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 3 Oct 2023 10:18:18 -0400 Subject: [PATCH] LibJS: Implement Intl.Locale.prototype.firstDayOfWeek This is a normative change in the Intl Locale Info spec. See: https://github.com/tc39/proposal-intl-locale-info/commit/f03a814 --- .../LibJS/Runtime/CommonPropertyNames.h | 1 + .../Libraries/LibJS/Runtime/Intl/Locale.cpp | 99 ++++++++++++++++++- .../Libraries/LibJS/Runtime/Intl/Locale.h | 12 ++- .../LibJS/Runtime/Intl/LocaleConstructor.cpp | 75 ++++++++++---- .../LibJS/Runtime/Intl/LocalePrototype.cpp | 26 +++-- .../LibJS/Runtime/Intl/LocalePrototype.h | 1 + .../Locale/Locale.prototype.firstDayOfWeek.js | 44 +++++++++ 7 files changed, 227 insertions(+), 31 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.firstDayOfWeek.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index dd11f03b4a..11ee16a56b 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -191,6 +191,7 @@ namespace JS { P(findLastIndex) \ P(findIndex) \ P(firstDay) \ + P(firstDayOfWeek) \ P(fixed) \ P(flags) \ P(flat) \ diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Locale.cpp b/Userland/Libraries/LibJS/Runtime/Intl/Locale.cpp index 6cf7d9c155..21dc3c6c92 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Locale.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/Locale.cpp @@ -182,6 +182,93 @@ StringView character_direction_of_locale(Locale const& locale_object) return "ltr"sv; } +// 1.1.8 WeekdayToNumber ( fw ), https://tc39.es/proposal-intl-locale-info/#sec-weekday-to-number +// FIXME: Spec issue: The spec definitions of WeekdayToNumber and WeekdayToString are backwards. +// https://github.com/tc39/proposal-intl-locale-info/issues/78 +Optional weekday_to_number(StringView weekday) +{ + struct WeekdayToNumber { + StringView type; + u8 number { 0 }; + }; + + // Table 2: First Day Type and Number, https://tc39.es/proposal-intl-locale-info/#table-locale-first-day-type-number + static constexpr auto weekday_to_number_table = AK::Array { + WeekdayToNumber { "mon"sv, 1 }, + WeekdayToNumber { "tue"sv, 2 }, + WeekdayToNumber { "wed"sv, 3 }, + WeekdayToNumber { "thu"sv, 4 }, + WeekdayToNumber { "fri"sv, 5 }, + WeekdayToNumber { "sat"sv, 6 }, + WeekdayToNumber { "sun"sv, 7 }, + }; + + // 1. For each row of Table 2, except the header row, in table order, do + for (auto const& row : weekday_to_number_table) { + // a. Let t be the name given in the Type column of the row. + auto type = row.type; + + // b. Let n be the name given in the Number column of the row. + auto number = row.number; + + // c. If fw is equal to t, return n. + if (weekday == type) + return number; + } + + // 2. Assert: Should not reach here. + // FIXME: Spec issue: This is currently reachable if an invalid value is provided as a locale extension, + // for example "en-u-fw-100". We return "undefined" for now to avoid crashing. + // https://github.com/tc39/proposal-intl-locale-info/issues/78 + return {}; +} + +// 1.1.9 WeekdayToString ( fw ), https://tc39.es/proposal-intl-locale-info/#sec-weekday-to-string +// FIXME: Spec issue: The spec definitions of WeekdayToNumber and WeekdayToString are backwards. +// https://github.com/tc39/proposal-intl-locale-info/issues/78 +StringView weekday_to_string(StringView weekday) +{ + struct WeekdayToString { + StringView value; + StringView type; + }; + + // Table 1: First Day Value and Type, https://tc39.es/proposal-intl-locale-info/#table-locale-first-day-option-type + static constexpr auto weekday_to_string_table = AK::Array { + WeekdayToString { "mon"sv, "mon"sv }, + WeekdayToString { "tue"sv, "tue"sv }, + WeekdayToString { "wed"sv, "wed"sv }, + WeekdayToString { "thu"sv, "thu"sv }, + WeekdayToString { "fri"sv, "fri"sv }, + WeekdayToString { "sat"sv, "sat"sv }, + WeekdayToString { "sun"sv, "sun"sv }, + WeekdayToString { "0"sv, "sun"sv }, + WeekdayToString { "1"sv, "mon"sv }, + WeekdayToString { "2"sv, "tue"sv }, + WeekdayToString { "3"sv, "wed"sv }, + WeekdayToString { "4"sv, "thu"sv }, + WeekdayToString { "5"sv, "fri"sv }, + WeekdayToString { "6"sv, "sat"sv }, + WeekdayToString { "7"sv, "sun"sv }, + }; + + // 1. For each row of Table 1, except the header row, in table order, do + for (auto const& row : weekday_to_string_table) { + // a. Let v be the name given in the Value column of the row. + auto value = row.value; + + // b. Let t be the name given in the Type column of the row. + auto type = row.type; + + // c. If fw is equal to v, return t. + if (weekday == value) + return type; + } + + // 2. Assert: Should not reach here. + VERIFY_NOT_REACHED(); +} + static u8 weekday_to_integer(Optional<::Locale::Weekday> weekday, ::Locale::Weekday falllback) { // NOTE: This fallback will be used if LibUnicode data generation is disabled. Its value should @@ -224,7 +311,7 @@ static Vector weekend_of_locale(StringView locale) return weekend; } -// 1.1.8 WeekInfoOfLocale ( loc ), https://tc39.es/proposal-intl-locale-info/#sec-week-info-of-locale +// 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]]. @@ -233,12 +320,20 @@ WeekInfo week_info_of_locale(Locale const& locale_object) // 2. Assert: locale matches the unicode_locale_id production. VERIFY(::Locale::parse_unicode_locale_id(locale).has_value()); - // 3. Return a record whose fields are defined by Table 1, with values based on locale. + // 3. Let r be a record whose fields are defined by Table 3, with values based on locale. WeekInfo week_info {}; week_info.minimal_days = ::Locale::get_locale_minimum_days(locale).value_or(1); week_info.first_day = weekday_to_integer(::Locale::get_locale_first_day(locale), ::Locale::Weekday::Monday); week_info.weekend = weekend_of_locale(locale); + // 4. Let fw be loc.[[FirstDayOfWeek]]. + // 5. If fw is not undefined, then + if (locale_object.has_first_day_of_week()) { + // a. Set r.[[FirstDay]] to fw. + week_info.first_day = locale_object.first_day_of_week(); + } + + // 6. Return r. return week_info; } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/Locale.h b/Userland/Libraries/LibJS/Runtime/Intl/Locale.h index a6e66309eb..5095cca540 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/Locale.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/Locale.h @@ -27,12 +27,13 @@ public: static constexpr auto relevant_extension_keys() { // 14.2.2 Internal slots, https://tc39.es/ecma402/#sec-intl.locale-internal-slots - // The value of the [[RelevantExtensionKeys]] internal slot is « "ca", "co", "hc", "kf", "kn", "nu" ». + // 1.3.2 Internal slots, https://tc39.es/proposal-intl-locale-info/#sec-intl.locale-internal-slots + // The value of the [[RelevantExtensionKeys]] internal slot is « "ca", "co", "fw", "hc", "kf", "kn", "nu" ». // If %Collator%.[[RelevantExtensionKeys]] does not contain "kf", then remove "kf" from %Locale%.[[RelevantExtensionKeys]]. // If %Collator%.[[RelevantExtensionKeys]] does not contain "kn", then remove "kn" from %Locale%.[[RelevantExtensionKeys]]. // FIXME: We do not yet have an Intl.Collator object. For now, we behave as if "kf" and "kn" exist, as test262 depends on it. - return AK::Array { "ca"sv, "co"sv, "hc"sv, "kf"sv, "kn"sv, "nu"sv }; + return AK::Array { "ca"sv, "co"sv, "fw"sv, "hc"sv, "kf"sv, "kn"sv, "nu"sv }; } virtual ~Locale() override = default; @@ -52,6 +53,10 @@ public: String const& collation() const { return m_collation.value(); } void set_collation(String collation) { m_collation = move(collation); } + bool has_first_day_of_week() const { return m_first_day_of_week.has_value(); } + u8 first_day_of_week() const { return m_first_day_of_week.value(); } + void set_first_day_of_week(u8 first_day_of_week) { m_first_day_of_week = first_day_of_week; } + bool has_hour_cycle() const { return m_hour_cycle.has_value(); } String const& hour_cycle() const { return m_hour_cycle.value(); } void set_hour_cycle(String hour_cycle) { m_hour_cycle = move(hour_cycle); } @@ -70,6 +75,7 @@ private: Optional m_calendar; // [[Calendar]] Optional m_case_first; // [[CaseFirst]] Optional m_collation; // [[Collation]] + Optional m_first_day_of_week; // [[FirstDayOfWeek]] Optional m_hour_cycle; // [[HourCycle]] Optional m_numbering_system; // [[NumberingSystem]] bool m_numeric { false }; // [[Numeric]] @@ -88,6 +94,8 @@ NonnullGCPtr hour_cycles_of_locale(VM&, Locale const& locale); NonnullGCPtr numbering_systems_of_locale(VM&, Locale const&); NonnullGCPtr time_zones_of_locale(VM&, StringView region); StringView character_direction_of_locale(Locale const&); +Optional weekday_to_number(StringView); +StringView weekday_to_string(StringView); WeekInfo week_info_of_locale(Locale const&); } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp index e48df93887..a903f5a1de 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/LocaleConstructor.cpp @@ -20,6 +20,7 @@ struct LocaleAndKeys { String locale; Optional ca; Optional co; + Optional fw; Optional hc; Optional kf; Optional kn; @@ -139,6 +140,8 @@ static LocaleAndKeys apply_unicode_extension_to_tag(StringView tag, LocaleAndKey return value.ca; if (key == "co"sv) return value.co; + if (key == "fw"sv) + return value.fw; if (key == "hc"sv) return value.hc; if (key == "kf"sv) @@ -241,6 +244,7 @@ ThrowCompletionOr LocaleConstructor::call() } // 14.1.1 Intl.Locale ( tag [ , options ] ), https://tc39.es/ecma402/#sec-Intl.Locale +// 1.2.3 Intl.Locale ( tag [ , options ] ), https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale ThrowCompletionOr> LocaleConstructor::construct(FunctionObject& new_target) { auto& vm = this->vm(); @@ -251,7 +255,7 @@ ThrowCompletionOr> LocaleConstructor::construct(FunctionObj // 2. Let relevantExtensionKeys be %Locale%.[[RelevantExtensionKeys]]. auto relevant_extension_keys = Locale::relevant_extension_keys(); - // 3. Let internalSlotsList be « [[InitializedLocale]], [[Locale]], [[Calendar]], [[Collation]], [[HourCycle]], [[NumberingSystem]] ». + // 3. Let internalSlotsList be « [[InitializedLocale]], [[Locale]], [[Calendar]], [[Collation]], [[FirstDayOfWeek]], [[HourCycle]], [[NumberingSystem]] ». // 4. If relevantExtensionKeys contains "kf", then // a. Append [[CaseFirst]] as the last element of internalSlotsList. // 5. If relevantExtensionKeys contains "kn", then @@ -299,51 +303,82 @@ ThrowCompletionOr> LocaleConstructor::construct(FunctionObj // 18. Set opt.[[co]] to collation. opt.co = TRY(get_string_option(vm, *options, vm.names.collation, ::Locale::is_type_identifier)); - // 19. Let hc be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined). - // 20. Set opt.[[hc]] to hc. + // 19. Let fw be ? GetOption(options, "firstDayOfWeek", "string", « "mon", "tue", "wed", "thu", "fri", "sat", "sun", "0", "1", "2", "3", "4", "5", "6", "7" », undefined). + auto first_day_of_week = TRY(get_string_option(vm, *options, vm.names.firstDayOfWeek, nullptr, AK::Array { "mon"sv, "tue"sv, "wed"sv, "thu"sv, "fri"sv, "sat"sv, "sun"sv, "0"sv, "1"sv, "2"sv, "3"sv, "4"sv, "5"sv, "6"sv, "7"sv })); + + // 20. Let firstDay be undefined. + Optional first_day_string; + + // 21. If fw is not undefined, then + if (first_day_of_week.has_value()) { + // a. Set firstDay to !WeekdayToString(fw). + first_day_string = MUST(String::from_utf8(weekday_to_string(*first_day_of_week))); + } + + // 22. Set opt.[[fw]] to firstDay. + opt.fw = move(first_day_string); + + // 23. Let hc be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined). + // 24. Set opt.[[hc]] to hc. opt.hc = TRY(get_string_option(vm, *options, vm.names.hourCycle, nullptr, AK::Array { "h11"sv, "h12"sv, "h23"sv, "h24"sv })); - // 21. Let kf be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined). - // 22. Set opt.[[kf]] to kf. + // 25. Let kf be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined). + // 26. Set opt.[[kf]] to kf. opt.kf = TRY(get_string_option(vm, *options, vm.names.caseFirst, nullptr, AK::Array { "upper"sv, "lower"sv, "false"sv })); - // 23. Let kn be ? GetOption(options, "numeric", boolean, empty, undefined). + // 27. Let kn be ? GetOption(options, "numeric", boolean, empty, undefined). auto kn = TRY(get_option(vm, *options, vm.names.numeric, OptionType::Boolean, {}, Empty {})); - // 24. If kn is not undefined, set kn to ! ToString(kn). - // 25. Set opt.[[kn]] to kn. + // 28. If kn is not undefined, set kn to ! ToString(kn). + // 29. Set opt.[[kn]] to kn. if (!kn.is_undefined()) opt.kn = TRY(kn.to_string(vm)); - // 26. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined). - // 27. If numberingSystem is not undefined, then + // 30. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined). + // 31. If numberingSystem is not undefined, then // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. - // 28. Set opt.[[nu]] to numberingSystem. + // 32. Set opt.[[nu]] to numberingSystem. opt.nu = TRY(get_string_option(vm, *options, vm.names.numberingSystem, ::Locale::is_type_identifier)); - // 29. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys). + // 33. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys). auto result = apply_unicode_extension_to_tag(tag, move(opt), relevant_extension_keys); - // 30. Set locale.[[Locale]] to r.[[locale]]. + // 34. Set locale.[[Locale]] to r.[[locale]]. locale->set_locale(move(result.locale)); - // 31. Set locale.[[Calendar]] to r.[[ca]]. + + // 35. Set locale.[[Calendar]] to r.[[ca]]. if (result.ca.has_value()) locale->set_calendar(result.ca.release_value()); - // 32. Set locale.[[Collation]] to r.[[co]]. + + // 36. Set locale.[[Collation]] to r.[[co]]. if (result.co.has_value()) locale->set_collation(result.co.release_value()); - // 33. Set locale.[[HourCycle]] to r.[[hc]]. + + // 37. Let firstDay be undefined. + Optional first_day_numeric; + + // 38. If r.[[fw]] is not undefined, then + if (result.fw.has_value()) { + // a. Set firstDay to ! WeekdayToNumber(r.[[fw]]). + first_day_numeric = weekday_to_number(*result.fw); + } + + // 39. Set locale.[[FirstDayOfWeek]] to firstDay. + if (first_day_numeric.has_value()) + locale->set_first_day_of_week(*first_day_numeric); + + // 40. Set locale.[[HourCycle]] to r.[[hc]]. if (result.hc.has_value()) locale->set_hour_cycle(result.hc.release_value()); - // 34. If relevantExtensionKeys contains "kf", then + // 41. If relevantExtensionKeys contains "kf", then if (relevant_extension_keys.span().contains_slow("kf"sv)) { // a. Set locale.[[CaseFirst]] to r.[[kf]]. if (result.kf.has_value()) locale->set_case_first(result.kf.release_value()); } - // 35. If relevantExtensionKeys contains "kn", then + // 42. If relevantExtensionKeys contains "kn", then if (relevant_extension_keys.span().contains_slow("kn"sv)) { // a. If SameValue(r.[[kn]], "true") is true or r.[[kn]] is the empty String, then if (result.kn.has_value() && (result.kn == "true"sv || result.kn->is_empty())) { @@ -357,11 +392,11 @@ ThrowCompletionOr> LocaleConstructor::construct(FunctionObj } } - // 36. Set locale.[[NumberingSystem]] to r.[[nu]]. + // 43. Set locale.[[NumberingSystem]] to r.[[nu]]. if (result.nu.has_value()) locale->set_numbering_system(result.nu.release_value()); - // 37. Return locale. + // 44. Return locale. return locale; } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.cpp index f7d52764a9..be7470c256 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.cpp @@ -39,6 +39,7 @@ void LocalePrototype::initialize(Realm& realm) define_native_accessor(realm, vm.names.caseFirst, case_first, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.collation, collation, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.collations, collations, {}, Attribute::Configurable); + define_native_accessor(realm, vm.names.firstDayOfWeek, first_day_of_week, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.hourCycle, hour_cycle, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.hourCycles, hour_cycles, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.numberingSystem, numbering_system, {}, Attribute::Configurable); @@ -141,6 +142,17 @@ JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::base_name) JS_ENUMERATE_LOCALE_KEYWORD_PROPERTIES #undef __JS_ENUMERATE +// 1.4.10 get Intl.Locale.prototype.firstDayOfWeek, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.firstDayOfWeek +JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::first_day_of_week) +{ + // 1. Let loc be the this value. + // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]). + auto locale_object = TRY(typed_this_object(vm)); + + // 3. Return loc.[[FirstDayOfWeek]]. + return locale_object->has_first_day_of_week() ? Value { locale_object->first_day_of_week() } : js_undefined(); +} + // 14.3.11 get Intl.Locale.prototype.numeric, https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numeric JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::numeric) { @@ -217,10 +229,10 @@ JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::region) __JS_ENUMERATE(hour_cycles) \ __JS_ENUMERATE(numbering_systems) -// 1.4.16 get Intl.Locale.prototype.calendars, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.calendars -// 1.4.17 get Intl.Locale.prototype.collations, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.collations -// 1.4.18 get Intl.Locale.prototype.hourCycles, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.hourCycles -// 1.4.19 get Intl.Locale.prototype.numberingSystems, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.numberingSystems +// 1.4.17 get Intl.Locale.prototype.calendars, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.calendars +// 1.4.18 get Intl.Locale.prototype.collations, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.collations +// 1.4.19 get Intl.Locale.prototype.hourCycles, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.hourCycles +// 1.4.20 get Intl.Locale.prototype.numberingSystems, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.numberingSystems #define __JS_ENUMERATE(keyword) \ JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::keyword) \ { \ @@ -230,7 +242,7 @@ JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::region) JS_ENUMERATE_LOCALE_INFO_PROPERTIES #undef __JS_ENUMERATE -// 1.4.20 get Intl.Locale.prototype.timeZones, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.timeZones +// 1.4.21 get Intl.Locale.prototype.timeZones, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.timeZones JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::time_zones) { // 1. Let loc be the this value. @@ -248,7 +260,7 @@ JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::time_zones) return time_zones_of_locale(vm, locale->language_id.region.value()); } -// 1.4.21 get Intl.Locale.prototype.textInfo, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.textInfo +// 1.4.22 get Intl.Locale.prototype.textInfo, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.textInfo JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::text_info) { auto& realm = *vm.current_realm(); @@ -270,7 +282,7 @@ JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::text_info) return info; } -// 1.4.22 get Intl.Locale.prototype.weekInfo, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.weekInfo +// 1.4.23 get Intl.Locale.prototype.weekInfo, https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.weekInfo JS_DEFINE_NATIVE_FUNCTION(LocalePrototype::week_info) { auto& realm = *vm.current_realm(); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.h index 03078adbfb..977efe819a 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/LocalePrototype.h @@ -31,6 +31,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(case_first); JS_DECLARE_NATIVE_FUNCTION(collation); JS_DECLARE_NATIVE_FUNCTION(collations); + JS_DECLARE_NATIVE_FUNCTION(first_day_of_week); JS_DECLARE_NATIVE_FUNCTION(hour_cycle); JS_DECLARE_NATIVE_FUNCTION(hour_cycles); JS_DECLARE_NATIVE_FUNCTION(numbering_system); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.firstDayOfWeek.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.firstDayOfWeek.js new file mode 100644 index 0000000000..700019349b --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/Locale/Locale.prototype.firstDayOfWeek.js @@ -0,0 +1,44 @@ +describe("errors", () => { + test("called on non-Locale object", () => { + expect(() => { + Intl.Locale.prototype.firstDayOfWeek; + }).toThrowWithMessage(TypeError, "Not an object of type Intl.Locale"); + }); + + test("invalid options", () => { + [100, Infinity, NaN, "hello", 152n, true].forEach(value => { + expect(() => { + new Intl.Locale("en", { firstDayOfWeek: value }).firstDayOfWeek; + }).toThrowWithMessage( + RangeError, + `${value} is not a valid value for option firstDayOfWeek` + ); + }); + }); + + // FIXME: Spec issue: It is not yet clear if the following invalid values should throw. For now, this tests our + // existing workaround behavior, which returns "undefined" for invalid extensions. + // https://github.com/tc39/proposal-intl-locale-info/issues/78 + test("invalid extensions", () => { + [100, Infinity, NaN, "hello", 152n, true].forEach(value => { + expect(new Intl.Locale(`en-u-fw-${value}`).firstDayOfWeek).toBeUndefined(); + }); + }); +}); + +describe("normal behavior", () => { + test("valid options", () => { + expect(new Intl.Locale("en").firstDayOfWeek).toBeUndefined(); + + ["mon", "tue", "wed", "thu", "fri", "sat", "sun"].forEach((day, index) => { + expect(new Intl.Locale(`en-u-fw-${day}`).firstDayOfWeek).toBe(index + 1); + expect(new Intl.Locale("en", { firstDayOfWeek: day }).firstDayOfWeek).toBe(index + 1); + expect(new Intl.Locale("en", { firstDayOfWeek: index + 1 }).firstDayOfWeek).toBe( + index + 1 + ); + expect(new Intl.Locale("en-u-fw-mon", { firstDayOfWeek: day }).firstDayOfWeek).toBe( + index + 1 + ); + }); + }); +});