Skip to content

Date: local setters near the time value limits use the zone's own offset (WebKit bump for oven-sh/WebKit#615) - #42189

Open
robobun wants to merge 4 commits into
mainfrom
robobun/ec792599/date-local-utc-limits
Open

robobun wants to merge 4 commits into
mainfrom
robobun/ec792599/date-local-utc-limits

Conversation

@robobun

@robobun robobun commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #42177 (WebKit upgrade to ccdcb8a026). Until that merges, the diff also shows its commits. Only the last commit belongs to this PR: scripts/build/deps/webkit.ts and test/js/bun/jsc/date-local-time-utc-conversion.test.ts.

Problem

  • Every local Date setter, the multi-argument Date constructor and Date.parse of an offset-less string give a shifted time value or NaN for a valid Date within one UTC offset of either end of the time value range (±8.64e15 ms). TZ=America/New_York: d = new Date(-8.64e15 + 36e5); d.setHours(d.getHours()) moves d by 56 min 2 s and new Date(-8.64e15).setMilliseconds(0) is NaN. TZ=Pacific/Apia: every local setter on the last 13 hours of the range is NaN. Node 26 returns the identity, as the note on UTC(t) requires. TZ=UTC is not affected, and the getters were already right.
  • The cause is in JSC: DateCache::DSTCache::localTimeOffset() (vendor/WebKit/Source/JavaScriptCore/runtime/JSDateMath.cpp) remapped a local time value outside the range to the same month and day in a year between 2008 and 2035 before it asked ICU for the offset, because its offset cache cannot hold a time outside the range. So New York got EDT -4:00 instead of its local mean time -4:56:02, and Apia got its 2008 offset -11:00 instead of +13:00.

Fix

Background

  • A time value is UTC milliseconds in ±8.64e15 (20 April -271821 to 13 September 275760). A local setter decomposes t + offset(t) into fields, edits one, recomposes a local time value l, and stores TimeClip(l - offset(l)). For t near a limit, l is past it by up to the offset.
  • JSC gets offsets from an ICU UCalendar behind a small interval cache ported from V8. For an instant before a zone's first rule ICU returns the zone's local mean time, which is why dates that old have offsets like -4:56:02. V8 asks ICU the same way, without the remap.
  • The test switches zones with process.env.TZ, which bun applies to the running process, and restores the original value at the end.
Notes

no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/bundler/bundler_bytecode_portable.test.ts

Point WEBKIT_VERSION at the preview build of oven-sh/WebKit#614, which
merges upstream WebKit main at ccdcb8a026 into the fork.

Upstream 64168e4d90 adds GlobalObjectMethodTable::moduleTypeIsAllowed
ahead of moduleLoaderImportModule, so the five method tables get a
nullptr slot for it. The new test pins four engine fixes from the range
and the host-defined import attribute path.
The serialized bytecode of the three corpus libraries that contain async
functions and generators (happy-dom, undici, libraries.js) changed with
upstream 4fa7b55ae4, which stops emitting tail calls in generator and
async function bodies. CI produced the same three hashes on all eleven
platforms, which is the update-the-snapshot case the test describes.
@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 10 days. After that, they cost $0.25 per reviewed file.

Or wait 17 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 13ec3084-aefe-41e3-97ce-e5ac57246a76

📥 Commits

Reviewing files that changed from the base of the PR and between c2bf9b4 and 93787d3.

📒 Files selected for processing (7)
  • scripts/build/deps/webkit.ts
  • src/jsc/bindings/NodeVM.cpp
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/runtime/bake/BakeGlobalObject.cpp
  • test/bundler/bundler_bytecode_portable.test.ts
  • test/js/bun/jsc/date-local-time-utc-conversion.test.ts
  • test/js/bun/jsc/webkit-upgrade-ccdcb8a026.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced with bun 1.4.3 against Node 26:

// TZ=America/New_York bun repro.js ; TZ=Pacific/Apia bun repro.js ; same with node
function rt(t){var d=new Date(t);return new Date(d.getFullYear(),d.getMonth(),d.getDate(),d.getHours(),d.getMinutes(),d.getSeconds(),d.getMilliseconds()).getTime()}
[-8.64e15, -8.64e15+36e5, 8.64e15, 8.64e15-18e6].forEach(function(t){
  var d=new Date(t);
  console.log(t, new Date(t).setMilliseconds(d.getMilliseconds()), new Date(t).setHours(d.getHours()), rt(t)); // all four columns must be equal
});

