QuickJS-ng: let the embedder supply Date's clock (`JS_SetDateNowFunc`)
Generated from repros/quickjs-date-now-hook/README.md when the site was built at 7aa23e2.
A feature patch, not a bug: QuickJS-ng reads the system clock for Date.now()
and new Date() and gives the embedder no way to supply another. niso keeps
one clock per page (niso_dom_wall_clock: performance.timeOrigin + performance.now()), which skips ahead with virtual time when the page is
idle, so performance.now() and timers move with it but Date.now() does
not. Pages that poll Date.now() across timers see time stand still: WPT
FileAPI/file/File-lastModified.html waits on step_wait for
Date.now() to move and fails about two runs in three.
0001-Add-JS_SetDateNowFunc.patch adds, in the shape of
JS_SetInterruptHandler:
/* return the current time in milliseconds since 1970-01-01 UTC, for Date.now() and new Date() */typedef int64_t JSDateNowFunc(JSContext *ctx, void *opaque);/* use func for the current time instead of the system clock; NULL restores the system clock */JS_EXTERN void JS_SetDateNowFunc(JSRuntime *rt, JSDateNowFunc *func, void *opaque);date_now() takes the context and calls the runtime’s function if one is
set; its two callers (Date.now and the Date constructor, which also covers
Date() called as a function) pass the context. Nothing changes for an
embedder that does not call it. The patch adds an api-test.c case
(date_now_func): a fixed clock is read by all three, moving it moves them,
and NULL brings back the system clock.
Status
Section titled “Status”- Made against niso’s pinned submodule, quickjs-ng
d7215b0(v0.18.0). Upstream master (checked 2026-11-07) has no such hook. - No existing issue or PR. The nearest is quickjs-ng/quickjs#260 (determinism,
closed), where the answer was to monkey-patch
Datefrom JS before running code. That works for a sandbox but not for a browser engine: a replacedDateis observable (Function.prototype.toString, identity ofDate.prototype.constructor, subclassing, realms), which is why niso does not do it. - Not applied in niso’s build and not filed. The submodule stays on upstream.
repros/quickjs-date-now-hook/test.shcopies the pinned submodule into a scratch directory, applies the patch,
builds api-test with CMake and runs it (exit 0 when it passes). Run on
Linux x86_64 with clang/gcc and CMake: it applies cleanly and api-test
passes; api-test.c and quickjs.c also pass make jscheck’s flags
(-Wall -Werror -DJS_CHECK_JSVALUE).
What niso does with it
Section titled “What niso does with it”crates/niso-quickjs has a date-now-hook feature. With it, the binding
(bindings/js/quickjs/qe_quickjs.c, under QE_DATE_NOW_HOOK) calls
JS_SetDateNowFunc(rt, date_now, js), where date_now returns
niso_js_wall_clock(js), the DOM’s niso_dom_wall_clock. Without the patch
the feature does not compile (JS_SetDateNowFunc is undeclared), so turning
it on means applying this patch (or taking a QuickJS-ng release that has the
hook) and adding date-now-hook to the crate’s default features.
With the patch applied to a scratch copy of the submodule and the feature on,
File-lastModified.html passes 40 runs of 40, 20 at a time (28 of 40
without), hr-time/basic.any.js passes its fifth subtest (“approximately the
right relative magnitude”), and the niso-quickjs test
date_skips_ahead_with_virtual_time passes along with the rest of the
crate’s tests.
Issue text (PR description)
Section titled “Issue text (PR description)”Add JS_SetDateNowFunc to let the embedder supply Date’s clock
Embedders with their own notion of time (a test runner or headless browser that skips idle time ahead, a deterministic replay) need
Date.now()andnew Date()to agree with their timers andperformance.now(). Today the only way is to replaceDatefrom JS, which is observable to scripts.This adds
JS_SetDateNowFunc(rt, func, opaque), modelled onJS_SetInterruptHandler: when set,date_now()returnsfunc(ctx, opaque)(milliseconds since the epoch) instead ofjs__gettimeofday_us() / 1000.NULLrestores the system clock, so nothing changes for embedders that do not call it.api-test.ccoversDate.now(),new Date()andDate()with a fixed clock, moving it, and resetting it.
Written with Claude Code (Claude Opus 5.5) while making niso’s clocks agree. Review and rewrite before filing.