1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 00:47:45 +00:00

LibJS: Allow out-of-order date ranges to be formatted

This is a normative change to the Intl spec:
769df4b
This commit is contained in:
Timothy Flynn 2022-07-26 06:38:35 -04:00 committed by Tim Flynn
parent 16d189e96b
commit 415742ab98
4 changed files with 60 additions and 29 deletions

View file

@ -49,7 +49,6 @@
M(IntlInvalidTime, "Time value must be between -8.64E15 and 8.64E15") \
M(IntlInvalidUnit, "Unit {} is not a valid time unit") \
M(IntlStartRangeAfterEndRange, "Range start {} is greater than range end {}") \
M(IntlStartTimeAfterEndTime, "Start time {} is after end time {}") \
M(IntlMinimumExceedsMaximum, "Minimum value {} is larger than maximum value {}") \
M(IntlNumberIsNaN, "{} must not be NaN") \
M(IntlNumberIsNaNOrInfinity, "Number must not be NaN or Infinity") \

View file

@ -934,29 +934,25 @@ ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_
if (isnan(end))
return vm.throw_completion<RangeError>(global_object, ErrorType::IntlInvalidTime);
// 5. If x is greater than y, throw a RangeError exception.
if (start > end)
return vm.throw_completion<RangeError>(global_object, ErrorType::IntlStartTimeAfterEndTime, start, end);
// 6. Let tm1 be ToLocalTime(x, dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
// 5. Let tm1 be ToLocalTime(x, dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
auto start_local_time = TRY(to_local_time(global_object, start, date_time_format.calendar(), date_time_format.time_zone()));
// 7. Let tm2 be ToLocalTime(y, dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
// 6. Let tm2 be ToLocalTime(y, dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
auto end_local_time = TRY(to_local_time(global_object, end, date_time_format.calendar(), date_time_format.time_zone()));
// 8. Let rangePatterns be dateTimeFormat.[[RangePatterns]].
// 7. Let rangePatterns be dateTimeFormat.[[RangePatterns]].
auto range_patterns = date_time_format.range_patterns();
// 9. Let rangePattern be undefined.
// 8. Let rangePattern be undefined.
Optional<Unicode::CalendarRangePattern> range_pattern;
// 10. Let dateFieldsPracticallyEqual be true.
// 9. Let dateFieldsPracticallyEqual be true.
bool date_fields_practically_equal = true;
// 11. Let patternContainsLargerDateField be false.
// 10. Let patternContainsLargerDateField be false.
bool pattern_contains_larger_date_field = false;
// 12. While dateFieldsPracticallyEqual is true and patternContainsLargerDateField is false, repeat for each row of Table 4 in order, except the header row:
// 11. While dateFieldsPracticallyEqual is true and patternContainsLargerDateField is false, repeat for each row of Table 4 in order, except the header row:
for_each_range_pattern_field(start_local_time, end_local_time, [&](auto start_value, auto end_value, auto field_name) {
// a. Let fieldName be the name given in the Range Pattern Field column of the row.
@ -1061,7 +1057,7 @@ ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_
return IterationDecision::Break;
});
// 13. If dateFieldsPracticallyEqual is true, then
// 12. If dateFieldsPracticallyEqual is true, then
if (date_fields_practically_equal) {
// a. Let pattern be dateTimeFormat.[[Pattern]].
auto const& pattern = date_time_format.pattern();
@ -1083,10 +1079,10 @@ ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_
return result;
}
// 14. Let result be a new empty List.
// 13. Let result be a new empty List.
Vector<PatternPartitionWithSource> result;
// 15. If rangePattern is undefined, then
// 14. If rangePattern is undefined, then
if (!range_pattern.has_value()) {
// a. Let rangePattern be rangePatterns.[[Default]].
range_pattern = Unicode::get_calendar_default_range_format(date_time_format.data_locale(), date_time_format.calendar());
@ -1112,7 +1108,7 @@ ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_
// step 3 here: https://unicode.org/reports/tr35/tr35-dates.html#intervalFormats
}
// 16. For each Record { [[Pattern]], [[Source]] } rangePatternPart in rangePattern.[[PatternParts]], do
// 15. For each Record { [[Pattern]], [[Source]] } rangePatternPart in rangePattern.[[PatternParts]], do
TRY(for_each_range_pattern_with_source(*range_pattern, [&](auto const& pattern, auto source) -> ThrowCompletionOr<void> {
// a. Let pattern be rangePatternPart.[[Pattern]].
// b. Let source be rangePatternPart.[[Source]].
@ -1141,7 +1137,7 @@ ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_
return {};
}));
// 17. Return result.
// 16. Return result.
return result;
}

View file

@ -36,12 +36,6 @@ describe("errors", () => {
}).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15");
});
});
test("called with values in bad order", () => {
expect(() => {
Intl.DateTimeFormat().formatRange(new Date(2021), new Date(1989));
}).toThrowWithMessage(RangeError, "Start time 2021 is after end time 1989");
});
});
const d0 = Date.UTC(1989, 0, 23, 7, 8, 9, 45);
@ -157,6 +151,14 @@ describe("dateStyle", () => {
expect(ja.formatRange(d0, d1)).toBe(d.ja);
});
});
test("dates in reverse order", () => {
const en = new Intl.DateTimeFormat("en", { dateStyle: "full", timeZone: "UTC" });
expect(en.formatRange(d1, d0)).toBe("Tuesday, December 7, 2021 Monday, January 23, 1989");
const ja = new Intl.DateTimeFormat("ja", { dateStyle: "full", timeZone: "UTC" });
expect(ja.formatRange(d1, d0)).toBe("2021年12月7日火曜日1989年1月23日月曜日");
});
});
describe("timeStyle", () => {

View file

@ -36,12 +36,6 @@ describe("errors", () => {
}).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15");
});
});
test("called with values in bad order", () => {
expect(() => {
Intl.DateTimeFormat().formatRangeToParts(new Date(2021), new Date(1989));
}).toThrowWithMessage(RangeError, "Start time 2021 is after end time 1989");
});
});
const d0 = Date.UTC(1989, 0, 23, 7, 8, 9, 45);
@ -343,6 +337,46 @@ describe("dateStyle", () => {
{ type: "day", value: "07", source: "endRange" },
]);
});
test("dates in reverse order", () => {
const en = new Intl.DateTimeFormat("en", { dateStyle: "full", timeZone: "UTC" });
expect(en.formatRangeToParts(d1, d0)).toEqual([
{ type: "weekday", value: "Tuesday", source: "startRange" },
{ type: "literal", value: ", ", source: "startRange" },
{ type: "month", value: "December", source: "startRange" },
{ type: "literal", value: " ", source: "startRange" },
{ type: "day", value: "7", source: "startRange" },
{ type: "literal", value: ", ", source: "startRange" },
{ type: "year", value: "2021", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "weekday", value: "Monday", source: "endRange" },
{ type: "literal", value: ", ", source: "endRange" },
{ type: "month", value: "January", source: "endRange" },
{ type: "literal", value: " ", source: "endRange" },
{ type: "day", value: "23", source: "endRange" },
{ type: "literal", value: ", ", source: "endRange" },
{ type: "year", value: "1989", source: "endRange" },
]);
const ja = new Intl.DateTimeFormat("ja", { dateStyle: "full", timeZone: "UTC" });
expect(ja.formatRangeToParts(d1, d0)).toEqual([
{ type: "year", value: "2021", source: "startRange" },
{ type: "literal", value: "年", source: "startRange" },
{ type: "month", value: "12", source: "startRange" },
{ type: "literal", value: "月", source: "startRange" },
{ type: "day", value: "7", source: "startRange" },
{ type: "literal", value: "日", source: "startRange" },
{ type: "weekday", value: "火曜日", source: "startRange" },
{ type: "literal", value: "", source: "shared" },
{ type: "year", value: "1989", source: "endRange" },
{ type: "literal", value: "年", source: "endRange" },
{ type: "month", value: "1", source: "endRange" },
{ type: "literal", value: "月", source: "endRange" },
{ type: "day", value: "23", source: "endRange" },
{ type: "literal", value: "日", source: "endRange" },
{ type: "weekday", value: "月曜日", source: "endRange" },
]);
});
});
describe("timeStyle", () => {