Intl vs IP Geolocation: They Answer Different Questions (And Your Timezone Bug Proves It)

TL;DR: Intl.DateTimeFormat().resolvedOptions().timeZone tells you what timezone the user’s device is configured for (a user preference). IP geolocation tells you roughly where the request came from (a network fact). They are not two implementations of the same feature. Most timezone bugs I’ve seen come from picking the wrong mechanism, not from misusing the one you picked.

If you’ve ever shipped a scheduling feature and then got a bug report like “my reminder fired at 3 AM,” you already know the pain. You need the visitor’s timezone. You search around, and two mechanisms show up in every answer:

  • Intl.DateTimeFormat().resolvedOptions().timeZone: one line of client-side JavaScript. Returns an IANA zone name like Asia/Taipei.
  • IP geolocation: a server-side lookup on the request IP against a MaxMind-style database. Returns a country, a region, a city, and an inferred timezone.

They look interchangeable. They aren’t. Frankly, half the timezone questions on Stack Overflow are people debugging the gap between these two, without realizing they measure different things.

What each one actually measures

Here’s the mental model that makes every downstream decision obvious:

  • Intl is a user preference, mediated by the OS. The browser reads the timezone from the operating system settings, the thing the user can change with two clicks. Browser support stopped being a question back in 20171, and it hands you a proper IANA zone identifier, DST rules included.
  • IP geolocation is a network fact. A database maps IP ranges to physical locations. The OS’s opinion is nowhere in the loop. You’re not asking “what does the user want?” You’re asking “where does the network think this packet came from?”

One is a statement of intent. The other is a measurement of the network path. Once you say that out loud, “which is more accurate?” stops being a meaningful question. Accurate at what?

Where the two answers disagree

These aren’t exotic edge cases. In 2026, the disagreement set is large and growing:

  • The traveller. A laptop in Tokyo still set to Los Angeles time: Intl says America/Los_Angeles, IP says Asia/Tokyo. Neither is wrong. They answer different questions.
  • The VPN user. IP resolves to wherever the exit node lives: Amsterdam, Singapore, whatever the provider sold. Industry surveys put VPN usage at around a third of all internet users2; this is not a fringe scenario.
  • iCloud Private Relay. This one surprised me when I dug into the docs: Private Relay does not simply hide location. Apple deliberately assigns relay IPs that preserve the user’s country and timezone, and by default even a coarse city-level area3. So country-level IP lookup still works. What your server actually sees, though, is an egress IP hosted by Fastly or another relay partner, shared by a crowd of unrelated users. Any fraud logic keyed on stable per-user IPs quietly breaks.
  • The cloud desktop. A remote dev box or hosted browser reports the datacenter’s IP, while Intl reports whatever the VM image was configured with, often UTC.
  • The privacy-conscious user who sets their OS to UTC on purpose. Rare, but they exist, and they will file bug reports.

Stop asking “which is accurate” and start asking “which does my feature need”

This is the decision table I wish someone had handed me years ago:

The question your feature asks Use
“What time should I display this timestamp in?” Intl: respect the OS setting the user already chose
“What zone do I schedule this reminder in?” Intl: you need the user’s IANA zone name, DST-aware
“Which country’s store/pricing should I default to?” IP as a hint, always with an explicit country picker
“Is this login suspicious?” IP only: the OS setting is user-controlled and therefore untrusted input
“Which holidays should I highlight?” Locale from account settings (or resolvedOptions().locale); IP only to pre-sort the picker

The security row deserves emphasis: for fraud detection, Intl is worse than useless, because the attacker controls it. The fraud-detection crowd arrives at the same split from the other side. Castle’s writeup (in the references below) treats disagreement between the two signals as a feature, not a bug. For display and scheduling, IP is worse than useless, because it punishes every traveller and VPN user for daring to move. The mechanisms don’t compete. They serve different rows of the table.

Two traps that survive even a correct choice

Trap 1: storing an offset instead of the zone name. Intl gives you Europe/Berlin, which is DST-aware, but only if you keep it as the IANA name and let a date library resolve it at render time. Computing “+2 hours” today and storing that offset works until the last Sunday of October, then silently breaks. Store the zone name. This one cost me a weekend once; I’d like it back.

Trap 2: trusting city-level IP precision. Country-level IP accuracy is genuinely high. City-level is another world entirely:

Precision level Accuracy
Country 99.8% (MaxMind, self-reported)4
US state/region ~80% (MaxMind, self-reported)4
US city, within 50 km ~66% (MaxMind, self-reported)4
City, typical range 20–75%; mobile resolves to broad regions (MaxMind)5

It gets worse: early IPv6 allocations geolocate far less reliably than IPv4. Any UX that announces “we detected you’re in $CITY” will be confidently wrong for a big slice of your users. Don’t build that UX.

Trap 3: calling Intl where there is no user. Run Intl.DateTimeFormat().resolvedOptions().timeZone during server-side rendering and you get the server’s timezone: usually UTC, occasionally whatever the VM image shipped with. The API didn’t fail; you asked a machine with no user about its user preference. Collect the zone in the browser, then send it to your backend explicitly.

A practical composition

What I actually do in my own projects:

  1. Default to Intl for anything the user experiences as display or scheduling. It’s one line, no network call, works offline, and respects what the user already told their OS.
  2. Use IP as a soft signal: prefill a country picker, never make it the final answer.
  3. Show what you inferred and let the user change it in one click. Inference plus an escape hatch beats silent cleverness every time.
  4. For security contexts, ignore Intl entirely and combine IP with device signals. Untrusted input doesn’t get a vote in fraud decisions.

While building Light My Day, my answer-first lookup tool, I ended up making a timezone detect page that shows both signals side by side: the zone your device claims and the one your IP implies. Watching the two disagree from behind a VPN or Private Relay is the fastest way I know to make this whole mental model click. It’s also an honest demo of point 3: show the user both inferences, and let them decide which one is “right.”

The takeaway

Intl = user preference, OS-mediated. IP = network fact, path-mediated. Pick by matching the mechanism to the question your feature is actually asking, and the entire class of “timezone detection” bugs dissolves into a table lookup.

The same question works beyond timezones, too. Accept-Language header vs the language saved on the account, IP-inferred currency vs the one the user picked at checkout: every “user preference vs network fact” pair yields to the same table.

Next time you’re reviewing a PR that touches timezones, try asking one question: “is this code asking what the user wants, or where the packet came from?” I’d be curious how often the answer surprises the author.

References

Footnotes

  1. MDN: Intl.DateTimeFormat.prototype.resolvedOptions(): Baseline “widely available” since September 2017; see also caniuse: IANA time zone support.

  2. Surfshark: VPN statistics and usage trends estimates over 1.75 billion VPN users as of May 2025, about a third of internet users (a fifth of the world’s population). Vendor-published research, so treat the precise figure as directional; other vendors’ estimates of the internet-user share land in the same rough band.

  3. Apple: Prepare your network for iCloud Private Relay: relay IPs preserve the user’s country and timezone; users can optionally coarsen city-level location to country-level.

  4. MaxMind knowledge base: Geolocation accuracy: “we estimate that our GeoIP products can identify users at the country level with 99.8% accuracy … around an 80% accuracy at the state/region level, and a 66% accuracy for cities (within a 50km radius of that city).” 2 3

  5. MaxMind: Understanding IP geolocation accuracy: “City level accuracy is where the precision drops further, with typical accuracy rates of 20% to 75%,” and mobile devices “typically resolve to a broad region … rather than a specific city or postal code.”