Date's local-time surface always behaves as if the host were UTC, and the JSGlobalObject__setTimeZone override that the private ABI exposes (#303) does not change that — it only reaches the Intl pipeline.
Measured
Host zone Asia/Manila (UTC+8, no DST), same program under zig-js (Debug test262 --eval) and JavaScriptCore (home-tool):
| zig-js | JavaScriptCore | |
|---|---|---|
new Date(0).getTimezoneOffset() | 0 | -480 |
new Date(Date.UTC(2026,6,1)).getTimezoneOffset() | 0 | -480 |
new Date(0).getHours() | 0 | 8 |
new Date(2026, 8, 22, 10, 20, 30).getTime() | 1790072430000 | 1790043630000 |
d = new Date(0); d.setHours(5); d.getTime() | 18000000 | -10800000 |
new Date(0).toString() tail | GMT+0000 (Coordinated Universal Time) | GMT+0800 (Philippine Standard Time) |
new Date(0).toLocaleTimeString("en-US") | 12:00:00 AM | 8:00:00 AM |
new Date("2026-09-22T10:20:30").getTime() | 1790072430000 | 1790043630000 |
Intl.DateTimeFormat().resolvedOptions().timeZone | UTC | Asia/Manila |
The last row matters for the others: an ISO date-time without an offset is local time (21.4.3.2), so parsing it as UTC is the same bug seen from the parser side.
Everything that takes an explicit zone is already correct — new Intl.DateTimeFormat("en-US", {timeZone: "Asia/Manila", ...}).format(0) gives 08:00, and {timeZone: "UTC"} gives 00:00. So the zone database and the offset lookup work; only Date is disconnected from them.
Root cause
Two places:
src/interpreter.zig:14153—if (eq(name, "getTimezoneOffset")) return Value.num(0);src/interpreter.zig:47499—fn nowTimeZone()returns.{ .name = timeZoneOverride() orelse "UTC", .offset_ns = 0 }. The name is carried, the offset is not, so even an embedder that callsJSGlobalObject__setTimeZone("Asia/Manila")still gets UTC out of every localDatemethod.
The primitive needed already exists: iana_offsets.offsetAt(name, epoch_seconds) (used at interpreter.zig:47943) and iana_offsets.transitionAt for DST boundaries.
Why the corpus is green anyway
test262's local-time tests are self-consistent: they compare Date against LocalTZA-derived expectations, and an engine that answers "offset 0" everywhere satisfies them. 48,506/48,506 cannot see this. It shows up the moment a host or an embedder has a real zone — which is every Home/Bun use.
The decision I'd want from you before implementing
What is the default when no setTimeZone override is set?
- Adopt the host zone (
TZenv var, else/etc/localtime), like JavaScriptCore, V8 and node. Matching behaviour, but engine output becomes host-dependent, and the corpus would then run under whatever zone CI has — the DST paths get exercised for the first time. - Keep UTC as the default and only honour an explicit
setTimeZone. Deterministic, keeps CI reproducible, still fixes the ABI defect and lets Home/Bun opt in.
Either way the work is the same underneath: route LocalTime/UTC (21.4.1.25-26), the local getters and setters, the Date(y, m, d, …) constructor, toString/toLocaleString and the offset-less ISO parse through the zone's offset at that instant, and make resolvedOptions().timeZone report it. I'm happy to take it once the default is settled.