How to Format Date with Timezone in TypeScript

You can format a date with a timezone in TypeScript by passing an IANA zone name, like America/New_York, as the timeZone option of Intl.DateTimeFormat or toLocaleString(). The Date itself never changes; only the text you print does:

new Intl.DateTimeFormat("en-US", { timeZone: "America/Chicago", timeStyle: "short" }).format(date);

I tested the code below with TypeScript 7.0.2 and Node.js v22.22.2 in strict mode. My test machine’s clock was pinned to US Central time, which matters more than you’d think.

We’ll cover the built-in formatter for each US zone, the machine time zone trap, an ISO string with an offset, daylight saving time, validating zone names and the type errors you’ll hit along the way.

Format a date in a specific time zone with Intl.DateTimeFormat

A JavaScript Date holds a single moment as milliseconds since 1970 UTC. It has no zone of its own. The zone only appears when you turn it into text, so that’s where you pass it:

const launch = new Date("2026-10-16T15:00:00Z");   // one instant in time

const zones: string[] = [
  "America/New_York",
  "America/Chicago",
  "America/Denver",
  "America/Phoenix",
  "America/Los_Angeles",
];

for (const timeZone of zones) {
  const text = new Intl.DateTimeFormat("en-US", {
    timeZone,
    weekday: "short",
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "numeric",
    minute: "2-digit",
    timeZoneName: "short",
  }).format(launch);
  console.log(timeZone.padEnd(20), text);
}

// dateStyle/timeStyle are a shortcut, but they can't be combined with timeZoneName
const styled = new Intl.DateTimeFormat("en-US", {
  dateStyle: "full",
  timeStyle: "short",
  timeZone: "America/Los_Angeles",
});
console.log("dateStyle + timeStyle:", styled.format(launch));
try {
  new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZoneName: "short" });
} catch (err) {
  console.log("dateStyle + timeZoneName ->", (err as Error).name);
}

Output:

America/New_York     Fri, Oct 16, 2026, 11:00 AM EDT
America/Chicago      Fri, Oct 16, 2026, 10:00 AM CDT
America/Denver       Fri, Oct 16, 2026, 9:00 AM MDT
America/Phoenix      Fri, Oct 16, 2026, 8:00 AM MST
America/Los_Angeles  Fri, Oct 16, 2026, 8:00 AM PDT
dateStyle + timeStyle: Friday, October 16, 2026 at 8:00 AM
dateStyle + timeZoneName -> TypeError
Command Prompt output of TypeScript Intl.DateTimeFormat formatting one date in New York, Chicago, Denver, Phoenix and Los Angeles time zones
One launch time, five US time zones: 11:00 AM EDT in New York is 8:00 AM PDT in Los Angeles.

Use the IANA names (America/Denver), not labels like “Mountain Time”. They carry the full daylight saving history, which is why Phoenix correctly shows MST in October while Denver is on MDT. For the basics of the object itself, see working with dates in TypeScript.

The dateStyle and timeStyle shortcuts are handy, but you can’t mix them with individual fields such as timeZoneName or hour. Doing that throws a TypeError at runtime. For general patterns without zones, see my guide on how to format dates in TypeScript.

Why does my date show the wrong time zone?

When you leave out timeZone, every method falls back to the zone of whatever computer runs the code. That’s the user’s laptop in a browser, and the server’s setting in Node.js. Here’s the same order timestamp printed several ways:

const order = new Date("2026-10-16T03:30:00Z");

console.log("runtime zone:  ", Intl.DateTimeFormat().resolvedOptions().timeZone);
console.log("toString():    ", order.toString());
console.log("getHours():    ", order.getHours());
console.log("toISOString(): ", order.toISOString());
console.log("no timeZone:   ", order.toLocaleString("en-US"));
console.log("New York:      ", order.toLocaleString("en-US", { timeZone: "America/New_York" }));
console.log("Los Angeles:   ", order.toLocaleString("en-US", { timeZone: "America/Los_Angeles" }));
console.log("LA date only:  ", order.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles" }));

Output:

runtime zone:   America/Chicago
toString():     Thu Oct 15 2026 22:30:00 GMT-0500 (Central Daylight Time)
getHours():     22
toISOString():  2026-10-16T03:30:00.000Z
no timeZone:    10/15/2026, 10:30:00 PM
New York:       10/15/2026, 11:30:00 PM
Los Angeles:    10/15/2026, 8:30:00 PM
LA date only:   10/15/2026
Command Prompt output comparing TypeScript Date toString, getHours, toISOString and toLocaleString with and without a timeZone option
Without timeZone, the output follows the machine (Central here); with it, the result is the same on every computer.

Notice the date flips too: 10:30 PM on October 15 in Chicago is 8:30 PM the same evening in Los Angeles, but already October 16 in UTC. That’s the classic off-by-one bug in date-only displays.

My rule: store and send dates as UTC with toISOString(), and pick the zone only at display time. If dates arrive as strings first, read how to convert a string to a date in TypeScript to parse them safely.

Methods such as getHours() and getDate() always use the machine zone. There’s no argument to change that, so don’t build zone logic on them, including when you get the week number from a date in TypeScript.

Format a date as an ISO string with a time zone offset

toISOString() only ever gives you UTC with a Z. APIs and logs often want local time plus an offset, like 2026-10-16T11:00:00-04:00. formatToParts() hands you each piece so you can assemble any layout:

type Parts = Record<string, string>;

function partsInZone(date: Date, timeZone: string): Parts {
  const fmt = new Intl.DateTimeFormat("en-US", {
    timeZone,
    year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", second: "2-digit",
    hourCycle: "h23",
    timeZoneName: "longOffset",          // "GMT-04:00"
  });
  const parts: Parts = {};
  for (const { type, value } of fmt.formatToParts(date)) parts[type] = value;
  return parts;
}

function isoInZone(date: Date, timeZone: string): string {
  const p = partsInZone(date, timeZone);
  const offset = p.timeZoneName === "GMT" ? "+00:00" : p.timeZoneName.slice(3);
  return `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}:${p.second}${offset}`;
}

function abbrInZone(date: Date, timeZone: string): string {
  return new Intl.DateTimeFormat("en-US", { timeZone, timeZoneName: "short" })
    .formatToParts(date)
    .find((p) => p.type === "timeZoneName")?.value ?? "";
}

function stampInZone(date: Date, timeZone: string): string {
  const p = partsInZone(date, timeZone);
  return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute} ${abbrInZone(date, timeZone)}`;
}

const midnightEastern = new Date("2026-10-17T04:00:00Z");
for (const tz of ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "UTC"]) {
  console.log(tz.padEnd(20), isoInZone(midnightEastern, tz).padEnd(26), stampInZone(midnightEastern, tz));
}

Output:

America/New_York     2026-10-17T00:00:00-04:00  2026-10-17 00:00 EDT
America/Chicago      2026-10-16T23:00:00-05:00  2026-10-16 23:00 CDT
America/Denver       2026-10-16T22:00:00-06:00  2026-10-16 22:00 MDT
America/Los_Angeles  2026-10-16T21:00:00-07:00  2026-10-16 21:00 PDT
UTC                  2026-10-17T04:00:00+00:00  2026-10-17 04:00 UTC
Command Prompt output of a TypeScript formatToParts helper printing an ISO 8601 date with offset and a time zone abbreviation for US zones
Midnight in New York is still October 16 in Chicago, Denver and Los Angeles, and each line carries its own offset.

Two details make this helper reliable:

  • hourCycle: "h23" prints midnight as 00 rather than 24.
  • timeZoneName: "longOffset" returns GMT-04:00, which I trim to -04:00.

Zero-padding comes free from "2-digit"; for other numbers, see adding leading zeros to a number in TypeScript.

UTC is the one zone that reports plain GMT with no digits, so the helper maps it to +00:00. The same "2-digit" idea is explained in formatting numbers as two digits in TypeScript.

How daylight saving time changes the time zone offset

US offsets aren’t fixed. In 2026, daylight saving time starts on March 8 and ends on November 1, and a few places, like most of Arizona, never switch. This checks the offset around both dates:

const offsetFmt = (timeZone: string) =>
  new Intl.DateTimeFormat("en-US", { timeZone, timeZoneName: "shortOffset" });

const checkDays = ["2026-03-07", "2026-03-09", "2026-10-31", "2026-11-02"];

for (const tz of ["America/Chicago", "America/Phoenix"]) {
  const fmt = offsetFmt(tz);
  const row = checkDays.map((d) => {
    const noonUtc = new Date(`${d}T18:00:00Z`);
    const zone = fmt.formatToParts(noonUtc).find((p) => p.type === "timeZoneName")?.value;
    return `${d.slice(5)} ${zone}`;
  });
  console.log(tz.padEnd(16), row.join(" | "));
}

Output:

America/Chicago  03-07 GMT-6 | 03-09 GMT-5 | 10-31 GMT-5 | 11-02 GMT-6
America/Phoenix  03-07 GMT-7 | 03-09 GMT-7 | 10-31 GMT-7 | 11-02 GMT-7

Chicago moves from GMT-6 to GMT-5 and back, while Phoenix stays at GMT-7 all year. Never hard-code “-05:00” for Central time; let the formatter look it up. DST also explains odd 23-hour days when you calculate the date difference in days in TypeScript.

Fix TypeScript errors when you format a date with a time zone

The options object is fully typed in the es2024 library, so a wrong value for timeZoneName fails at compile time. Here I guessed “abbr” instead of “short”:

const shipped = new Date("2026-10-16T21:15:00Z");

const fmt = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/Denver",
  hour: "numeric",
  minute: "2-digit",
  timeZoneName: "abbr",
});
console.log(fmt.format(shipped));

Output:

tz_typo.ts(7,3): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Type '"abbr"' is not assignable to type '"long" | "longGeneric" | "longOffset" | "short" | "shortGeneric" | "shortOffset" | undefined'.
Command Prompt output of the TypeScript compiler error TS2769 for an invalid timeZoneName option in Intl.DateTimeFormat
TS2769 (No overload matches this call) lists every allowed value: short, long, shortOffset, longOffset, shortGeneric and longGeneric.

The timeZone option, however, is just a string. TypeScript can’t tell that “Central Standard Time” isn’t a real zone, so the mistake only shows up at runtime as a RangeError. Check user input with a small guard:

function isValidTimeZone(timeZone: string): boolean {
  try {
    new Intl.DateTimeFormat("en-US", { timeZone });
    return true;
  } catch {
    return false;
  }
}

for (const tz of ["America/Denver", "Central Standard Time", "US/Pacific", "EST"]) {
  console.log(tz.padEnd(22), isValidTimeZone(tz));
}

try {
  new Date("2026-10-16T15:00:00Z").toLocaleString("en-US", { timeZone: "Eastern Time" });
} catch (err) {
  console.log((err as Error).name + ":", (err as Error).message);
}

const usZones = Intl.supportedValuesOf("timeZone").filter((z) => z.startsWith("America/"));
console.log("America/* zones this runtime knows:", usZones.length);

Output:

America/Denver         true
Central Standard Time  false
US/Pacific             true
EST                    true
RangeError: Invalid time zone specified: Eastern Time
America/* zones this runtime knows: 144

Old aliases like US/Pacific and EST still work, but I’d stick to the America/... names. If you’d rather catch typos at compile time, a union of the zones you support works well; see my TypeScript key value pair guide for mapping labels to zone names.

Convert a local time in a US time zone to a UTC Date

Formatting goes from an instant to text. Sometimes you need the reverse: a user in Denver picks 4:30 PM on November 3, and you must store the correct UTC moment. The built-in API has no direct method, but the offset helper does the job:

type Parts = Record<string, string>;

function partsInZone(date: Date, timeZone: string): Parts {
  const fmt = new Intl.DateTimeFormat("en-US", {
    timeZone,
    year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", second: "2-digit",
    hourCycle: "h23",
    timeZoneName: "longOffset",          // "GMT-04:00"
  });
  const parts: Parts = {};
  for (const { type, value } of fmt.formatToParts(date)) parts[type] = value;
  return parts;
}

function isoInZone(date: Date, timeZone: string): string {
  const p = partsInZone(date, timeZone);
  const offset = p.timeZoneName === "GMT" ? "+00:00" : p.timeZoneName.slice(3);
  return `${p.year}-${p.month}-${p.day}T${p.hour}:${p.minute}:${p.second}${offset}`;
}

function offsetMinutes(date: Date, timeZone: string): number {
  const iso = isoInZone(date, timeZone);
  const sign = iso.at(-6) === "-" ? -1 : 1;
  return sign * (Number(iso.slice(-5, -3)) * 60 + Number(iso.slice(-2)));
}

// "4:30 PM on Nov 3, 2026 in Denver" -> the real instant
function fromZonedTime(y: number, mo: number, d: number, h: number, mi: number, timeZone: string): Date {
  const guess = Date.UTC(y, mo - 1, d, h, mi);
  const first = guess - offsetMinutes(new Date(guess), timeZone) * 60_000;
  return new Date(guess - offsetMinutes(new Date(first), timeZone) * 60_000);
}

const meeting = fromZonedTime(2026, 11, 3, 16, 30, "America/Denver");
console.log("stored as UTC:", meeting.toISOString());
console.log("Denver:      ", isoInZone(meeting, "America/Denver"));
console.log("New York:    ", isoInZone(meeting, "America/New_York"));

Output:

stored as UTC: 2026-11-03T23:30:00.000Z
Denver:       2026-11-03T16:30:00-07:00
New York:     2026-11-03T18:30:00-05:00

It guesses, measures the offset at that guess, and corrects once more so DST boundaries land right. With the UTC value stored, you can compare dates in TypeScript or sort an array of dates in TypeScript without zone surprises.

Do you need a library to format dates with time zones?

For display, Intl.DateTimeFormat covers almost everything, and it ships with Node.js and every modern browser. A library earns its place when you do heavy zone math or want format tokens like yyyy-MM-dd HH:mm zzz.

Popular picks are date-fns-tz (formatInTimeZone), the newer @date-fns/tz package for date-fns 4, and Luxon. Moment Timezone still works, but Moment itself is in maintenance mode, so I wouldn’t start a new project on it.

NeedUseWatch out for
Show a date in one US zonetoLocaleString("en-US", { timeZone })Leaving out timeZone
Reusable formatter, many datesnew Intl.DateTimeFormat(...)Mixing dateStyle with field options
ISO string with offsetformatToParts() + longOffsetUTC reports plain GMT
Abbreviation like EDTtimeZoneName: "short"Non-US zones may show GMT+1
Storage and APIstoISOString()Always UTC, never local

Once your zones are sorted, these TypeScript posts help with the data around your dates:

Frequently asked questions

How do I format a date in a specific time zone in TypeScript?

Pass an IANA name as timeZone: date.toLocaleString("en-US", { timeZone: "America/Chicago" }). The same option works with Intl.DateTimeFormat.

How do I show EST, CST or PST next to the time?

Add timeZoneName: "short". It prints the right abbreviation for the date, so you’ll see EDT in summer and EST in winter.

Why is toISOString() in UTC?

By design it always returns UTC with a trailing Z. Build a local string with an offset using formatToParts() and longOffset.

How do I get the user’s time zone?

Call Intl.DateTimeFormat().resolvedOptions().timeZone. In a browser that’s the visitor’s zone; on a server it’s the machine’s setting.

Can I change the time zone of a Date object?

No. A Date is a UTC instant with no zone. You choose the zone each time you format it.

Where are all the Intl.DateTimeFormat options documented?