Summary
Date.UTC is implemented, but its argument normalization does not match Node for important edge cases:
- missing
day defaults to 1, not 0;
- no arguments should return
NaN because the required year is undefined;
- years
0..99 are interpreted as 1900 + year;
- component overflow/underflow should be normalized by the Date algorithm.
Perry currently pads every missing argument with 0 and casts components directly before calling components_to_timestamp.
Expected Node behavior
Local Node v25.9.0 probe:
Date.UTC(); // NaN
Date.UTC(2020); // 1577836800000 (2020-01-01T00:00:00.000Z)
Date.UTC(2020, 0); // 1577836800000
Date.UTC(2020, 0, 1); // 1577836800000
Date.UTC(2020, 0, 0); // 1577750400000 (2019-12-31T00:00:00.000Z)
Date.UTC(0, 0, 1); // -2208988800000 (1900-01-01T00:00:00.000Z)
Date.UTC(99, 0, 1); // 915148800000 (1999-01-01T00:00:00.000Z)
Date.UTC(100, 0, 1); // -59011459200000 (0100-01-01T00:00:00.000Z)
Date.UTC(2020, 12, 1); // 1609459200000 (2021-01-01T00:00:00.000Z)
Current Perry implementation
Source evidence from current tree:
crates/perry-hir/src/lower/expr_call/module_static.rs lowers any Date.UTC(...) call to Expr::DateUtc(args).
crates/perry-codegen/src/expr/misc_methods.rs lowers up to seven args and pads every missing slot with double_literal(0.0). That makes Date.UTC() and Date.UTC(2020) indistinguishable from explicit zeroes for missing components, but Node treats missing year as NaN and missing day as 1.
crates/perry-runtime/src/date.rs::js_date_utc() casts year as i32, converts month + 1 to u32, casts day/hour/minute/second to unsigned integers, and passes them to components_to_timestamp.
js_date_utc() does not apply the 0..99 => 1900 + year rule that the adjacent js_date_new_local_components() implementation documents for multi-argument new Date(...).
- The unsigned casts make negative/overflow component normalization unlikely to match Node's date construction semantics for cases such as day
0, negative days/months, and month overflow.
Suggested test surface
Add parity tests for the edge cases above:
assert.equal(Number.isNaN(Date.UTC()), true);
assert.equal(Date.UTC(2020), Date.UTC(2020, 0, 1));
assert.equal(Date.UTC(2020, 0), Date.UTC(2020, 0, 1));
assert.equal(Date.UTC(2020, 0, 0), Date.parse("2019-12-31T00:00:00.000Z"));
assert.equal(Date.UTC(0, 0, 1), Date.parse("1900-01-01T00:00:00.000Z"));
assert.equal(Date.UTC(99, 0, 1), Date.parse("1999-01-01T00:00:00.000Z"));
assert.equal(Date.UTC(100, 0, 1), Date.parse("0100-01-01T00:00:00.000Z"));
assert.equal(Date.UTC(2020, 12, 1), Date.parse("2021-01-01T00:00:00.000Z"));
Scope / non-goals
This is not the older Date corruption work around #748/#858/#859 or the Date reference/mutation fix #2089. It is specifically about the static Date.UTC argument defaulting and normalization contract.
Summary
Date.UTCis implemented, but its argument normalization does not match Node for important edge cases:daydefaults to1, not0;NaNbecause the requiredyearisundefined;0..99are interpreted as1900 + year;Perry currently pads every missing argument with
0and casts components directly before callingcomponents_to_timestamp.Expected Node behavior
Local Node v25.9.0 probe:
Current Perry implementation
Source evidence from current tree:
crates/perry-hir/src/lower/expr_call/module_static.rslowers anyDate.UTC(...)call toExpr::DateUtc(args).crates/perry-codegen/src/expr/misc_methods.rslowers up to seven args and pads every missing slot withdouble_literal(0.0). That makesDate.UTC()andDate.UTC(2020)indistinguishable from explicit zeroes for missing components, but Node treats missingyearasNaNand missingdayas1.crates/perry-runtime/src/date.rs::js_date_utc()castsyear as i32, convertsmonth + 1tou32, castsday/hour/minute/secondto unsigned integers, and passes them tocomponents_to_timestamp.js_date_utc()does not apply the0..99 => 1900 + yearrule that the adjacentjs_date_new_local_components()implementation documents for multi-argumentnew Date(...).0, negative days/months, and month overflow.Suggested test surface
Add parity tests for the edge cases above:
Scope / non-goals
This is not the older Date corruption work around #748/#858/#859 or the Date reference/mutation fix #2089. It is specifically about the static
Date.UTCargument defaulting and normalization contract.