How to Detect User Timezone in JavaScript (3 Methods Compared)
Displaying times in the wrong timezone is a surprisingly common UX problem. Meeting schedulers, event platforms, dashboards, and notification systems all need to know the user's timezone. Here are three ways to detect it, with tradeoffs for each.
Method 1: Intl API (Client-Side)
The simplest and most reliable client-side approach:
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(timezone); // "America/New_York"Pros:
- No API call needed โ instant, zero latency
- Reads from the OS timezone setting โ very accurate
- Supported in all modern browsers (97%+ coverage)
Cons:
- Only works client-side โ not available in server components or APIs
- Returns the OS setting, which the user may have configured incorrectly
- Can't be used for server-side rendering or email scheduling
Method 2: IP Geolocation (Server or Client)
Get the timezone from the user's IP address:
const { timezone } = await fetch(
"https://geo.kamero.ai/api/geo"
).then(r => r.json());
console.log(timezone); // "America/New_York"Pros:
- Works on both server and client side
- Doesn't depend on the user's OS settings
- Returns IANA timezone name (same format as Intl API)
- Also gives you city, country, and coordinates as a bonus
Cons:
- Requires a network request (~50ms)
- VPN users will get the VPN server's timezone
- Slightly less accurate than the OS setting for edge cases
Method 3: Date UTC Offset
The old-school approach using the Date object:
const offsetMinutes = new Date().getTimezoneOffset();
const offsetHours = -offsetMinutes / 60;
console.log(`UTC${offsetHours >= 0 ? "+" : ""}${offsetHours}`);
// "UTC-5"Pros:
- Works everywhere, even in very old browsers
- No API call needed
Cons:
- Returns an offset, not a timezone name โ UTC-5 could be EST, CDT, COT, PET, or ECT
- Doesn't account for DST transitions
- Ambiguous โ multiple timezones share the same offset
- Not useful for scheduling future events
Which Method Should You Use?
| Scenario | Best Method | Why |
|---|---|---|
| Display local time in UI | Intl API | Instant, accurate, no network |
| Server-side rendering | IP Geolocation | Only option on the server |
| Email scheduling | IP Geolocation | Need timezone before user interacts |
| Analytics / logging | IP Geolocation | Works server-side, gives extra data |
| Calendar / meeting app | Intl API + user confirmation | Highest accuracy, user can correct |
| Fallback / legacy | Date offset | Last resort when nothing else works |
Combining Methods for Best Results
The most robust approach uses the Intl API as the primary source and IP geolocation as a fallback or server-side complement:
async function detectTimezone() {
// Try Intl API first (client-side only)
if (typeof window !== "undefined") {
const intlTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (intlTz) return intlTz;
}
// Fallback to IP geolocation (works anywhere)
try {
const { timezone } = await fetch(
"https://geo.kamero.ai/api/geo"
).then(r => r.json());
return timezone || "UTC";
} catch {
return "UTC";
}
}
// Usage
const tz = await detectTimezone();
const now = new Date().toLocaleString("en-US", {
timeZone: tz,
dateStyle: "full",
timeStyle: "short",
});Practical Example: Show Event Times Locally
async function formatEventTime(utcTime: string) {
const tz = await detectTimezone();
const date = new Date(utcTime);
return {
local: date.toLocaleString("en-US", {
timeZone: tz,
dateStyle: "medium",
timeStyle: "short",
}),
timezone: tz,
offset: date.toLocaleString("en-US", {
timeZone: tz,
timeZoneName: "short",
}).split(" ").pop(),
};
}
const event = await formatEventTime("2026-03-15T18:00:00Z");
// { local: "Mar 15, 2026, 1:00 PM", timezone: "America/New_York", offset: "EST" }Get Timezone from IP
The Kamero API returns IANA timezone with every request. No key needed.
View Documentation โ