Signal

Timezone

Reads the device's OS-configured IANA timezone name and UTC offset in minutes — values derived from the OS timezone database and therefore stable across browser engines on the same machine.

Tier 1 hardware src/signals/timezone.ts

What it measures

The signal collects two values: the IANA timezone name (e.g., 'America/New_York') from Intl.DateTimeFormat().resolvedOptions().timeZone, and the current UTC offset in minutes (e.g., -300 for UTC-5) from new Date().getTimezoneOffset(). Both reflect the OS timezone configuration and are the same regardless of which browser is used.

The locale string (e.g., 'en-US') from resolvedOptions() is a browser preference, not an OS property. It is stored in value.engine for diagnostics but is never included in the hash.

How it's collected

Inside a try/catch block, the collector calls Intl.DateTimeFormat().resolvedOptions() to extract the timeZone and locale strings (defaulting both to empty string on failure). The raw timeZone string is then passed through canonicaliseTimezone(), which maps known IANA legacy alias names to their canonical equivalents before hashing. Outside the try/catch, new Date().getTimezoneOffset() is called to get the UTC offset as an integer.

The canonical timezone name and the string representation of the offset are joined with a pipe delimiter and hashed with xxHash64 to produce a 16-character hex string. The function always returns normal confidence when execution reaches the return statement; absent is only returned if an unhandled exception propagates out of the outer try/catch.

Confidence rules

ConfidenceTrigger
normalThe outer try/catch does not throw (always when JS is available)
absentThe outer try block throws an unhandled exception

Why hardware-bound

Both Intl.DateTimeFormat().resolvedOptions().timeZone and Date.prototype.getTimezoneOffset() are specified by ECMAScript and return values derived from the OS timezone setting. All major browser engines — Blink, Gecko, and WebKit — delegate to the OS timezone database. A device set to 'Europe/Berlin' returns the same IANA name and offset across Chrome, Safari, and Firefox.

VPN usage does not change the OS timezone. The signal therefore remains stable through VPN connections and produces the same hash regardless of the network exit point.

Things worth knowing

  • getTimezoneOffset() returns UTC minus local time in minutes, so UTC+5 returns -300 and UTC-5 returns +300 — the inverse of ISO 8601 sign convention. The raw numeric value is stored and hashed as-is.
  • DST transitions change the timezoneOffset value while the IANA name remains fixed. Two fingerprints collected across a DST boundary will have different hashes even for the same device. Matching on the IANA name alone (value.hardware[0]) can recover the link across DST transitions.
  • If Intl.DateTimeFormat fails (very old browsers or non-standard runtimes), timezoneName and locale are empty strings. The offset is still collected, and the signal returns normal with a partial hash of '|offset'.
  • Firefox with resistFingerprinting overrides the timezone to UTC (timezoneName = 'UTC', timezoneOffset = 0). The signal returns normal but the hash will not match non-resistFingerprinting browsers on the same device. Brave and Safari apply no timezone restrictions.