bun 1.4.3, TZ=America/New_York: the first row is NaN NaN NaN, the second row is off by 3362000 ms (56 min 2 s). TZ=Pacific/Apia: the last two rows are NaN. Node prints four equal columns in every row, and so does bun bd with this pin.

The engine fix is oven-sh/WebKit#615, now rebased on oven-sh/WebKit main after #614 merged there. This PR pins its preview build autobuild-preview-pr-615-8030fb83 and adds test/js/bun/jsc/date-local-time-utc-conversion.test.ts (bun 1.4.3 fails 10 of 43, the pinned build passes all).

The branch is stacked on #42177, because bun needs that PR's embedder changes to build against a WebKit past #614. Only the last commit is this PR's. Merge order: #42177, then oven-sh/WebKit#615, then this PR with the pin moved to the merge commit.

CI on 93787d3 (build 114066): 180 of 181 jobs pass, and the new test passes on every platform. The one red job is test/js/bun/http/serve-pending-promise-abort-leak.test.ts on debian 13 x64-asan. This PR does not touch that code, and the same test is red on the final build of the unrelated merged PR #42227, so it is a failure on main. The next push here is the rebase and re-pin after #42177 and oven-sh/WebKit#615 land.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. Since it bumps the WebKit prebuilt — and to a preview tag that, as the description notes, must be swapped for the merged oven-sh/WebKit#615 SHA before landing — a human should gate the merge.

What was reviewed:

  • The round-trip test: each check() writes a unique key into results/expected and the aggregate .toEqual covers every setter, the multi-arg constructor, and the ±1 ms NaN boundary — no vacuous or overwritten entries.
  • process.env.TZ handling: setTimeZone asserts the epoch offset so a zone that didn't take effect fails loudly, and afterAll restores/deletes the original value.
  • The east-of-Greenwich block: offset / msPerHour is an integer for Tokyo/Apia/Kiritimati at maxTimeValue, so the setHours identity holds; the -5 * msPerHour starting point keeps the pre-set value in range for all three.
Extended reasoning...

Overview

This PR changes two files. scripts/build/deps/webkit.ts moves WEBKIT_VERSION from the pinned commit dfd696443b9b… to autobuild-preview-pr-615-c1a60940, the autobuild artifact of oven-sh/WebKit#615. test/js/bun/jsc/date-local-time-utc-conversion.test.ts is new: it exercises the JSC-side fix that PR carries by round-tripping the ten time values nearest ±8.64e15 ms through the multi-argument Date constructor and every local setter across 13 IANA zones (via in-process process.env.TZ), then pins zone-specific edge cases for New York's local-mean-time offset at the minimum bound and Tokyo/Apia/Kiritimati at the maximum, plus Date.parse with an explicit UTC offset.

Security risks

None identified. The test is pure in-process Date arithmetic with no network, no filesystem writes beyond the harness, no subprocesses, and no user-controlled input parsing. The WebKit pin change is a build-time artifact selector; the security-relevant question is provenance of the prebuilt, which is why the preview tag must become a merged SHA before landing.

Level of scrutiny

High, and human-gated. WebKit is the JavaScript engine — any bump changes execution semantics for every JS program Bun runs. The repo's own vendoring guidance (.claude/docs/landing-prs.md) is explicit: never merge a pin to an ephemeral artifact (preview tags, unmerged-PR builds); swap to the merged upstream SHA and verify prebuilt artifacts exist for every platform × flavor first. The PR author already flags this, so the defer is about ensuring a human confirms the swap and the cross-platform prebuilt availability rather than raising a new objection.

Other factors

The test file is well-placed alongside the existing webkit-upgrade-*.test.ts files in test/js/bun/jsc/. It follows the aggregated-.toEqual convention from test/CLAUDE.md, uses describe.each/test.each, has no sleeps/timeouts/hardcoded ports, and restores the TZ global in afterAll. I checked that each check() label is unique per timeValue (no silent overwrites in the results map), that the fields.slice(...) spreads land on the right setter overloads, and that offset / msPerHour is integral for the three chosen east-of-Greenwich zones so setHours receives whole hours. The PR description states 10 of 43 tests fail on bun 1.4.3 and pass on the bumped build, satisfying the "fails for the right reason" bar. No CODEOWNERS entry covers these paths.

@robobun

robobun commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Agreed on the merge gate. Once oven-sh/WebKit#615 lands I move WEBKIT_VERSION to its merge commit, confirm the autobuild-<sha> release has the prebuilt for every platform and flavor CI links, rerun the new test against that pin, and update the PR body. Until then this stays unmergeable by design.

Its tree is identical to the preview build CI ran against
(autobuild-preview-pr-614-3db1d92e).
…ar the time value limits use the zone's own offset (WebKit bump for oven-sh/WebKit#615)

Pin WebKit to the preview build of oven-sh/WebKit#615 and add a test.
JSC remapped a local time value outside the time value range to a year
between 2008 and 2035 before it asked ICU for the UTC offset, so a local
setter or the multi-argument constructor on a valid Date within one UTC
offset of either end of the range was off by the difference between the
two offsets, or NaN.
@robobun
robobun force-pushed the robobun/ec792599/date-local-utc-limits branch from 84c51a2 to 93787d3 Compare September 11, 2026 01:07
@robobun

robobun commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

The diff grew to 7 files because this branch is now stacked on #42177. oven-sh/WebKit#615 had to be rebased past oven-sh/WebKit#614, and bun needs the embedder changes of #42177 to build against that WebKit. Five of the seven files are #42177's and are reviewed there. This PR's own change is still the last commit only: the pin in scripts/build/deps/webkit.ts and test/js/bun/jsc/date-local-time-utc-conversion.test.ts. After #42177 merges I rebase on main and the diff goes back to those two files.

* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "dfd696443b9ba87c4f516d1c724f0abacdd8768a";
export const WEBKIT_VERSION = "autobuild-preview-pr-615-8030fb83";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 WEBKIT_VERSION is pinned to autobuild-preview-pr-615-8030fb83, an ephemeral preview-PR release tag; once oven-sh/WebKit#615 merges or closes that tag is garbage-collected and every fresh build (CI cold cache, new contributor, bun run clean cache) 404s downloading the WebKit prebuilt. Fix: before merge, swap the pin to the merged upstream SHA (40-hex commit on oven-sh/WebKit main) whose autobuild-<sha> release carries every platform × flavor artifact — never land a preview-pr-* tag on main. The PR description acknowledges this ("Do not merge before oven-sh/WebKit#615 lands"), but the pin as committed is the ephemeral shape.

Extended reasoning...

prebuiltUrl() in scripts/build/deps/webkit.ts:68-75 builds https://github.com/oven-sh/WebKit/releases/download/autobuild-preview-pr-615-8030fb83/bun-webkit-<os>-<arch><suffix>.tar.gz. Preview-PR autobuild releases on oven-sh/WebKit are transient — they exist only while the PR is open and are deleted after merge/close. On the base branch the pin is the merged SHA dfd696443b9ba87c4f516d1c724f0abacdd8768a, whose autobuild-<sha> release is permanent. After this PR lands and #615 is merged upstream, bun run build on any machine without a warm $BUN_INSTALL/build-cache/webkit-preview-pr-615-8030fb83* extraction hits a 404 in download.ts and the build aborts. test/internal/source-lints/webkit-prebuilt-url.test.ts:122-124 explicitly accepts autobuild-* tags, so no automated check blocks this. The repo's dependency-bump rule (landing-prs.md → Dependencies & vendoring) forbids merging pins to unmerged-PR builds for exactly this reason.

Verification: normal — acknowledged in diff: the PR description states "The pin is a preview tag, not a merge commit. Do not merge before oven-sh/WebKit#615 lands. I move the pin to the merged commit then." That flags the hazard but does not resolve it; the code as it stands is what merges. scripts/build/deps/webkit.ts:6 changes WEBKIT_VERSION from the 40-hex merged SHA… | normal — acknowledged in PR…

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and intended: this thread stays open as the merge blocker. The preview pin is what lets CI here exercise the engine change while oven-sh/WebKit#615 is in review. After #42177 and oven-sh/WebKit#615 land I rebase on main, push the 40-hex merge commit here, check that its autobuild-<sha> release carries every os, arch and debug/lto/asan tarball, and resolve this thread with that push.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants