From 1827e02e53a3b4e7187648d6fadc993ae9214b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 13 Jun 2026 00:34:18 +0200 Subject: [PATCH] fix(date): NaN-revive setters seed from raw +0, not LocalTime(0) (#4927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Date.prototype.setFullYear step 2 substitutes t = +0 for a NaN time value WITHOUT applying LocalTime — the seed components are the raw UTC +0 reading (1970-01-01 00:00:00). js_date_apply_setter substituted 0.0 before calling rebuild_local_with, which then decoded the epoch through timestamp_to_local_components, picking up the 1970 epoch's local offset: a CET process revived new Date(NaN).setFullYear(2020) to local 01:00 (test expected 00:00), and TZ=America/New_York landed on Dec 31 19:00. CI (TZ=UTC) never saw it. Pass the NaN through instead — both rebuild_with and rebuild_local_with already implement the spec's NaN branch (seed 1970/0/1 00:00:00, revive only when a year override is present). --- crates/perry-runtime/src/date.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/perry-runtime/src/date.rs b/crates/perry-runtime/src/date.rs index 467d26f95a..dd2ba7ed9d 100644 --- a/crates/perry-runtime/src/date.rs +++ b/crates/perry-runtime/src/date.rs @@ -876,7 +876,12 @@ pub extern "C" fn js_date_apply_setter( if t.is_nan() && !is_full_year { return f64::NAN; } - let base = if t.is_nan() { 0.0 } else { t }; + // #4927: a NaN time value is passed through to the rebuild AS NaN — its + // NaN branch seeds the spec's `t = +0` substitution from the RAW +0 + // components (1970-01-01 00:00:00), per Date.prototype.setFullYear step 2, + // which skips LocalTime(t). Substituting 0.0 here ran the epoch through + // timestamp_to_local_components, so a CET process revived + // `new Date(NaN).setFullYear(2020)` to local 01:00 instead of 00:00. // The leading component is required: an omitted call (`setHours()`) coerces // `undefined` → NaN, so the leading slot is always `Some`. let lead = Some(coerced.first().copied().unwrap_or(f64::NAN)); @@ -895,9 +900,9 @@ pub extern "C" fn js_date_apply_setter( _ => return date_cell_store(date, f64::NAN), }; if is_utc != 0 { - rebuild_with(date, base, year, month0, day, hour, minute, second, ms) + rebuild_with(date, t, year, month0, day, hour, minute, second, ms) } else { - rebuild_local_with(date, base, year, month0, day, hour, minute, second, ms) + rebuild_local_with(date, t, year, month0, day, hour, minute, second, ms) } }