Skip to content

Date.now() and process.hrtime() return wall clock at top-level (global scope) in wrangler dev / Miniflare, but 0 in deployed workerd #6839

Description

@JeremyFunk

What versions & operating system are you using?

System:
OS: macOS 26.4.1
CPU: (10) arm64 Apple M4
Memory: 178.25 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.15.0 - /Users/jeremyfunk/.local/state/fnm_multishells/6412_1781534594231/bin/node
npm: 10.9.2 - /Users/jeremyfunk/.local/state/fnm_multishells/6412_1781534594231/bin/npm
bun: 1.3.8 - /opt/homebrew/bin/bun
npmPackages:
@cloudflare/workers-types: ^4.20260603.1 => 4.20260615.1
wrangler: ^4.41.0 => 4.100.0

Please provide a link to a minimal reproduction

https://github.com/JeremyFunk/workerd-datenow-mismatch

Describe the Bug

The behaviour of Date.now() in the global scope varies between miniflare dev and a deployed worker.

Miniflare dev behaviour

In Miniflare dev, the Date.now() function returns epoch time both in the global scope (so when getting the value on startup of the worker) and in request time. Interstingly, performance.now() is 0 on startup, and becomes epoch in requests.

Here is a condensed of the code I used to verify this (see attached repo).

const globalDateNow = Date.now()
const globalPerformanceNow = performance.now()

const handler = async (): Promise<Response> => {
    const requestDateNow = Date.now()
    const requestPerformanceNow = performance.now()

   const log = `
    globalDateNow: ${globalDateNow}
    globalPerformanceNow: ${globalPerformanceNow}

    requestDateNow: ${requestDateNow}
    requestPerformanceNow: ${requestPerformanceNow}
    `

    return new Response(log, { headers: { "content-type": "text/plain; charset=utf-8" }})
}

This code results in the following output:

globalDateNow: 1781534905975
globalPerformanceNow: 0

requestDateNow: 1781535318562
requestPerformanceNow: 1781535318563

Note that performance.now() and Date.now() disagree here on startup.

Deployed worker behaviour

Executing the same code in production yields this output:

globalDateNow: 0
globalPerformanceNow: 0

requestDateNow: 1781535318562
requestPerformanceNow: 1781535318563

Note that performance.now() and Date.now() do not disagree here, they are identical in both cases.

What the docs state

In the Cloudflare workers performance API docs it states that, in production deployed workers, performacne.now() and Date.now() only advance or increment after I/O occurs. While in local development, timers increment regardless of whether I/O happens.

I guess that explains part of the issue, why globalDateNow and requestDateNow disagree between prod and dev. But the docs do imply that performance.now() and Date.now() work identical in this regard, which they don't.

Impact

This disagree causes the span start time of effect's tracer to be incorrect. Effect precomputes the time origin on startup. The exact beahviour of time origin can vary between runtimes, some runtimes do require an offset.

The bug is only triggered on effect's side when nodejs-compat is enabled with wrangler, which causes the code to go down a different branch on startup. In this branch they calculate the offset of time based on the time values present on startup, which are then corrected on request time. But since this value is precomputed, the incorrect time offset is reused.

The underlying bug is probably still with miniflare, as that has an undocumented mismatch in behaviour between production and dev enviornments. If gloablDateNow and globalPerformanceNow had identical behaviour, regardless of whether they were showing epoch or 0, the bug would not be triggered in Effect.

I created a separate issue for effect here.


Left out here for bravity, but relevant to the effect specific issue, since they use that function (included in linked repo):
The value of process.hrtime.bigint() tracks that of Date.now(), so it is 0 on startup in production, and epoch in miniflare dev. This is actually what causes the bug in effect, and is why nodejs-compat is required to trigger it (since process.hrtime.bigint() is not present in the globalThis.process.hrtime path in the runtime otherwise). But the Date.now() disagree happens regardless of whether nodejs_compat is enabled.

Please provide any relevant error logs

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    Status
    Other

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions