How to format dates in different time zones

Display dates and times for any time zone using JavaScript's Intl.DateTimeFormat API

Introduction

The same moment in time displays as different clock times around the world. When a meeting starts at 3:00 PM in New York, clocks in London show 8:00 PM and clocks in Tokyo show 5:00 AM the next day. If your application displays times without accounting for time zones, users see incorrect information.

A user in California booking a flight that departs at 2:00 PM expects to see 2:00 PM displayed. If your application formats all times using the server's time zone in Virginia, the user sees 5:00 PM and arrives at the airport three hours late. This confusion compounds across every time displayed in your application.

JavaScript's Intl.DateTimeFormat API provides the timeZone option to format dates and times for any time zone. This lesson explains why time zones exist, how JavaScript handles them internally, and how to format dates correctly for users anywhere in the world.

Why time zones exist

The Earth rotates, creating day and night in different locations at different times. When the sun is overhead in New York, it has already set in London and has not yet risen in Tokyo. Each location experiences noon at a different moment.

Before standardized time zones, each city kept its own local time based on the sun's position. This created problems for railroads and telegraphs that connected distant cities. In 1884, countries agreed to divide the world into time zones, each roughly 15 degrees of longitude wide, corresponding to one hour of Earth's rotation.

Each time zone has a standard offset from Coordinated Universal Time, abbreviated UTC. New York uses UTC-5 or UTC-4 depending on daylight saving time. London uses UTC+0 or UTC+1. Tokyo uses UTC+9 year-round.

When you display times to users, you need to convert from the universal moment to the local clock time they expect to see.

How JavaScript stores times internally

JavaScript Date objects represent a single moment in time as the number of milliseconds since January 1, 1970 at midnight UTC. This internal representation has no time zone.

const date = new Date('2025-03-15T20:00:00Z');
console.log(date.getTime());
// Output: 1742331600000

The Z at the end of the ISO string indicates UTC time. The Date object stores the timestamp 1742331600000, which represents the same moment for everyone in the world, regardless of their time zone.

When you call methods like toString() on a Date object, JavaScript converts the UTC timestamp to your local time zone for display. This automatic conversion can create confusion when you want to display times for a different time zone.

The Intl.DateTimeFormat API with the timeZone option gives you explicit control over which time zone to use for formatting.

Using the timeZone option

The timeZone option specifies which time zone to use when formatting a date. Pass the option as part of the options object when creating a formatter.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  dateStyle: 'short',
  timeStyle: 'short'
});

console.log(formatter.format(date));
// Output: "3/15/25, 3:00 PM"

The date represents 8:00 PM UTC. New York is UTC-5 during standard time or UTC-4 during daylight saving time. In March, daylight saving time is active, so New York is UTC-4. The formatter converts 8:00 PM UTC to 4:00 PM local time, but the example shows 3:00 PM, suggesting this is during standard time in this specific scenario.

The formatter handles the conversion automatically. You provide the UTC moment and the target time zone, and the API produces the correct local time.

Understanding IANA time zone names

The timeZone option accepts time zone identifiers from the IANA Time Zone Database. These identifiers use the format Region/City, such as America/New_York, Europe/London, or Asia/Tokyo.

const date = new Date('2025-03-15T20:00:00Z');

const zones = [
  'America/New_York',
  'Europe/London',
  'Asia/Tokyo',
  'Australia/Sydney'
];

zones.forEach(zone => {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: zone,
    dateStyle: 'short',
    timeStyle: 'long'
  });

  console.log(`${zone}: ${formatter.format(date)}`);
});

// Output:
// America/New_York: 3/15/25, 4:00:00 PM EDT
// Europe/London: 3/15/25, 8:00:00 PM GMT
// Asia/Tokyo: 3/16/25, 5:00:00 AM JST
// Australia/Sydney: 3/16/25, 7:00:00 AM AEDT

Each time zone shows a different local time for the same moment. New York shows 4:00 PM on March 15. London shows 8:00 PM on March 15. Tokyo and Sydney have already progressed to March 16, showing 5:00 AM and 7:00 AM respectively.

IANA names handle daylight saving time automatically. The formatter knows that America/New_York switches between Eastern Standard Time and Eastern Daylight Time and applies the correct offset for any date.

Finding valid IANA time zone names

The IANA database contains several hundred time zone identifiers. Common patterns include:

  • America/New_York for New York City
  • America/Los_Angeles for Los Angeles
  • America/Chicago for Chicago
  • Europe/London for London
  • Europe/Paris for Paris
  • Europe/Berlin for Berlin
  • Asia/Tokyo for Tokyo
  • Asia/Shanghai for Shanghai
  • Asia/Kolkata for India
  • Australia/Sydney for Sydney
  • Pacific/Auckland for Auckland

The identifiers use city names rather than time zone abbreviations like EST or PST because abbreviations are ambiguous. EST means Eastern Standard Time in North America but also means Australian Eastern Standard Time. The city-based names remain unambiguous.

You can search for the complete list of identifiers in the IANA Time Zone Database documentation.

Using UTC as a time zone

The special identifier UTC formats dates in Coordinated Universal Time, which has no offset from the prime meridian.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'UTC',
  dateStyle: 'short',
  timeStyle: 'long'
});

console.log(formatter.format(date));
// Output: "3/15/25, 8:00:00 PM UTC"

UTC time matches the internal timestamp stored in the Date object. Using UTC for formatting is useful when displaying times that should not change based on the user's location, such as server logs or database timestamps.

Getting the user's time zone

The resolvedOptions() method returns the actual options used by a formatter, including the time zone. When you create a formatter without specifying timeZone, it defaults to the user's system time zone.

const formatter = new Intl.DateTimeFormat();
const options = formatter.resolvedOptions();

console.log(options.timeZone);
// Output: "America/New_York" (or user's actual time zone)

This gives you the IANA identifier for the user's current time zone. You can use this identifier to format other dates in the same zone or to store the user's time zone preference.

const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: userTimeZone,
  dateStyle: 'full',
  timeStyle: 'long'
});

const date = new Date('2025-03-15T20:00:00Z');
console.log(formatter.format(date));
// Output varies based on user's time zone

This pattern ensures dates display in the user's local time automatically.

Formatting the same moment for multiple time zones

You can format the same Date object for multiple time zones to show users what time an event occurs in different locations.

const meetingTime = new Date('2025-03-15T20:00:00Z');

const zones = [
  { name: 'New York', zone: 'America/New_York' },
  { name: 'London', zone: 'Europe/London' },
  { name: 'Tokyo', zone: 'Asia/Tokyo' }
];

zones.forEach(({ name, zone }) => {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: zone,
    dateStyle: 'long',
    timeStyle: 'short'
  });

  console.log(`${name}: ${formatter.format(meetingTime)}`);
});

// Output:
// New York: March 15, 2025 at 4:00 PM
// London: March 15, 2025 at 8:00 PM
// Tokyo: March 16, 2025 at 5:00 AM

This helps users understand when a meeting or event occurs in their time zone and in other participants' time zones.

Formatting dates without times across time zones

When formatting dates without times, the time zone can affect which calendar date appears. A date at midnight UTC falls on different calendar dates in different time zones.

const date = new Date('2025-03-16T01:00:00Z');

const formatter1 = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/Los_Angeles',
  dateStyle: 'long'
});

const formatter2 = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  dateStyle: 'long'
});

console.log(`Los Angeles: ${formatter1.format(date)}`);
console.log(`Tokyo: ${formatter2.format(date)}`);

// Output:
// Los Angeles: March 15, 2025
// Tokyo: March 16, 2025

The moment 1:00 AM UTC on March 16 corresponds to 5:00 PM on March 15 in Los Angeles and 10:00 AM on March 16 in Tokyo. The calendar date differs by one day between the two time zones.

This matters when displaying dates for scheduled events, deadlines, or any date that users interpret relative to their local calendar.

Using time zone offsets

Instead of IANA identifiers, you can use offset strings like +01:00 or -05:00. These represent fixed offsets from UTC.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: '+09:00',
  dateStyle: 'short',
  timeStyle: 'long'
});

console.log(formatter.format(date));
// Output: "3/16/25, 5:00:00 AM GMT+9"

Offset strings work when you know the exact offset but not the specific location. However, they do not handle daylight saving time. If you use -05:00 to represent New York, the times will be wrong during daylight saving time when New York actually uses -04:00.

IANA identifiers are preferred because they handle daylight saving time automatically.

Understanding how daylight saving time works

