DevToolsForYou
Date & Time

Working with Dates Across Time Zones in JavaScript

A practical guide to JavaScript dates — the Date object, UTC vs local time, formatting, parsing, and how to avoid the most common time zone bugs.

2 min readUpdated Apr 11, 2026

How the Date object works internally

A JavaScript Date is always stored internally as a UTC millisecond timestamp (the number of milliseconds since 1970-01-01T00:00:00Z). There is no stored time zone — only the display methods use the local time zone. This is the root of most date bugs: developers confuse the stored UTC value with the local representation.

javascript
const d = new Date('2024-01-15T12:00:00Z'); // noon UTC
console.log(d.getTime());       // 1705320000000 — always UTC ms
console.log(d.toISOString());   // '2024-01-15T12:00:00.000Z' — always UTC
console.log(d.toString());      // local time (machine-dependent)
console.log(d.toUTCString());   // UTC string

Parsing dates safely

Date string parsing is notoriously inconsistent across engines. The safest rule: only parse ISO 8601 strings, and always include a time zone offset. A date-only string like '2024-01-15' is parsed as UTC midnight in modern browsers but as local midnight in some environments — a common off-by-one-day bug.

javascript
// Safe — explicit UTC offset
new Date('2024-01-15T00:00:00Z');     // midnight UTC
new Date('2024-01-15T00:00:00+05:30'); // midnight IST

// Risky — no time component
new Date('2024-01-15');  // UTC in browsers, local in Node

// Avoid — non-ISO formats are implementation-defined
new Date('January 15, 2024');  // works in Chrome, may fail elsewhere

Formatting dates for display

Use Intl.DateTimeFormat for locale-aware formatting — it handles time zones, 12/24-hour clocks, and language-specific formats. Avoid building date strings manually with getMonth() (0-indexed!) or getDate().

javascript
const d = new Date('2024-01-15T12:00:00Z');

// Format in a specific time zone and locale
const fmt = new Intl.DateTimeFormat('en-GB', {
  timeZone: 'Asia/Kolkata',
  year: 'numeric', month: 'short', day: 'numeric',
  hour: '2-digit', minute: '2-digit', timeZoneName: 'short',
});
console.log(fmt.format(d));
// '15 Jan 2024, 17:30 IST'

// Quick ISO string
d.toISOString();  // '2024-01-15T12:00:00.000Z'

// Locale default
d.toLocaleDateString('en-US', { timeZone: 'America/New_York' });
// '1/15/2024'

Date arithmetic

Do arithmetic on timestamps (milliseconds), not on date components. Adding days by incrementing getDate() fails at month boundaries — use a library or operate on the underlying number.

javascript
const MS_PER_DAY = 86_400_000;

// Add 7 days
const d = new Date('2024-01-15T00:00:00Z');
const plus7 = new Date(d.getTime() + 7 * MS_PER_DAY);
console.log(plus7.toISOString()); // 2024-01-22T00:00:00.000Z

// Difference in days between two dates
const a = new Date('2024-01-01T00:00:00Z');
const b = new Date('2024-03-15T00:00:00Z');
const diffDays = (b - a) / MS_PER_DAY;
console.log(diffDays); // 74

The Temporal API (modern replacement)

The TC39 Temporal API is available in modern browsers and Node.js 22+ (behind a flag in earlier versions). It was designed to fix all of Date's problems: explicit time zones, unambiguous parsing, immutable values, and proper calendar support. Use it for new code where available.

javascript
// Temporal — explicit, unambiguous
const instant = Temporal.Instant.from('2024-01-15T12:00:00Z');
const zoned   = instant.toZonedDateTimeISO('Asia/Kolkata');

console.log(zoned.toString());
// '2024-01-15T17:30:00+05:30[Asia/Kolkata]'

// Add duration
const later = zoned.add({ days: 7, hours: 2 });

// Plain date (no time zone)
const date = Temporal.PlainDate.from('2024-01-15');
console.log(date.add({ months: 1 }).toString()); // '2024-02-15'
Frequently asked questions

Why is getMonth() 0-indexed?

A historical quirk inherited from Java's java.util.Date. Months are 0–11 (January = 0, December = 11) but days are 1–31. Always add 1 when displaying a month: new Date().getMonth() + 1. The Temporal API uses 1-indexed months.

Should I use a date library like date-fns or dayjs?

For complex date manipulation (parsing arbitrary formats, recurring events, calendar support) a library saves a lot of pain. date-fns is modular and tree-shakeable. dayjs is tiny and has a moment.js-compatible API. For basic formatting and arithmetic in modern environments, the native Date + Intl APIs are sufficient.

What is the difference between UTC and GMT?

For most practical purposes they are the same. GMT (Greenwich Mean Time) is a time zone. UTC (Coordinated Universal Time) is the international time standard that GMT is defined relative to. UTC never observes daylight saving time; some GMT-based time zones do. In code, always use UTC.

Related cheatsheetsAll cheatsheets →
Related guidesAll guides →