js-on-sx: Date.prototype.toISOString proper YMDhms format + Type/RangeError gates
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s

This commit is contained in:
2026-05-10 07:39:40 +00:00
parent fb8bb9f105
commit 5bb65d8315
2 changed files with 131 additions and 4 deletions

View File

@@ -1193,12 +1193,137 @@
js-date-iso
(fn
(d)
(cond
((or (not (dict? d)) (not (contains? (keys d) "__js_is_date__")))
(raise (js-new-call TypeError (js-args "this is not a Date object"))))
(else
(let
((ms-raw (get d "__date_value__")))
(let
((ms (if (= (type-of ms-raw) "rational") (exact->inexact ms-raw) ms-raw)))
(cond
((or (= ms nil) (js-undefined? ms))
(raise (js-new-call RangeError (js-args "Invalid time value"))))
((not (number? ms))
(raise (js-new-call RangeError (js-args "Invalid time value"))))
((js-number-is-nan ms)
(raise (js-new-call RangeError (js-args "Invalid time value"))))
((or (> ms 8640000000000000) (< ms -8640000000000000))
(raise (js-new-call RangeError (js-args "Invalid time value"))))
(else (js-date-iso-format ms)))))))))
(define
js-date-iso-format
(fn
(ms)
(let
((ms (get d "__date_value__")))
((day-ms 86400000) (sec-ms 1000) (min-ms 60000) (hr-ms 3600000))
(let
((year
(+ 1970 (js-math-trunc (/ ms 31557600000)))))
(str (js-date-year-pad year) "-01-01T00:00:00.000Z")))))
((days (floor (/ ms day-ms)))
(time-of-day
(let ((m (modulo (js-num-to-int ms) day-ms)))
(if (< m 0) (+ m day-ms) m))))
(let
((hh (js-math-trunc (/ time-of-day hr-ms)))
(mm (js-math-trunc (/ (modulo time-of-day hr-ms) min-ms)))
(ss (js-math-trunc (/ (modulo time-of-day min-ms) sec-ms)))
(msec (modulo time-of-day sec-ms))
(ymd (js-date-days-to-ymd days)))
(let
((y (nth ymd 0)) (mo (nth ymd 1)) (d (nth ymd 2)))
(str
(js-date-iso-year y)
"-"
(js-pad2 mo)
"-"
(js-pad2 d)
"T"
(js-pad2 hh)
":"
(js-pad2 mm)
":"
(js-pad2 ss)
"."
(js-pad3 msec)
"Z")))))))
(define
js-date-iso-year
(fn
(y)
(cond
((or (< y 0) (> y 9999))
(let
((sign (if (< y 0) "-" "+"))
(ay (if (< y 0) (- 0 y) y)))
(str sign (js-date-year-6 ay))))
((< y 10) (str "000" (js-to-string y)))
((< y 100) (str "00" (js-to-string y)))
((< y 1000) (str "0" (js-to-string y)))
(else (js-to-string y)))))
(define
js-date-year-6
(fn
(y)
(cond
((< y 10) (str "00000" (js-to-string y)))
((< y 100) (str "0000" (js-to-string y)))
((< y 1000) (str "000" (js-to-string y)))
((< y 10000) (str "00" (js-to-string y)))
((< y 100000) (str "0" (js-to-string y)))
(else (js-to-string y)))))
(define js-pad2 (fn (n) (if (< n 10) (str "0" (js-to-string n)) (js-to-string n))))
(define
js-pad3
(fn
(n)
(cond
((< n 10) (str "00" (js-to-string n)))
((< n 100) (str "0" (js-to-string n)))
(else (js-to-string n)))))
(define
js-date-days-to-ymd
(fn
(days-since-epoch)
(let
((d (+ days-since-epoch 719468)))
(let
((era (if (>= d 0) (js-math-trunc (/ d 146097)) (js-math-trunc (/ (- d 146096) 146097)))))
(let
((doe (- d (* era 146097))))
(let
((yoe
(js-math-trunc
(/
(-
(+
(- doe (js-math-trunc (/ doe 1460)))
(js-math-trunc (/ doe 36524)))
(js-math-trunc (/ doe 146096)))
365))))
(let
((y (+ yoe (* era 400)))
(doy
(-
doe
(+
(* yoe 365)
(-
(js-math-trunc (/ yoe 4))
(js-math-trunc (/ yoe 100)))))))
(let
((mp (js-math-trunc (/ (+ (* 5 doy) 2) 153))))
(let
((day (- (+ doy 1) (js-math-trunc (/ (+ (* 153 mp) 2) 5))))
(month (if (< mp 10) (+ mp 3) (- mp 9))))
(list
(if (<= month 2) (+ y 1) y)
month
day))))))))))
(define
js-date-year-pad

View File

@@ -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.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.
- 2026-05-10 — **`JSON.stringify` honours `replacer` (function + array forms), `space`, and `toJSON`.** Previous impl ignored the second/third arguments entirely and never called `toJSON`. Rewrote around a `js-json-serialize-property(key, holder, rep-fn, rep-keys, gap, indent)` core: walks `toJSON` first, then replacer-fn (with `holder` as `this`); arrays-as-replacer become a property-name allowlist; numeric `space` clamped to 0..10 spaces, string `space` truncated to 10 chars, non-empty gap activates indented output with `:``: ` separator. Number wrapper / String wrapper / Boolean wrapper unwrap before serialization; non-finite numbers serialize as `"null"`; functions serialize as `undefined`. Result: built-ins/JSON/stringify 6/30 → 14/30 (+8). conformance.sh: 148/148.
- 2026-05-10 — **`JSON.parse` raises spec-correct `SyntaxError` instances and rejects malformed input.** Previously `JSON.parse("12 34")` silently returned `12` (no trailing-content check), `JSON.parse('""')` accepted control chars in strings, an unterminated string read off the end, and the inner `(error "JSON: ...")` calls produced generic Errors not `instanceof SyntaxError`. Added: (1) post-value whitespace skip + trailing-content check in `js-json-parse`; (2) control-char rejection (code < 0x20) and unterminated-string check in `js-json-parse-string-loop`; (3) all internal "JSON: ..." errors now `(raise (js-new-call SyntaxError ...))`. Result: built-ins/JSON/parse 7/30 → 25/30 (+18). JSON 26/30. conformance.sh: 148/148.