From 1a412c63beafe401839fe534f2c2f0ed91a6187d Mon Sep 17 00:00:00 2001 From: Ralph Date: Sun, 28 Jun 2026 12:08:32 -0700 Subject: [PATCH] =?UTF-8?q?fix(intl):=20#5582=20=E2=80=94=20emit=20timeZon?= =?UTF-8?q?eName=20part=20for=20Temporal.Instant=20in=20formatToParts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #5770 (CodeRabbit review). The `timeZoneName`-part guard in `formatToParts` skipped *every* Temporal value, including `Temporal.Instant`. An `Instant` is anchored to the timeline, so — like a `Date`/number — it must render a zone label when `timeZoneName` is requested; only the *plain* Temporal kinds (PlainDate/PlainTime/PlainDateTime/PlainYearMonth/PlainMonthDay) carry no zone and stay suppressed. (`ZonedDateTime`/`Duration` are rejected upstream by `date_arg_to_clipped_ms` and never reach this path.) Behavior-preserving for the test262 intl402/DateTimeFormat suite (pass 145, runtime-fail 78, unchanged); the in-scope timezonename cases use only plain Temporal types. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../perry-runtime/src/intl/date_collator.rs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/perry-runtime/src/intl/date_collator.rs b/crates/perry-runtime/src/intl/date_collator.rs index f4c444e1c..c16093605 100644 --- a/crates/perry-runtime/src/intl/date_collator.rs +++ b/crates/perry-runtime/src/intl/date_collator.rs @@ -110,19 +110,27 @@ fn date_time_format_to_parts_value(obj: *const ObjectHeader, value: f64) -> f64 } /// Append a `timeZoneName` part when the `timeZoneName` option is set and the -/// value being formatted denotes a real instant (a `Date` or numeric -/// timestamp). A Temporal *plain* value (PlainDate/PlainTime/PlainDateTime/…) -/// carries no time zone, so it must NOT print one — see -/// `temporal-*-formatting-timezonename.js`. Perry ships no CLDR zone-name data, -/// so the rendered label is best-effort (the in-scope tests observe only the -/// part's presence and string-ness, all with the UTC default zone). +/// value being formatted is anchored to the timeline (a `Date`/number, or a +/// `Temporal.Instant`). A Temporal *plain* value (PlainDate/PlainTime/ +/// PlainDateTime/PlainYearMonth/PlainMonthDay) carries no time zone, so it must +/// NOT print one — see `temporal-*-formatting-timezonename.js`. +/// (`Temporal.ZonedDateTime`/`Duration` are rejected upstream by +/// `date_arg_to_clipped_ms` and never reach here.) Perry ships no CLDR +/// zone-name data, so the rendered label is best-effort (the in-scope tests +/// observe only the part's presence and string-ness, all with the UTC default). fn append_time_zone_name_part( parts: &mut Vec<(&'static str, String)>, obj: *const ObjectHeader, value: f64, ) { - if crate::temporal::is_temporal_value(value) { - return; + use crate::temporal::TemporalKind::*; + if let Some(kind) = crate::temporal::temporal_kind(value) { + if matches!( + kind, + PlainDate | PlainTime | PlainDateTime | PlainYearMonth | PlainMonthDay + ) { + return; + } } if let Some(style) = get_string_field(obj, KEY_TIME_ZONE_NAME) { let tz = get_string_field(obj, KEY_TIME_ZONE).unwrap_or_else(|| "UTC".to_string());