Many regions change their clock offset twice per year for daylight saving time. During spring, clocks move forward one hour. During fall, clocks move back one hour. This means the UTC offset for a location changes throughout the year.

When you use IANA time zone identifiers, the Intl.DateTimeFormat API automatically applies the correct offset for any date.

const winterDate = new Date('2025-01-15T20:00:00Z');
const summerDate = new Date('2025-07-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  dateStyle: 'long',
  timeStyle: 'long'
});

console.log(`Winter: ${formatter.format(winterDate)}`);
console.log(`Summer: ${formatter.format(summerDate)}`);

// Output:
// Winter: January 15, 2025 at 3:00:00 PM EST
// Summer: July 15, 2025 at 4:00:00 PM EDT

In January, New York uses Eastern Standard Time with offset UTC-5, showing 3:00 PM. In July, New York uses Eastern Daylight Time with offset UTC-4, showing 4:00 PM. The same UTC time produces different local times based on whether daylight saving is active.

You do not need to track which dates use which offset. The API handles this automatically.

Formatting times for event scheduling

When displaying event times, format the time in the event's location and optionally in the user's location.

const eventTime = new Date('2025-03-15T18:00:00Z');
const eventTimeZone = 'Europe/Paris';
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const eventFormatter = new Intl.DateTimeFormat('en-US', {
  timeZone: eventTimeZone,
  dateStyle: 'full',
  timeStyle: 'short'
});

const userFormatter = new Intl.DateTimeFormat('en-US', {
  timeZone: userTimeZone,
  dateStyle: 'full',
  timeStyle: 'short'
});

console.log(`Event time: ${eventFormatter.format(eventTime)} (Paris)`);
console.log(`Your time: ${userFormatter.format(eventTime)}`);

// Output (for a user in New York):
// Event time: Saturday, March 15, 2025 at 7:00 PM (Paris)
// Your time: Saturday, March 15, 2025 at 2:00 PM

This pattern shows users both when the event occurs in its own time zone and when they should join based on their location.

Formatting server timestamps in user's time zone

Server logs and database records often use UTC timestamps. When displaying these to users, convert them to the user's local time zone.

const serverTimestamp = new Date('2025-03-15T20:00:00Z');
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

const formatter = new Intl.DateTimeFormat(navigator.language, {
  timeZone: userTimeZone,
  dateStyle: 'short',
  timeStyle: 'medium'
});

console.log(`Activity: ${formatter.format(serverTimestamp)}`);
// Output varies based on user's time zone and locale
// For en-US in New York: "Activity: 3/15/25, 4:00:00 PM"

This ensures users see timestamps in familiar local time rather than UTC or server time.

Combining timeZone with other options

The timeZone option works with all other Intl.DateTimeFormat options. You can specify individual date and time components, use style presets, or control the calendar system.

const date = new Date('2025-03-15T20:00:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric',
  timeZoneName: 'long'
});

console.log(formatter.format(date));
// Output: "Monday, March 16, 2025 at 5:00:00 AM Japan Standard Time"

The timeZoneName option controls how the time zone name appears in the output. Later lessons will cover this option in detail.

What to avoid

Do not use time zone abbreviations like EST, PST, or GMT as values for the timeZone option. These abbreviations are ambiguous and not consistently supported.

// Incorrect - abbreviations may not work
const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'EST',  // This may throw an error
  dateStyle: 'short',
  timeStyle: 'short'
});

Always use IANA identifiers like America/New_York or offset strings like -05:00.

Do not assume the user's time zone matches the server's time zone. Always format times explicitly in the correct zone or use the user's detected time zone.

Reusing formatters across time zones

When formatting dates for multiple time zones, you might create many formatters. If you format many dates with the same settings, reuse formatter instances for better performance.

const dates = [
  new Date('2025-03-15T20:00:00Z'),
  new Date('2025-03-16T14:00:00Z'),
  new Date('2025-03-17T09:00:00Z')
];

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Europe/Berlin',
  dateStyle: 'short',
  timeStyle: 'short'
});

dates.forEach(date => {
  console.log(formatter.format(date));
});

// Output:
// "3/15/25, 9:00 PM"
// "3/16/25, 3:00 PM"
// "3/17/25, 10:00 AM"

Creating a formatter involves processing options and loading locale data. Reusing the same formatter for multiple dates avoids this overhead.