From 0c1a9be26f984a970bfe5cb3c29f8c2f3a246044 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:54:27 +0000 Subject: [PATCH] Intl.DateTimeFormat: do not set [[DayPeriod]] from the AM/PM pattern field setFormatsFromPattern maps the ICU pattern back into the resolvedOptions fields. It was treating 'a' (the AM/PM marker, which is emitted whenever the resolved hour cycle is h11/h12) the same as 'b'/'B' (the flexible day-period fields that the ECMA-402 dayPeriod option actually requests), so any 12-hour component formatter reported resolvedOptions().dayPeriod === "short" even though no dayPeriod option was passed. Feeding those options back into the constructor then requests dayPeriod for real, and buildSkeleton turns that into 'B', so the round-trip changes "10:05 AM" into "10:05 in the morning". Only 'b'/'B' populate m_dayPeriod now; 'a' is left to hourCycle/hour12. m_dayPeriod is read exclusively by resolvedOptions(), so formatToParts (which maps UDAT_AM_PM_FIELD independently) is unaffected. Matches V8. --- JSTests/stress/intl-datetimeformat.js | 2 +- Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/JSTests/stress/intl-datetimeformat.js b/JSTests/stress/intl-datetimeformat.js index 6bb394e393562..e40ff6026cd9f 100644 --- a/JSTests/stress/intl-datetimeformat.js +++ b/JSTests/stress/intl-datetimeformat.js @@ -700,7 +700,7 @@ shouldBe(JSON.stringify(Intl.DateTimeFormat('zh', { era: 'short', year: 'numeric { let dtf = new Intl.DateTimeFormat("en", {hour: "numeric", minute: "numeric", second: "numeric", fractionalSecondDigits: 1}); shouldBe(dtf.format(t), `7:00:23.1 AM`); - shouldBe(JSON.stringify(dtf.resolvedOptions()), `{"locale":"en","calendar":"gregory","numberingSystem":"latn","timeZone":"America/Los_Angeles","hourCycle":"h12","hour12":true,"dayPeriod":"short","hour":"numeric","minute":"2-digit","second":"2-digit","fractionalSecondDigits":1}`); + shouldBe(JSON.stringify(dtf.resolvedOptions()), `{"locale":"en","calendar":"gregory","numberingSystem":"latn","timeZone":"America/Los_Angeles","hourCycle":"h12","hour12":true,"hour":"numeric","minute":"2-digit","second":"2-digit","fractionalSecondDigits":1}`); } shouldThrow(() => { new Intl.DateTimeFormat("en", {hour: "numeric", minute: "numeric", second: "numeric", fractionalSecondDigits: 0}); diff --git a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp b/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp index 48fe494425647..caf284133b680 100644 --- a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp +++ b/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp @@ -342,6 +342,10 @@ void IntlDateTimeFormat::setFormatsFromPattern(IntlDateTimeFormatImpl& impl, Str impl.m_day = Day::TwoDigit; break; case 'a': + // AM/PM marker. Governed by hourCycle/hour12, not the dayPeriod + // option (ECMA-402 Table: Components of date and time formats), + // so it does not populate [[DayPeriod]] for resolvedOptions(). + break; case 'b': case 'B': if (count <= 3)