Ask any JavaScript developer how to convert between timezones and someone will say: "use getTimezoneOffset()."
Don't.
Here's why, and what to use instead.
What getTimezoneOffset() Actually Returns
const d = new Date();
console.log(d.getTimezoneOffset()); // e.g., -330 for IST, 300 for US/Eastern
Two problems with this:
1. It returns the local timezone offset, not an arbitrary one.
getTimezoneOffset() tells you the offset of the machine running the code. You cannot use it to find out what time it is in Tokyo from a server running in UTC.
2. The sign is backwards from what you'd expect.
UTC+5:30 (India) returns -330. UTC-5:00 (US East) returns 300. The value is UTC - local, not local - UTC. This trips up almost everyone the first time.
The Wrong Way to Convert Timezones
// ❌ This only works if the server happens to be in the right timezone
function toNewYorkTime(utcDate) {
const offset = -300; // "NYC is UTC-5" hardcoded
const localTime = new Date(utcDate.getTime() + offset * 60000);
return localTime;
}
This breaks the moment New York observes Daylight Saving Time (UTC-4, not -5). You've just hardcoded a bug that appears twice a year.
The Right Way: Intl.DateTimeFormat
Modern JavaScript has built-in timezone support via the Intl API:
// Display a UTC timestamp in any IANA timezone
function formatInTimezone(date, timezone, locale = 'en-US') {
return new Intl.DateTimeFormat(locale, {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
}).format(date);
}
const now = new Date();
formatInTimezone(now, 'America/New_York'); // "03/24/2026, 08:00:00"
formatInTimezone(now, 'Asia/Tokyo'); // "03/24/2026, 21:00:00"
formatInTimezone(now, 'Europe/Paris'); // "03/24/2026, 13:00:00"
This correctly handles DST, historical timezone changes, and all the edge cases you'd never think of.
Getting the Offset for Any Timezone
Sometimes you need the numeric offset (for database storage, API calls, etc.):
function getTimezoneOffsetMinutes(timezone, date = new Date()) {
// Format as offset string in the target timezone
const formatter = new Intl.DateTimeFormat('en', {
timeZone: timezone,
timeZoneName: 'shortOffset', // 'GMT+5:30', 'GMT-4', etc.
});
const parts = formatter.formatToParts(date);
const offsetStr = parts.find(p => p.type === 'timeZoneName')?.value;
// Parse 'GMT+5:30' → +330, 'GMT-4' → -240
const match = offsetStr?.match(/GMT([+-])(\d+)(?::(\d+))?/);
if (!match) return 0;
const sign = match[1] === '+' ? 1 : -1;
const hours = parseInt(match[2], 10);
const minutes = parseInt(match[3] || '0', 10);
return sign * (hours * 60 + minutes);
}
getTimezoneOffsetMinutes('America/New_York'); // -240 (during EDT)
getTimezoneOffsetMinutes('Asia/Kolkata'); // +330 (IST, no DST)
IANA Timezone Names Are What You Need
Forget abbreviations like EST, PST, IST. They're ambiguous:
-
IST= Indian Standard Time or Irish Standard Time or Israel Standard Time -
CST= Central Standard Time (US) or China Standard Time or Cuba Standard Time
Always use IANA timezone database names: America/New_York, Asia/Kolkata, Pacific/Auckland. These are unambiguous, globally consistent, and maintained with DST rule updates.
// Get user's IANA timezone
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(userTimezone); // e.g., 'America/Los_Angeles'
DST Edge Cases
Spring forward (clocks skip an hour):
In US/Eastern, 2:30am on March 8 doesn't exist. new Date('2026-03-08T02:30:00') in an ET environment will silently resolve to 3:30am.
Fall back (clocks repeat an hour):
1:30am on November 1 happens twice in US/Eastern. Without an explicit UTC offset, you can't distinguish them.
The only reliable way to handle these: store all timestamps in UTC, convert to local only for display.
// Store this
const event = { startsAt: '2026-11-01T06:30:00Z' }; // UTC ✓
// Display this
formatInTimezone(new Date(event.startsAt), 'America/New_York');
// Correctly shows 1:30 AM EST (after fall-back)
The Temporal API (Coming Soon)
// Future Temporal API — clean timezone handling
const now = Temporal.Now.zonedDateTimeISO('America/New_York');
const tokyo = now.withTimeZone('Asia/Tokyo');
console.log(tokyo.toString()); // No ambiguity, no offset math
Temporal makes timezone-aware arithmetic first-class. It's Stage 3 and shipping in Node 22+ experimentally.
Quick Timezone Lookup
When working across timezones, I often need a quick sanity check on what time it is somewhere right now. For static date conversions ("what time is 3pm EST in Tokyo?"), datetimecalculator.app handles the basics without needing to spin up a REPL.
Summary
-
getTimezoneOffset()only returns the local machine's offset, and the sign is inverted - Use
Intl.DateTimeFormatwith IANA timezone names for reliable display - Never hardcode UTC offsets — they change with DST
- Always store in UTC; convert to local only at the presentation layer
- Avoid timezone abbreviations; use full IANA names
What's the wildest timezone bug you've shipped? The DST "gains an hour" failures seem to be a universal developer experience.
Top comments (0)