From b8d4f8debf46e9591102bf8d609b4f32cab6b3f3 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 21 Jul 2022 14:41:47 -0400 Subject: [PATCH] LibJS: Selectively display DateTimeFormat day periods as noon --- .../LibJS/Runtime/Intl/DateTimeFormat.cpp | 27 +++++++++++- .../DateTimeFormat.prototype.format.js | 42 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp index d18eba5ca0..1d188c9b7d 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -503,6 +504,30 @@ static Optional find_calendar_field(StringView name, Unicode::Cal return {}; } +static Optional resolve_day_period(StringView locale, StringView calendar, Unicode::CalendarPatternStyle style, Span pattern_parts, LocalTime local_time) +{ + // Use the "noon" day period if the locale has it, but only if the time is either exactly 12:00.00 or would be displayed as such. + if (local_time.hour == 12) { + auto it = find_if(pattern_parts.begin(), pattern_parts.end(), [&](auto const& part) { + if (part.type == "minute"sv && local_time.minute != 0) + return true; + if (part.type == "second"sv && local_time.second != 0) + return true; + if (part.type == "fractionalSecondDigits"sv && local_time.millisecond != 0) + return true; + return false; + }); + + if (it == pattern_parts.end()) { + auto noon_symbol = Unicode::get_calendar_day_period_symbol(locale, calendar, style, Unicode::DayPeriod::Noon); + if (noon_symbol.has_value()) + return *noon_symbol; + } + } + + return Unicode::get_calendar_day_period_symbol_for_hour(locale, calendar, style, local_time.hour); +} + // 11.5.6 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern ThrowCompletionOr> format_date_time_pattern(GlobalObject& global_object, DateTimeFormat& date_time_format, Vector pattern_parts, double time, Unicode::CalendarPattern const* range_format_options) { @@ -606,7 +631,7 @@ ThrowCompletionOr> format_date_time_pattern(GlobalObjec auto style = date_time_format.day_period(); // ii. Let fv be a String value representing the day period of tm in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat. - auto symbol = Unicode::get_calendar_day_period_symbol_for_hour(data_locale, date_time_format.calendar(), style, local_time.hour); + auto symbol = resolve_day_period(data_locale, date_time_format.calendar(), style, pattern_parts, local_time); if (symbol.has_value()) formatted_value = *symbol; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js index 709a5d7331..164e571a70 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/DateTimeFormat/DateTimeFormat.prototype.format.js @@ -273,6 +273,48 @@ describe("dayPeriod", () => { expect(en.format(date1)).toBe("5 at night"); expect(en.format(date2)).toBe("11 at night"); }); + + test("noon", () => { + const date0 = Date.UTC(2017, 11, 12, 12, 0, 0, 0); + const date1 = Date.UTC(2017, 11, 12, 12, 1, 0, 0); + const date2 = Date.UTC(2017, 11, 12, 12, 0, 1, 0); + const date3 = Date.UTC(2017, 11, 12, 12, 0, 0, 500); + + // prettier-ignore + const data = [ + { minute: undefined, second: undefined, fractionalSecondDigits: undefined, en0: "12 noon", en1: "12 noon", en2: "12 noon", en3: "12 noon", ar0: "١٢ ظهرًا", ar1: "١٢ ظهرًا", ar2: "١٢ ظهرًا", ar3: "١٢ ظهرًا" }, + { minute: "numeric", second: undefined, fractionalSecondDigits: undefined, en0: "12:00 noon", en1: "12:01 in the afternoon", en2: "12:00 noon", en3: "12:00 noon", ar0: "١٢:٠٠ ظهرًا", ar1: "١٢:٠١ ظهرًا", ar2: "١٢:٠٠ ظهرًا", ar3: "١٢:٠٠ ظهرًا" }, + { minute: "numeric", second: "numeric", fractionalSecondDigits: undefined, en0: "12:00:00 noon", en1: "12:01:00 in the afternoon", en2: "12:00:01 in the afternoon", en3: "12:00:00 noon", ar0: "١٢:٠٠:٠٠ ظهرًا", ar1: "١٢:٠١:٠٠ ظهرًا", ar2: "١٢:٠٠:٠١ ظهرًا", ar3: "١٢:٠٠:٠٠ ظهرًا" }, + { minute: "numeric", second: "numeric", fractionalSecondDigits: 1, en0: "12:00:00.0 noon", en1: "12:01:00.0 in the afternoon", en2: "12:00:01.0 in the afternoon", en3: "12:00:00.5 in the afternoon", ar0: "١٢:٠٠:٠٠٫٠ ظهرًا", ar1: "١٢:٠١:٠٠٫٠ ظهرًا", ar2: "١٢:٠٠:٠١٫٠ ظهرًا", ar3: "١٢:٠٠:٠٠٫٥ ظهرًا" }, + ]; + + // The en locale includes the "noon" fixed day period, whereas the ar locale does not. + data.forEach(d => { + const en = new Intl.DateTimeFormat("en", { + dayPeriod: "short", + timeZone: "UTC", + minute: d.minute, + second: d.second, + fractionalSecondDigits: d.fractionalSecondDigits, + }); + expect(en.format(date0)).toBe(d.en0); + expect(en.format(date1)).toBe(d.en1); + expect(en.format(date2)).toBe(d.en2); + expect(en.format(date3)).toBe(d.en3); + + const ar = new Intl.DateTimeFormat("ar", { + dayPeriod: "short", + timeZone: "UTC", + minute: d.minute, + second: d.second, + fractionalSecondDigits: d.fractionalSecondDigits, + }); + expect(ar.format(date0)).toBe(d.ar0); + expect(ar.format(date1)).toBe(d.ar1); + expect(ar.format(date2)).toBe(d.ar2); + expect(ar.format(date3)).toBe(d.ar3); + }); + }); }); describe("hour", () => {