Skip to content

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.

  • 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 Date from JS before running code. That works for a sandbox but not for a browser engine: a replaced Date is observable (Function.prototype.toString, identity of Date.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.
Terminal window
repros/quickjs-date-now-hook/test.sh

copies 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).

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.

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() and new Date() to agree with their timers and performance.now(). Today the only way is to replace Date from JS, which is observable to scripts.

This adds JS_SetDateNowFunc(rt, func, opaque), modelled on JS_SetInterruptHandler: when set, date_now() returns func(ctx, opaque) (milliseconds since the epoch) instead of js__gettimeofday_us() / 1000. NULL restores the system clock, so nothing changes for embedders that do not call it. api-test.c covers Date.now(), new Date() and Date() 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.