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

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

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

Two details make this helper reliable:
hourCycle: "h23"prints midnight as00rather than24.timeZoneName: "longOffset"returnsGMT-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'.

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.
| Need | Use | Watch out for |
|---|---|---|
| Show a date in one US zone | toLocaleString("en-US", { timeZone }) | Leaving out timeZone |
| Reusable formatter, many dates | new Intl.DateTimeFormat(...) | Mixing dateStyle with field options |
| ISO string with offset | formatToParts() + longOffset | UTC reports plain GMT |
| Abbreviation like EDT | timeZoneName: "short" | Non-US zones may show GMT+1 |
| Storage and APIs | toISOString() | Always UTC, never local |
Once your zones are sorted, these TypeScript posts help with the data around your dates:
- TypeScript enums explained
- Import a JSON file in TypeScript
- Filter arrays in TypeScript
- Map an array of objects in TypeScript
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?
See the Intl.DateTimeFormat() constructor on MDN.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn