Frontend Development 17 min read

Mastering Time Zones in JavaScript: From UTC Storage to DST Detection

Learn how to correctly handle time zones in JavaScript by storing dates in UTC, using the IANA database, detecting daylight saving time, and leveraging built‑in Intl APIs or libraries like Luxon, with practical code examples and best‑practice guidelines.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Mastering Time Zones in JavaScript: From UTC Storage to DST Detection

A recent issue reported by Australian users who could not select courses due to an incorrect time‑zone offset led to this comprehensive guide on handling time zones in JavaScript.

Time Zone

A time zone is a region of the Earth that shares the same standard time, usually defined as an offset from Coordinated Universal Time (UTC), e.g., UTC+8 means eight hours ahead of UTC.

Basic Concepts

UTC (Coordinated Universal Time) is the global reference.

GMT (Greenwich Mean Time) is historically similar to UTC.

Offset: the difference from UTC, such as CST = UTC+8.

Time‑zone names like EST, PST, etc.

Daylight Saving & Standard Time

Daylight Saving Time (DST) advances clocks by one hour during summer to make better use of daylight. It originated from Benjamin Franklin’s suggestion and became widespread in the 20th century, especially during wars to save energy.

Not all regions observe DST; tropical countries usually do not, while temperate zones often do.

IANA Time‑Zone Database

Computers use the IANA time‑zone database (e.g., "Asia/Shanghai", "America/New_York") to manage offsets and DST rules.

Asia/Shanghai – China Standard Time

America/New_York – Eastern Time (US)

Europe/London – London Time

When converting across zones, consider offset differences, DST transitions, and keep the database up‑to‑date.

How to Handle Time Zones

Store and process times in UTC, then convert to the user’s local zone only for display.

JavaScript’s Date object works with UTC internally. Use Intl.DateTimeFormat or toLocaleString to format for a specific zone, or rely on third‑party libraries such as Moment.js (deprecated) or Luxon.

Moment.js – rich features but no longer maintained; prefer Day.js or Luxon.

Luxon – modern library that supports the IANA database.

<code>// Using Luxon to format a time in a specific zone
const { DateTime } = require("luxon");
const dt = DateTime.now().setZone("America/New_York");
console.log(dt.toString()); // outputs New York time
</code>

Getting the Current Time Zone

The recommended way is to use Intl.DateTimeFormat().resolvedOptions().timeZone :

<code>const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(timeZone); // e.g., "Asia/Shanghai"
</code>

You can also use new Date().getTimezoneOffset() to obtain the minute difference from UTC.

<code>const offset = new Date().getTimezoneOffset();
console.log(offset); // e.g., -480 means UTC+8
const offsetHours = -offset / 60;
console.log(offsetHours); // 8
</code>

Detecting Daylight Saving Time

Compare offsets between winter and summer months:

<code>function isDaylightSavingTime() {
  const january = new Date(new Date().getFullYear(), 0, 1);
  const july = new Date(new Date().getFullYear(), 6, 1);
  const current = new Date();
  const stdTimezoneOffset = Math.max(january.getTimezoneOffset(), july.getTimezoneOffset());
  return current.getTimezoneOffset() < stdTimezoneOffset;
}
console.log(isDaylightSavingTime()); // true if DST is active
</code>

Alternatively, inspect the short time‑zone name returned by Intl.DateTimeFormat for a "D" (e.g., "PDT").

<code>function isDaylightSavingTimeIntl() {
  const date = new Date();
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  const dtf = new Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'short' });
  const parts = dtf.formatToParts(date);
  const timeZoneName = parts.find(p => p.type === 'timeZoneName');
  return timeZoneName ? timeZoneName.value.includes('D') : false;
}
console.log(isDaylightSavingTimeIntl());
</code>

Using Third‑Party Libraries

With moment-timezone you can directly query DST status:

<code>const moment = require('moment-timezone');
function isDaylightSavingTimeMoment() {
  const tz = moment.tz.guess();
  return moment.tz(tz).isDST();
}
console.log(isDaylightSavingTimeMoment()); // true if DST
</code>

About Intl

The Intl object provides internationalization APIs for dates, numbers, strings, etc. Key features include:

Intl.DateTimeFormat – time‑zone aware formatting.

Intl.NumberFormat – number and currency formatting.

Intl.Collator – locale‑aware string comparison.

Intl.RelativeTimeFormat – relative time descriptions.

Intl.ListFormat – natural language list formatting.

Intl.PluralRules – pluralization rules.

Most modern browsers support the core Intl features; newer ones (RelativeTimeFormat, ListFormat, PluralRules) require recent versions (Chrome 72+, Firefox 78+, Safari 13+).

For environments lacking Intl support, consider polyfills (e.g., @formatjs/intl) or perform formatting on the backend.

Time‑Zone Handling Tips

Key points to avoid errors:

1. Understand that Date stores time in UTC. Methods like getTime() return a UTC timestamp. Methods without the "UTC" prefix (e.g., getHours() ) return local time.

<code>const now = new Date();
console.log(now.getTime()); // UTC timestamp
</code>

Use getUTCHours() for UTC hour values.

<code>const date = new Date();
console.log("Local hour:", date.getHours());
console.log("UTC hour:", date.getUTCHours());
</code>

When creating a Date from a timestamp, the displayed string reflects the local zone.

<code>const timestamp = 1700000000000;
const date = new Date(timestamp);
console.log("Local time:", date.toString());
console.log("UTC time:", date.toUTCString());
</code>

2. Avoid manual offset calculations. Use toLocaleString or Intl APIs instead of adding/subtracting offsets, especially around DST transitions.

<code>// Not recommended: manual offset adjustment
const localDate = new Date();
const utcDate = new Date(localDate.getTime() + localDate.getTimezoneOffset() * 60000);
</code>

DST can change offsets (e.g., New York switches from UTC‑5 to UTC‑4). Rely on Intl or libraries like Luxon to handle these changes automatically.

3. Store times in a consistent format. Use UTC ISO‑8601 strings for persistence and convert to the user’s zone only for display.

<code>const utcNow = new Date().toISOString();
console.log(utcNow); // e.g., "2024-11-15T10:30:00.000Z"
</code>

Unix timestamps are raw numbers without zone information, while ISO‑8601 strings include the "Z" suffix to denote UTC.

4. Never hard‑code offsets. Retrieve the current offset with getTimezoneOffset() and keep the IANA database up‑to‑date.

<code>const offset = new Date().getTimezoneOffset();
console.log(offset); // e.g., -480 for UTC+8
</code>

Updating the ICU data (e.g., via the full‑icu package in Node.js) ensures the latest zone rules.

Conclusion

Mastering time‑zone handling in JavaScript—storing in UTC, using the IANA database, detecting DST, and leveraging Intl or modern libraries—prevents subtle bugs and ensures consistent behavior across regions.

JavaScriptDSTDateTime ZoneIntlLuxon
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.