How to Handle Timezones and UTC in JavaScript
Timezones trip up almost every developer at some point, mostly because
Date objects don't actually "have" a timezone — internally, every
Date is just a millisecond count since the Unix epoch (UTC). Timezones
only enter the picture when you format or parse a date. Once you
internalize that, most of the confusion goes away.
1. Detect the visitor's timezone
No library needed — the browser already knows:
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
// e.g. "America/Argentina/Buenos_Aires"2. Format a date in any timezone
Pass a timeZone to Intl.DateTimeFormat and it handles the
conversion, including daylight saving, for you:
new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true,
}).format(new Date());
// "3:45:12 PM"3. Get a timezone's current UTC offset
Useful for labels like "UTC-5" or converting between two zones:
function getOffsetLabel(timeZone, date) {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone, timeZoneName: "shortOffset",
}).formatToParts(date);
return parts.find(p => p.type === "timeZoneName").value; // "GMT-5"
}4. Convert a wall-clock time from one zone to another
This is the part people usually reach for a library for, but it's doable with just the offset above: build a UTC timestamp from the target date/time, then subtract the source zone's offset.
function convert(zoneA, hh, mm, refDate) {
// Y/M/D "today" as seen in zoneA
const parts = new Intl.DateTimeFormat("en-CA", {
timeZone: zoneA, year: "numeric", month: "2-digit", day: "2-digit",
}).formatToParts(refDate);
const get = t => +parts.find(p => p.type === t).value;
const offsetMin = /* parse "-300" from getOffsetLabel(zoneA, refDate) */ -300;
const utcMs = Date.UTC(get("year"), get("month") - 1, get("day"), hh, mm) - offsetMin * 60000;
return new Date(utcMs); // format this in zoneB to get the converted time
}This is exactly the technique behind our own Meeting Planner tool — worth a look if you'd rather not write the conversion logic yourself.
5. The one rule that prevents most bugs
Store and transmit dates in UTC (ISO 8601, e.g. 2026-07-22T14:30:00Z),
and only convert to a local timezone at the last moment — when you render it for a
human. Databases, APIs, and logs should never store "local time" without an
offset attached; that ambiguity is where most timezone bugs come from.
Do you need a library at all?
For most apps, no — Intl and Date cover formatting,
conversion, and offset lookups natively in every modern browser and in Node.js. Reach
for a library like date-fns-tz or Luxon only if you need
heavier date-math (adding business days, recurring events, etc.) — not just timezone
display.