Accurate time makes or breaks reminders, calendars, reports, and invoices. If your app says a meeting is “tomorrow” but your user sees “today,” trust takes a hit. You’ll learn exactly how to detect the user’s time zone and UTC offset in modern JavaScript, what to store, how to handle Daylight Saving Time, and how to avoid the most common traps.
What You’ll Get From Reading
You’ll get a complete, working JavaScript “mini-project” for reliable time zone detection in browser and Node, a clear explanation of the APIs, and advice on when to use offsets versus IANA time zone names. You’ll also see how this article improves on three popular competitor posts by filling the gaps they leave, and you’ll pick up tips they don’t cover, like consent and privacy signals around time data.
What “Time Zone” & “Offset”
A time zone is a human label like Europe/Stockholm
that includes political rules about clocks moving forward and back. An offset is a raw difference from UTC like +01:00
or -0700
. Offsets alone can’t tell you if Stockholm is about to switch to summer time; the IANA time zone name can. We’ll show you how to grab both. (The key methods we rely on are Intl.DateTimeFormat().resolvedOptions().timeZone
and Date.prototype.getTimezoneOffset()
.
The One Line Modern Solution
The most reliable cross-browser way to get the user’s time zone name is:
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
// e.g., "Europe/Stockholm"
This is the standards-based approach recommended today, and it works in all modern browsers. It returns an IANA zone string you can safely store server-side for scheduling and formatting. (See MDN for details on resolvedOptions()
.
The Quick Way to Get the UTC Offset
If you only need the numeric difference from UTC at “right now,” use:
const offsetMinutes = -new Date().getTimezoneOffset();
// e.g., +120 for UTC+02:00; note the sign inversion
The native getTimezoneOffset()
returns minutes “behind UTC,” so Sweden in summer (UTC+02:00) yields -120
and we negate it for the intuitive sign. This detail trips people up; get it right. (Behavior explained by MDN and long-standing articles.
A Complete Mini Project You Can Paste into Your App
Below is a tiny, self-contained project that detects the client’s zone and offset in the browser, sends them to your server, and shows how to format times back to the user. You can drop this into a framework or use it as vanilla JS.
{
"name": "timezone-detector",
"version": "1.0.0",
"description": "Detect client time zone and UTC offset reliably",
"type": "module",
"scripts": {
"start": "node server.js"
},
"dependencies": {}
}
// /public/timezone.js
export function detectTimeZone() {
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || null;
const offsetMinutes = -new Date().getTimezoneOffset();
return {
timeZone, // "Europe/Stockholm"
offsetMinutes, // e.g., 120
offsetISO: toOffsetISO(offsetMinutes) // "+02:00"
};
}
export function toOffsetISO(minutes) {
const sign = minutes >= 0 ? '+' : '-';
const abs = Math.abs(minutes);
const hh = String(Math.floor(abs / 60)).padStart(2, '0');
const mm = String(abs % 60).padStart(2, '0');
return `${sign}${hh}:${mm}`;
}
export function formatLocal(date, timeZone) {
// Fallback to system TZ if none provided
const tz = timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone;
return new Intl.DateTimeFormat(undefined, {
timeZone: tz,
year: 'numeric', month: 'short', day: '2-digit',
hour: '2-digit', minute: '2-digit'
}).format(date);
}
<!-- /public/index.html -->
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Time Zone Detector Demo</title>
<meta name="description" content="Detect the client time zone and UTC offset in JavaScript">
</head>
<body>
<h1>Time Zone Detector</h1>
<pre id="out"></pre>
<script type="module">
import { detectTimeZone, formatLocal } from './timezone.js';
const info = detectTimeZone();
const now = new Date();
document.getElementById('out').textContent = JSON.stringify({
...info,
nowLocal: formatLocal(now, info.timeZone),
nowUTC: now.toISOString()
}, null, 2);
// Example: send to your server
fetch('/api/timezone', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(info)
});
</script>
</body>
</html>
// /server.js
import http from 'node:http';
import { readFile } from 'node:fs/promises';
import { extname } from 'node:path';
const port = 3000;
const server = http.createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/api/timezone') {
let body = '';
req.on('data', chunk => (body += chunk));
req.on('end', () => {
try {
const data = JSON.parse(body);
console.log('Client time zone payload:', data);
} catch {}
res.writeHead(204).end();
});
return;
}
// Simple static file server for /public
const filePath = req.url === '/' ? '/public/index.html' : req.url;
try {
const file = await readFile(new URL(`.${filePath}`, import.meta.url));
const type = {
'.html': 'text/html',
'.js': 'text/javascript',
'.json': 'application/json'
}[extname(filePath)] || 'text/plain';
res.writeHead(200, { 'Content-Type': type }).end(file);
} catch {
res.writeHead(404).end('Not found');
}
});
server.listen(port, () => {
console.log(`Demo on http://localhost:${port}`);
});
Final Thought
In short, make the platform do the heavy lifting for you detect the IANA time zone with Intl.DateTimeFormat().resolvedOptions().timeZone
, capture the current offset with getTimezoneOffset()
, store both, and format with Intl
so Daylight Saving Time never bites you again. This small habit prevents missed meetings, confused invoices, and “is it today or tomorrow?” moments for startups, global SaaS, and even local crews like närkes elektriska scheduling site visits. Ship the tiny mini project, add a privacy note, test around DST, and you’ll turn “time” from a support headache into a quiet, reliable feature your users don’t have to think about.