Skip to content
All fingerprint checksNavigator identity

a Date's own text agrees with the Date's own offset

Check id date-tostring-coherence

What an ordinary browser yields

At every instant, the GMT±HHMM the string renders equals minus getTimezoneOffset() read off the same Date, and the string is exactly toDateString() + " " + toTimeString().

What a detector infers

A Date's text is not opaque. ECMA-262 defines toString() as toDateString() + " " + toTimeString(), fixes the weekday and month tables to English so they are never localized, and bakes the UTC offset into the result as GMT±HHMM. So a single Date object answers the timezone question through paths that do not consult each other: the numeric one (getTimezoneOffset), and the one nobody remembers to hook — the offset the engine renders into the string. PerimeterX collects the raw toString() output in the same pass as getTimezoneOffset() and Intl.resolvedOptions().timeZone, then differences them server-side for free. The check reads four instants spread across the current year plus now, and three details in it are load-bearing rather than incidental. It parses hours and minutes SEPARATELY and signs the whole quantity, because a shortcut that reads only the hours flags every user in India, Nepal, Chatham and Newfoundland. It pins the probes to the current year, because an epoch-0 date reintroduces sub-minute Local Mean Time — Asia/Kolkata before 1906 was +05:53:20, which renders as GMT+0553 against a whole-minute API and manufactures a false contradiction out of a correct browser. And it never predicts DST: both readings come off ONE Date at ONE instant, so a zone whose rules we would have got wrong is still self-consistent and still passes. What the check permanently refuses to touch is the parenthesised zone NAME and the IANA id. The name renders in the browser's own ICU locale, Antarctica/Troll's name is literally the string (GMT+02:00), and Asia/Kathmandu resolves to the alias Asia/Katmandu — asserting on any of that would be both a false-positive minefield and a hidden reference table.

How to resolve it

A timezone is one host setting rendered by several readers that do not check with each other. Patching Date.prototype.toString in JavaScript leaves getTimezoneOffset and Intl telling the truth. Set the zone at the layer they all derive from — the OS, the container, or the browser's own timezone override, which reconfigures ICU underneath every reader at once and is invisible to this check by design. If you run a timezone-changing extension, this is what it looks like from outside: your setup contradicts itself, which is not the same as you being a bot.

What is a timezone / IP mismatch?

Read in the wild by

PerimeterX / HUMAN

Captures the engine's full Date() string — weekday, month, GMT offset and the localized zone name in one field — in the same payload as the numeric getTimezoneOffset and the Intl IANA zone.

PerimeterX collector main.min.js (iFood deployment, /root/re/PerimeterX_RE/stample/ifood/source/main.min.js line 7898) contains `t["Czt+cU1WeEM="] = hS[hQ(802)]()` where hQ_map.json line 804 gives "802": "Date" — Date called without `new`, which returns the full toString-format string. Live EV2 payloads (stample/ifood/sample/*/decoded_payload_2.json) decode "Czt+cU1WeEM=" to "Wed May 20 2026 15:52:00 GMT+0800 (中国标准时间)", travelling in the same payload as "OARNTn5iRnw=" = -480, assigned by `t[0]["OARNTn5iRnw="]=CD()` where `function CD(){try{return(new Date).getTimezoneOffset()}catch(t){return 9999}}`, and "UT0kdxRdI0Y=" = "Asia/Shanghai", assigned by `try{var n=hS[hQ(357)][hQ(358)]();t[hQ(359)]=n.resolvedOptions()[hQ(360)]}` with hQ 357="Intl", 358="DateTimeFormat", 359="UT0kdxRdI0Y=", 360="timeZone". main/EN/EV1_EV2_UNIFIED_REFERENCE.md §B "Browser Runtime" line 110 labels the field "Date.toString()". Corroborated on a second deployment: the same semantic under Grubhub key "UiJkKBRPYRo=" decodes to "Wed May 20 2026 16:02:20 GMT+0800 (中国标准时间)" in stample/grub/sample/1/decoded_payload_2.json.

CreepJS

CreepJS, an open-source fingerprinting research demo rather than a production detector, lists Date's text surface — toString, toDateString, toTimeString — alongside getTimezoneOffset in a single lie-test target array, checking each for signs of tampering (native-code toString signature, proxy traps, stray descriptors) as one entry in a uniform sweep across dozens of prototypes. It tests those methods for having been hooked; it does not difference the rendered GMT offset against getTimezoneOffset the way this check does.

creepjs, src/lies/index.ts, inside getPrototypeLies() (between the CSS2Properties and GPU entries): `searchLies(() => Date, { target: ['getDate', 'getDay', 'getFullYear', 'getHours', 'getMinutes', 'getMonth', 'getTime', 'getTimezoneOffset', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMonth', 'setSeconds', 'setTime', 'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString', 'toLocaleTimeString', 'toString', 'toTimeString', 'valueOf'] })` — where searchLies delegates to queryLies, whose tampering tests include `['failed toString']: (!hasKnownToString(name)[scope.Function.prototype.toString.call(apiFunction)]`.

Every attribution traces to a published artifact. See the sources and their limits.

Nearby checks in Navigator identity

See all 26 checks in Navigator identity
Who builds this test

Clearcote is a browser built for fingerprint coherence

It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.

This audit takes no position on how Clearcote scores on Navigator identity checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.

See the other checks in Navigator identity — the family date-tostring-coherence belongs to.