mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 06:27:45 +00:00
LibJS: Implement the Extend TimeZoneName Option Proposal
This is a stage 4 proposal that was recently merged into the main
ECMA-402 spec. See:
1ba5ee7
This commit is contained in:
parent
126a3fe180
commit
022b416570
5 changed files with 92 additions and 21 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
|
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@pm.me>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -587,20 +587,23 @@ Optional<Unicode::CalendarPattern> basic_format_matcher(Unicode::CalendarPattern
|
||||||
// 6. Let shortMorePenalty be 3.
|
// 6. Let shortMorePenalty be 3.
|
||||||
constexpr int short_more_penalty = 3;
|
constexpr int short_more_penalty = 3;
|
||||||
|
|
||||||
// 7. Let bestScore be -Infinity.
|
// 7. Let offsetPenalty be 1.
|
||||||
|
constexpr int offset_penalty = 1;
|
||||||
|
|
||||||
|
// 8. Let bestScore be -Infinity.
|
||||||
int best_score = NumericLimits<int>::min();
|
int best_score = NumericLimits<int>::min();
|
||||||
|
|
||||||
// 8. Let bestFormat be undefined.
|
// 9. Let bestFormat be undefined.
|
||||||
Optional<Unicode::CalendarPattern> best_format;
|
Optional<Unicode::CalendarPattern> best_format;
|
||||||
|
|
||||||
// 9. Assert: Type(formats) is List.
|
// 10. Assert: Type(formats) is List.
|
||||||
// 10. For each element format of formats, do
|
// 11. For each element format of formats, do
|
||||||
for (auto& format : formats) {
|
for (auto& format : formats) {
|
||||||
// a. Let score be 0.
|
// a. Let score be 0.
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
// b. For each property name property shown in Table 4, do
|
// b. For each property name property shown in Table 4, do
|
||||||
format.for_each_calendar_field_zipped_with(options, [&](auto const& format_prop, auto const& options_prop, auto) {
|
format.for_each_calendar_field_zipped_with(options, [&](auto const& format_prop, auto const& options_prop, auto type) {
|
||||||
using ValueType = typename RemoveReference<decltype(options_prop)>::ValueType;
|
using ValueType = typename RemoveReference<decltype(options_prop)>::ValueType;
|
||||||
|
|
||||||
// i. If options has a field [[<property>]], let optionsProp be options.[[<property>]]; else let optionsProp be undefined.
|
// i. If options has a field [[<property>]], let optionsProp be options.[[<property>]]; else let optionsProp be undefined.
|
||||||
|
@ -614,7 +617,62 @@ Optional<Unicode::CalendarPattern> basic_format_matcher(Unicode::CalendarPattern
|
||||||
else if (options_prop.has_value() && !format_prop.has_value()) {
|
else if (options_prop.has_value() && !format_prop.has_value()) {
|
||||||
score -= removal_penalty;
|
score -= removal_penalty;
|
||||||
}
|
}
|
||||||
// v. Else if optionsProp ≠ formatProp, then
|
// v. Else if property is "timeZoneName", then
|
||||||
|
else if (type == Unicode::CalendarPattern::Field::TimeZoneName) {
|
||||||
|
// This is needed to avoid a compile error. Although we only enter this branch for TimeZoneName,
|
||||||
|
// the lambda we are in will be generated with property types other than CalendarPatternStyle.
|
||||||
|
auto compare_prop = [](auto prop, auto test) { return prop == static_cast<ValueType>(test); };
|
||||||
|
|
||||||
|
// 1. If optionsProp is "short" or "shortGeneric", then
|
||||||
|
if (compare_prop(options_prop, Unicode::CalendarPatternStyle::Short) || compare_prop(options_prop, Unicode::CalendarPatternStyle::ShortGeneric)) {
|
||||||
|
// a. If formatProp is "shortOffset", decrease score by offsetPenalty.
|
||||||
|
if (compare_prop(format_prop, Unicode::CalendarPatternStyle::ShortOffset))
|
||||||
|
score -= offset_penalty;
|
||||||
|
// b. Else if formatProp is "longOffset", decrease score by (offsetPenalty + shortMorePenalty).
|
||||||
|
else if (compare_prop(format_prop, Unicode::CalendarPatternStyle::LongOffset))
|
||||||
|
score -= offset_penalty + short_more_penalty;
|
||||||
|
// c. Else if optionsProp is "short" and formatProp is "long", decrease score by shortMorePenalty.
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::Short) || compare_prop(format_prop, Unicode::CalendarPatternStyle::Long))
|
||||||
|
score -= short_more_penalty;
|
||||||
|
// d. Else if optionsProp is "shortGeneric" and formatProp is "longGeneric", decrease score by shortMorePenalty.
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::ShortGeneric) || compare_prop(format_prop, Unicode::CalendarPatternStyle::LongGeneric))
|
||||||
|
score -= short_more_penalty;
|
||||||
|
// e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
|
||||||
|
else if (options_prop != format_prop)
|
||||||
|
score -= removal_penalty;
|
||||||
|
}
|
||||||
|
// 2. Else if optionsProp is "shortOffset" and formatProp is "longOffset", decrease score by shortMorePenalty.
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::ShortOffset) || compare_prop(format_prop, Unicode::CalendarPatternStyle::LongOffset)) {
|
||||||
|
score -= short_more_penalty;
|
||||||
|
}
|
||||||
|
// 3. Else if optionsProp is "long" or "longGeneric", then
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::Long) || compare_prop(options_prop, Unicode::CalendarPatternStyle::LongGeneric)) {
|
||||||
|
// a. If formatProp is "longOffset", decrease score by offsetPenalty.
|
||||||
|
if (compare_prop(format_prop, Unicode::CalendarPatternStyle::LongOffset))
|
||||||
|
score -= offset_penalty;
|
||||||
|
// b. Else if formatProp is "shortOffset", decrease score by (offsetPenalty + longLessPenalty).
|
||||||
|
else if (compare_prop(format_prop, Unicode::CalendarPatternStyle::ShortOffset))
|
||||||
|
score -= offset_penalty + long_less_penalty;
|
||||||
|
// c. Else if optionsProp is "long" and formatProp is "short", decrease score by longLessPenalty.
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::Long) || compare_prop(format_prop, Unicode::CalendarPatternStyle::Short))
|
||||||
|
score -= long_less_penalty;
|
||||||
|
// d. Else if optionsProp is "longGeneric" and formatProp is "shortGeneric", decrease score by longLessPenalty.
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::LongGeneric) || compare_prop(format_prop, Unicode::CalendarPatternStyle::ShortGeneric))
|
||||||
|
score -= long_less_penalty;
|
||||||
|
// e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
|
||||||
|
else if (options_prop != format_prop)
|
||||||
|
score -= removal_penalty;
|
||||||
|
}
|
||||||
|
// 4. Else if optionsProp is "longOffset" and formatProp is "shortOffset", decrease score by longLessPenalty.
|
||||||
|
else if (compare_prop(options_prop, Unicode::CalendarPatternStyle::LongOffset) || compare_prop(format_prop, Unicode::CalendarPatternStyle::ShortOffset)) {
|
||||||
|
score -= long_less_penalty;
|
||||||
|
}
|
||||||
|
// 5. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
|
||||||
|
else if (options_prop != format_prop) {
|
||||||
|
score -= removal_penalty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// vi. Else if optionsProp ≠ formatProp, then
|
||||||
else if (options_prop != format_prop) {
|
else if (options_prop != format_prop) {
|
||||||
using ValuesType = Conditional<IsIntegral<ValueType>, AK::Array<u8, 3>, AK::Array<Unicode::CalendarPatternStyle, 5>>;
|
using ValuesType = Conditional<IsIntegral<ValueType>, AK::Array<u8, 3>, AK::Array<Unicode::CalendarPatternStyle, 5>>;
|
||||||
ValuesType values {};
|
ValuesType values {};
|
||||||
|
@ -701,7 +759,7 @@ Optional<Unicode::CalendarPattern> basic_format_matcher(Unicode::CalendarPattern
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 11. Return bestFormat.
|
// 12. Return bestFormat.
|
||||||
return best_format;
|
return best_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,7 +939,9 @@ ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(GlobalObjec
|
||||||
// ii. Let v be dateTimeFormat.[[TimeZone]].
|
// ii. Let v be dateTimeFormat.[[TimeZone]].
|
||||||
auto const& value = date_time_format.time_zone();
|
auto const& value = date_time_format.time_zone();
|
||||||
|
|
||||||
// iii. Let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale. The String value may also depend on the value of the [[InDST]] field of tm. If the implementation does not have a localized representation of f, then use the String value of v itself.
|
// iii. Let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat.
|
||||||
|
// The String value may also depend on the value of the [[InDST]] field of tm if f is "short", "long", "shortOffset", or "longOffset".
|
||||||
|
// If the implementation does not have a localized representation of f, then use the String value of v itself.
|
||||||
// FIXME: This should take [[InDST]] into account.
|
// FIXME: This should take [[InDST]] into account.
|
||||||
auto formatted_value = Unicode::get_time_zone_name(data_locale, value, style).value_or(value);
|
auto formatted_value = Unicode::get_time_zone_name(data_locale, value, style).value_or(value);
|
||||||
|
|
||||||
|
@ -973,6 +1033,9 @@ ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(GlobalObjec
|
||||||
formatted_value = symbol.value_or(String::number(value));
|
formatted_value = symbol.value_or(String::number(value));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
// xi. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
|
// xi. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
|
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@pm.me>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -216,7 +216,7 @@ ThrowCompletionOr<void> for_each_calendar_field(GlobalObject& global_object, Uni
|
||||||
constexpr auto narrow_short_long = AK::Array { "narrow"sv, "short"sv, "long"sv };
|
constexpr auto narrow_short_long = AK::Array { "narrow"sv, "short"sv, "long"sv };
|
||||||
constexpr auto two_digit_numeric = AK::Array { "2-digit"sv, "numeric"sv };
|
constexpr auto two_digit_numeric = AK::Array { "2-digit"sv, "numeric"sv };
|
||||||
constexpr auto two_digit_numeric_narrow_short_long = AK::Array { "2-digit"sv, "numeric"sv, "narrow"sv, "short"sv, "long"sv };
|
constexpr auto two_digit_numeric_narrow_short_long = AK::Array { "2-digit"sv, "numeric"sv, "narrow"sv, "short"sv, "long"sv };
|
||||||
constexpr auto short_long = AK::Array { "short"sv, "long"sv };
|
constexpr auto time_zone = AK::Array { "short"sv, "long"sv, "shortOffset"sv, "longOffset"sv, "shortGeneric"sv, "longGeneric"sv };
|
||||||
|
|
||||||
// Table 4: Components of date and time formats, https://tc39.es/ecma402/#table-datetimeformat-components
|
// Table 4: Components of date and time formats, https://tc39.es/ecma402/#table-datetimeformat-components
|
||||||
TRY(callback(pattern.weekday, vm.names.weekday, narrow_short_long));
|
TRY(callback(pattern.weekday, vm.names.weekday, narrow_short_long));
|
||||||
|
@ -229,7 +229,7 @@ ThrowCompletionOr<void> for_each_calendar_field(GlobalObject& global_object, Uni
|
||||||
TRY(callback(pattern.minute, vm.names.minute, two_digit_numeric));
|
TRY(callback(pattern.minute, vm.names.minute, two_digit_numeric));
|
||||||
TRY(callback(pattern.second, vm.names.second, two_digit_numeric));
|
TRY(callback(pattern.second, vm.names.second, two_digit_numeric));
|
||||||
TRY(callback(pattern.fractional_second_digits, vm.names.fractionalSecondDigits, Empty {}));
|
TRY(callback(pattern.fractional_second_digits, vm.names.fractionalSecondDigits, Empty {}));
|
||||||
TRY(callback(pattern.time_zone_name, vm.names.timeZoneName, short_long));
|
TRY(callback(pattern.time_zone_name, vm.names.timeZoneName, time_zone));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -359,11 +359,13 @@ describe("normal behavior", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all valid timeZoneName options", () => {
|
test("all valid timeZoneName options", () => {
|
||||||
["short", "long"].forEach(timeZoneName => {
|
["short", "long", "shortOffset", "longOffset", "shortGeneric", "longGeneric"].forEach(
|
||||||
expect(() => {
|
timeZoneName => {
|
||||||
new Intl.DateTimeFormat("en", { timeZoneName: timeZoneName });
|
expect(() => {
|
||||||
}).not.toThrow();
|
new Intl.DateTimeFormat("en", { timeZoneName: timeZoneName });
|
||||||
});
|
}).not.toThrow();
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all valid formatMatcher options", () => {
|
test("all valid formatMatcher options", () => {
|
||||||
|
|
|
@ -377,6 +377,10 @@ describe("timeZoneName", () => {
|
||||||
const data = [
|
const data = [
|
||||||
{ timeZoneName: "short", en0: "12/7/2021, 5:40 PM UTC", en1: "1/23/1989, 7:08 AM UTC", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م UTC", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص UTC" },
|
{ timeZoneName: "short", en0: "12/7/2021, 5:40 PM UTC", en1: "1/23/1989, 7:08 AM UTC", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م UTC", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص UTC" },
|
||||||
{ timeZoneName: "long", en0: "12/7/2021, 5:40 PM Coordinated Universal Time", en1: "1/23/1989, 7:08 AM Coordinated Universal Time", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م التوقيت العالمي المنسق", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص التوقيت العالمي المنسق" },
|
{ timeZoneName: "long", en0: "12/7/2021, 5:40 PM Coordinated Universal Time", en1: "1/23/1989, 7:08 AM Coordinated Universal Time", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م التوقيت العالمي المنسق", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص التوقيت العالمي المنسق" },
|
||||||
|
{ timeZoneName: "shortOffset", en0: "12/7/2021, 5:40 PM GMT", en1: "1/23/1989, 7:08 AM GMT", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص غرينتش" },
|
||||||
|
{ timeZoneName: "longOffset", en0: "12/7/2021, 5:40 PM GMT", en1: "1/23/1989, 7:08 AM GMT", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص غرينتش" },
|
||||||
|
{ timeZoneName: "shortGeneric", en0: "12/7/2021, 5:40 PM GMT", en1: "1/23/1989, 7:08 AM GMT", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص غرينتش" },
|
||||||
|
{ timeZoneName: "longGeneric", en0: "12/7/2021, 5:40 PM GMT", en1: "1/23/1989, 7:08 AM GMT", ar0: "٧/١٢/٢٠٢١, ٥:٤٠ م غرينتش", ar1: "٢٣/١/١٩٨٩, ٧:٠٨ ص غرينتش" },
|
||||||
];
|
];
|
||||||
|
|
||||||
test("all", () => {
|
test("all", () => {
|
||||||
|
|
|
@ -186,9 +186,11 @@ describe("correct behavior", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("timeZoneName", () => {
|
test("timeZoneName", () => {
|
||||||
["short", "long"].forEach(timeZoneName => {
|
["short", "long", "shortOffset", "longOffset", "shortGeneric", "longGeneric"].forEach(
|
||||||
const en = new Intl.DateTimeFormat("en", { timeZoneName: timeZoneName });
|
timeZoneName => {
|
||||||
expect(en.resolvedOptions().timeZoneName).toBe(timeZoneName);
|
const en = new Intl.DateTimeFormat("en", { timeZoneName: timeZoneName });
|
||||||
});
|
expect(en.resolvedOptions().timeZoneName).toBe(timeZoneName);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue