Time zones are the biggest source of date bugs, because the same moment has different clock times in different places.
Two things that are different
- A moment in time is the same for everybody. A
Datestores exactly that: a timestamp. - A clock reading (“10:30 on 15 March”) depends on the time zone.
A Date object has no time zone of its own. Whenever you read parts of it or print it, JavaScript turns the moment into a clock reading using either UTC or the visitor’s local zone.
const moment = new Date("2026-03-15T23:30:00Z");
console.log(moment.getUTCDate()); // → 15 (UTC reading)
// moment.getDate() could be 15 or 16, depending on where the user is
At 23:30 UTC, it is already tomorrow in Tokyo or Sydney. That is how “off by one day” bugs happen.
UTC
UTC (Coordinated Universal Time) is the shared reference with no daylight saving. "Z" at the end of an ISO string means UTC.
Rules that prevent most problems
- Store and send timestamps in UTC, preferably ISO strings (
toISOString()) or numbers. - Convert to local time only when you display it, with
toLocaleString,Intl.DateTimeFormator the local getters. - Be careful with date-only values such as birthdays and holidays. They have no time and no zone, but
new Date("2026-03-15")is UTC midnight, which can display as the previous day in the Americas.
const birthday = new Date("2026-03-15"); // UTC midnight
console.log(birthday.toLocaleDateString("en-US", { timeZone: "UTC" })); // → 3/15/2026
console.log(birthday.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles" })); // → 3/14/2026
The second line is the same moment shown in Los Angeles, where it is still the 14th. For a date-only value, display it with timeZone: "UTC", as in the first line, so it never shifts.
Seeing the visitor’s zone
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(typeof zone); // → string
The result is a name such as Asia/Kolkata.
Daylight saving time
Clocks jump in spring and fall in many places, so local days can be 23 or 25 hours long, and some local times do not exist or happen twice. Doing arithmetic in UTC avoids it, and the Intl formatters apply the correct rules for display.
Do not compute with time zone offsets by hand
getTimezoneOffset() is the offset right now, and offsets change with daylight saving. Avoid subtracting it manually. Let Intl do the conversion.