From 237ea5ce84ceec4622f9a63babad7c63843e4519 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 09:03:42 +0000 Subject: [PATCH] js-on-sx: Date.UTC and new Date propagate NaN/Infinity args --- lib/js/runtime.sx | 36 +++++++++++++++++++++++++++++++++--- plans/js-on-sx.md | 2 ++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index ebd6ccc6..9a2c6a67 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -1111,8 +1111,10 @@ (v) (cond ((number? v) v) + ((= (type-of v) "rational") (exact->inexact v)) ((= (type-of v) "string") (js-date-parse-string v)) - (else 0)))) + ((js-undefined? v) (js-nan-value)) + (else (js-to-number v))))) (define js-date-parse-string @@ -1159,6 +1161,26 @@ (* secs 1000) ms)))))) +(define + js-date-args-have-nan? + (fn + (args i) + (cond + ((>= i (len args)) false) + (else + (let + ((n (js-to-number (nth args i)))) + (cond + ((or (number? n) (= (type-of n) "rational")) + (let + ((nf (if (= (type-of n) "rational") (exact->inexact n) n))) + (cond + ((js-number-is-nan nf) true) + ((= nf (js-infinity-value)) true) + ((= nf (- 0 (js-infinity-value))) true) + (else (js-date-args-have-nan? args (+ i 1)))))) + (else (js-date-args-have-nan? args (+ i 1))))))))) + (define js-date-civil-to-days (fn @@ -1219,6 +1241,7 @@ (cond ((= (len args) 0) 0) ((= (len args) 1) (js-date-from-one (nth args 0))) + ((js-date-args-have-nan? args 0) (js-nan-value)) (else (js-date-from-parts args)))) (dict-set! this "__js_is_date__" true) this))))) @@ -1228,8 +1251,15 @@ (fn (&rest args) (cond - ((= (len args) 0) 0) - (else (js-date-from-parts args)))) + ((= (len args) 0) (js-nan-value)) + ((js-date-args-have-nan? args 0) (js-nan-value)) + (else + (let + ((ms (js-date-from-parts args))) + (cond + ((or (> ms 8640000000000000) (< ms -8640000000000000)) + (js-nan-value)) + (else ms)))))) :prototype {:getTime (fn () (js-date-time-value (js-this))) :valueOf (fn () (js-date-time-value (js-this))) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 85649175..a9776e4e 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. +- 2026-05-10 — **`Date.UTC` / `new Date(...)` propagate NaN/±Infinity arguments and return NaN.** `Date.UTC()` (no args) returned 0 instead of NaN; `Date.UTC(NaN, ...)` did the math and produced bogus ms; `new Date(year, NaN)` constructed a normal Date instead of an invalid one. Added `js-date-args-have-nan?` (also detects ±Infinity and propagates from rationals) used by both `Date.UTC` and the multi-arg constructor branch; UTC now returns NaN on no-arg / any-NaN-arg / out-of-range result, and `new Date(args)` stores NaN in `__date_value__` when any arg is NaN. Also fixed `js-date-from-one(undefined)` to return NaN. Result: built-ins/Date/UTC 6/16 → 10/16 (+4). Date 17/30 → 26/30 (timeouts dropped from 12 → 4 because invalid Dates now short-circuit). conformance.sh: 148/148. + - 2026-05-10 — **Real `Date` construction + getters via Howard-Hinnant civil-day arithmetic.** `js-date-from-parts` now computes a true ms-since-epoch from `(year, month, day, hour, min, sec, ms)` via `js-date-civil-to-days` (the inverse of last iteration's `days-to-ymd`), with the legacy 2-digit-year coercion (0..99 → 1900+y). `getFullYear/Month/Date/Day/Hours/Minutes/Seconds/Milliseconds` (UTC + non-UTC) all share a new `js-date-getter`: TypeErrors on non-Date this, returns NaN on invalid time, otherwise decomposes ms into y/m/d/h/m/s/ms/dow. Plus added `Date.prototype.constructor = Date` (was missing). Result: each of the 8 Date getter categories went 2/6 → 5/6 (+3 each, +24 total). Date toISOString 11/16 → 13/16. Some Date construction-loop tests now exceed the 15s per-test timeout — the new civil math is heavier than the old (year-1970)*ms-per-year approximation, but correctness wins. conformance.sh: 148/148. - 2026-05-10 — **`Date.prototype.toISOString` produces real `YYYY-MM-DDTHH:mm:ss.sssZ` and validates input.** Old `js-date-iso` only computed the year and hardcoded the rest as `01-01T00:00:00.000Z`. Added: (1) TypeError when this isn't a Date (no `__js_is_date__` slot); (2) RangeError when ms is NaN, undefined, or |ms| > 8.64e15; (3) full date breakdown via Howard-Hinnant `days_to_civil` algorithm (`js-date-days-to-ymd`) → year/month/day, plus modular hours/min/sec/ms; (4) extended-year format `±YYYYYY` for years outside 0..9999. Result: built-ins/Date/prototype/toISOString 7/16 → 11/16 (+4). Date 21/30. conformance.sh: 148/148.