Open your browser. Look at the clock. That number didn't come from the internet. Your machine already knew it.
The browser reads two things from the operating system: a count of milliseconds since 1 January 1970 UTC, and a zone identifier like Europe/Lisbon. No network request. No permission dialog. The moment is universal. The zone makes it local. That's the whole mechanism.
Here's how it works, link by link.
Where the browser gets the moment
Your operating system maintains a count that advances monotonically. It syncs with NTP servers, usually every few hours. A hardware clock on the motherboard carries the value across power cycles. Left alone, that hardware clock drifts by seconds a day.
That drift shows up directly in what gets displayed. If your OS clock is off by thirty seconds, every timestamp the browser produces is off by thirty seconds. The browser cannot correct it. It has no independent source of truth.
The system clock is the foundation. Get it wrong, and everything above it is wrong too.
Two JavaScript interfaces, one source
Date.now() returns the current moment as milliseconds since the epoch. No zone is involved. None is needed. The number is identical whether you call it from Tokyo or Toronto.
new Date() creates an object representing the current moment. Internally it stores the same kind of millisecond count. The object itself has no zone. It's just a moment.
Here's what trips people up. The Date object stores a moment, but most of its methods present that moment in the machine's own zone. toString(), getHours(), getFullYear() all apply the system's configured time zone automatically. The object doesn't become local. It stays universal. The display methods translate it.
So when new Date().getHours() returns 3 PM, that's not because the Date object is "in" your time zone. It's because the method applied your zone to the underlying moment.
What the methods actually return
The Date object's getter methods all operate in local time by default. getHours() returns the hour in the system zone. getUTCHours() returns the hour in UTC. Same moment, two different numbers.
This is the source of countless bugs. A developer calls getHours(), stores the result, and later discovers the value is meaningless without the zone that produced it. The fix is simple: store the millisecond count. Convert to hours only at display time.
How the browser knows your zone
The browser reads your time zone from the operating system. It doesn't guess based on your IP address. It doesn't ask where you are. It reads the region setting your OS has stored.
Modern operating systems store the zone as an IANA identifier. Names like Europe/Lisbon or America/Sao_Paulo. The browser reads that identifier directly and uses it to convert moments to local time.
This matters more than it sounds. The IANA database records not just each region's current offset but its entire history of rule changes and its scheduled future ones. That's why a machine can correctly show the local time of a meeting held in 1998 under rules that no longer apply.
The database is updated several times a year as governments change their minds. Those updates reach browsers through operating system patches and browser releases.
Why the IANA database matters for display
The IANA database is not just a list of offsets. It's a complete history. When a government changes its daylight saving rules, the database records that change. When a country moves its border, the database reflects it.
That's why "just use UTC" isn't always the right advice for display. UTC tells you the moment, but it doesn't tell you what a clock on a wall in Mumbai would show. The zone does that.
What Intl.DateTimeFormat reveals about the zone
The Intl.DateTimeFormat API provides locale-aware formatting. It's the modern way to present dates and times in JavaScript.
Here's the key call:
javascript
Intl.DateTimeFormat().resolvedOptions().timeZone
This returns the IANA zone identifier. Something like Asia/Kolkata or Europe/London. It tells you exactly what zone the tool is using.
How the API picks a zone
The resolved zone comes from the operating system. The API doesn't guess. It doesn't fall back to UTC. It uses whatever the OS reports, and if the OS reports something invalid, the behaviour is implementation-defined.
That means two machines running the same browser but configured with distinct zones will produce different output from the same Intl.DateTimeFormat call. The API is deterministic given the same inputs. The inputs include the zone.
What the API doesn't do
Intl.DateTimeFormat makes no network requests. It formats locally. All the locale data, all the zone rules, all the formatting patterns ship with the browser or the OS. The formatting happens entirely in the engine.
This is unlike some server-side libraries that fetch zone data on demand. The browser already has everything it needs.
What happens when you change your computer's clock
Changing your computer clock changes the moment, not just the zone.
If you set your clock back three hours, Date.now() returns a smaller number. The moment itself is different. The browser has no way to know you were "really" at the correct time before. It trusts the OS clock completely.
This is unlike changing your time zone. When you change your zone from America/New_York to Europe/London, the moment stays the same. The OS clock doesn't change. Only the display changes.
That's a common point of confusion. People think changing the zone changes the time. It doesn't. It changes what the time looks like. The moment is what it is.
Debugging the two failure modes
So if you're debugging a timestamp problem, ask two questions. Is the clock itself wrong? Or is the zone wrong? They're different problems with different fixes.
A wrong clock means Date.now() returns an incorrect value. Fix it with NTP. A wrong zone means the display methods apply the wrong offset. Fix it in the OS region settings. Confusing the two wastes hours.
Why your clock might be wrong even with NTP
Your computer's clock isn't automatically accurate. It syncs with NTP servers, but that sync isn't immediate. It happens periodically, and the interval varies by operating system and configuration.
If your clock drifts between syncs, that drift appears in every timestamp you produce. A browser can't fix that. It reads what the OS gives it.
Leap seconds and the gap in JavaScript
UTC occasionally gets an extra second to stay in sync with Earth's rotation. The IANA database tracks these. Most systems handle them by smearing the extra second across the day or by simply ignoring it. JavaScript's Date object counts milliseconds since the epoch, and it doesn't account for leap seconds.
As of October 2026, no new leap second has been inserted since 31 December 2016. That's the longest gap since the practice began in 1972. The General Conference on Weights and Measures resolved in November 2022 to abolish leap seconds by 2035, replacing them with a larger correction at longer intervals.
No permissions, no network: the privacy of local time
The browser needs no permission to tell time. It doesn't ask. It can't. There's no permission prompt for "access your clock."
That's because the time isn't sensitive. It's not your location. It's not your files. It's a number that every machine on earth shares, give or take a zone setting.
A page that shows a clock makes no network request for the time. It reads the OS clock and the zone setting. That's it. The time you see is computed locally, from your system's data.
What a script can infer
A script can't tell where you are from your clock. It can only tell what your clock says. Your zone setting is a weak signal, but it's a signal a script can read without asking.
The IANA zone identifier narrows you to a region, not a street address. Europe/London covers the entire United Kingdom. America/New_York spans multiple states. It's coarse. But it's there, and it's readable by any script that calls Intl.DateTimeFormat().resolvedOptions().timeZone.
When does the wall clock lie, and which timer should you use?
There's another clock in JavaScript, and it's not for telling time.
performance.now() returns a high-resolution relative timer. It measures time since the page's navigation start. It's not wall clock time. It's monotonic, meaning it only moves forward, and it doesn't care about your zone or your clock settings.
This is the tool for measuring how long something takes. Animations, benchmarks, profiling. You don't want a timer that jumps when the system clock syncs. performance.now() doesn't jump. It's designed for intervals.
Date.now() is for timestamps. When did this happen? When should that fire? It's tied to the wall clock, and the wall clock can be wrong.
When to use which
Use performance.now() for measuring duration. Use Date.now() for recording when something happened. Mixing them up produces bugs that are hard to spot and harder to fix.
A benchmark that calls Date.now() will report nonsense if an NTP sync fires mid-test. A scheduler that uses performance.now() for absolute times will drift. The two clocks serve separate purposes. Treat them that way.
What this means for your code
Understanding this chain changes how you write code.
Store moments, not local times. The moment is universal. The local time is a display artifact. When you store a local time without its zone, you lose information. When you store a moment, you can always recover the local time for any zone.
Store milliseconds, convert at the boundary
The common advice to "store UTC" is right but incomplete. Store the moment as milliseconds since the epoch. That's the most portable format. Convert to local time only at the display boundary.
When you do convert, use Intl.DateTimeFormat. It respects the user's zone and locale. Don't manually add or subtract offsets. The zone database knows more than you do about when daylight saving starts and ends.
Future events and changing rules
When you're scheduling future events, remember: the rules can change. The IANA database is updated as governments change their minds. A timestamp stored today might display differently next year. That's not a bug. It's the system working as designed.
For past events, store the moment. For future events, store the intended local time plus the zone identifier. That way, when the rules change, the system can recalculate the correct moment. Storing only the moment for a future event locks in today's rules. That might be wrong tomorrow.