From 72f61396cd1c7b907792f6d79adbfbd193d9b55c Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 13 Aug 2024 13:03:17 -0400 Subject: [PATCH] LibJS: Correctly display a negative sign on negative durations This is a normative change in the Intl.DurationFormat proposal. See: https://github.com/tc39/proposal-intl-duration-format/commit/adfc4a1 --- .../LibJS/Runtime/Intl/DurationFormat.cpp | 34 ++++++++++++++----- .../DurationFormat.prototype.format.js | 21 ++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp index f6ec6e903b4..ebd31a81a85 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp @@ -615,7 +615,7 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& return result; } -// 1.1.12 FormatNumericUnits ( durationFormat, duration, firstNumericUnit, signDisplayed ) +// 1.1.12 FormatNumericUnits ( durationFormat, duration, firstNumericUnit, signDisplayed ), https://tc39.es/proposal-intl-duration-format/#sec-formatnumericunits Vector format_numeric_units(VM& vm, DurationFormat const& duration_format, Temporal::DurationRecord const& duration, StringView first_numeric_unit, bool sign_displayed) { // 1. Assert: firstNumericUnit is "hours", "minutes", or "seconds". @@ -685,20 +685,38 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 16. If hoursFormatted is true, then if (hours_formatted) { - // a. Append FormatNumericHours(durationFormat, hoursValue, signDisplayed) to numericPartsList. + // a. If signDisplayed is true, then + if (sign_displayed) { + // i. If hoursValue is 0 and DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]) is -1, then + if (hours_value == 0 && Temporal::duration_sign(duration.years, duration.months, duration.weeks, duration.days, duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds) == -1) { + // 1. Set hoursValue to negative-zero. + hours_value = -0.0; + } + } + + // b. Append FormatNumericHours(durationFormat, hoursValue, signDisplayed) to numericPartsList. numeric_parts_list.extend(format_numeric_hours(vm, duration_format, hours_value, sign_displayed)); - // b. Set signDisplayed to false. - sign_displayed = hours_value < 0; + // c. Set signDisplayed to false. + sign_displayed = false; } // 17. If minutesFormatted is true, then if (minutes_formatted) { - // a. Append FormatNumericMinutes(durationFormat, minutesValue, hoursFormatted, signDisplayed) to numericPartsList. + // a. If signDisplayed is true, then + if (sign_displayed) { + // i. If minutesValue is 0 and DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]) is -1, then + if (minutes_value == 0 && Temporal::duration_sign(duration.years, duration.months, duration.weeks, duration.days, duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds) == -1) { + // 1. Set minutesValue to negative-zero. + minutes_value = -0.0; + } + } + + // b. Append FormatNumericMinutes(durationFormat, minutesValue, hoursFormatted, signDisplayed) to numericPartsList. numeric_parts_list.extend(format_numeric_minutes(vm, duration_format, minutes_value, hours_formatted, sign_displayed)); - // b. Set signDisplayed to false. - sign_displayed = minutes_value < 0; + // c. Set signDisplayed to false. + sign_displayed = false; } // 18. If secondsFormatted is true, then @@ -707,7 +725,7 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du numeric_parts_list.extend(format_numeric_seconds(vm, duration_format, seconds_value, minutes_formatted, sign_displayed)); // b. Set signDisplayed to false. - sign_displayed = seconds_value < 0; + sign_displayed = false; } // 19. Return numericPartsList. diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js index 03424106e52..5c2e241695d 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/DurationFormat/DurationFormat.prototype.format.js @@ -110,6 +110,27 @@ describe("correct behavior", () => { const lt = new Intl.DurationFormat("lt", { style: "digital" }); expect(lt.format(duration)).toBe("01:02:03"); }); + + test("negative duration fields", () => { + const duration = { + years: -1, + months: -2, + weeks: -3, + days: -4, + hours: -5, + minutes: -6, + seconds: -7, + milliseconds: -8, + microseconds: -9, + nanoseconds: -11, + }; + + const en = new Intl.DurationFormat("en", { style: "digital" }); + expect(en.format(duration)).toBe("-1 yr, 2 mths, 3 wks, 4 days, 5:06:07.008009011"); + + const de = new Intl.DurationFormat("de", { style: "digital" }); + expect(de.format(duration)).toBe("-1 J, 2 Mon., 3 Wo., 4 Tg. und 5:06:07,008009011"); + }); }); describe("errors", () => {