Get the UTC Offset for a Timezone in JavaScript
To get a zone's UTC offset at a moment, build a Temporal.ZonedDateTime in that zone and read .offset ('-04:00') or .offsetNanoseconds. Part of Timezone Offset Math Explained.
Why this scenario is tricky
Asking "what is the UTC offset for a time zone" contains a hidden assumption that is the source of most offset bugs: that a zone has an offset, singular. It does not. A zone has a rule set that maps each instant to an offset, and the offset changes — twice a year for zones that observe daylight saving, and occasionally when a government rewrites the rules. New York is -05:00 in winter and -04:00 in summer, so "the offset for America/New_York" is only meaningful relative to a specific instant. Any code that stores or hard-codes a single offset for a zone is correct for at most half the year and wrong the other half.
This is why the correct question is always "what is the offset for this zone at this instant." Legacy Date.getTimezoneOffset() only answers it for the host zone and returns the value with an inverted sign (positive for zones behind UTC), which is a perennial source of confusion. Temporal makes the dependency on the instant explicit: you project an instant into the zone and read the offset that applies at that moment, so the answer is always tied to the instant it is valid for. The trickiness is entirely in the assumption that an offset is a fixed property of a zone; once you tie it to an instant, it becomes straightforward.
An offset is not a property of a zone — it is a property of a zone at an instant, because DST moves it twice a year. Asking "what is New York's offset" is meaningless without a date; America/New_York is -05:00 in January and -04:00 in July. Read the offset off a zoned instant, not a lookup table.
Minimal working solution
instant.toZonedDateTimeISO(zone).offset gives the offset string ('-04:00' or '-05:00') that applies in that zone at that instant. The key is that you start from an instant — a specific moment — because that is the only thing that determines which side of a daylight-saving transition you are on. Reading the offset off the resulting ZonedDateTime returns the correct value for that moment, and it will differ for the same zone at a different instant, exactly as it should.
Using Temporal.Now.instant() as the instant gives you the current offset for a zone, which is the common case for "what's the offset in Tokyo right now." But be deliberate: if you need the offset that applied to a historical event, or that will apply to a future scheduled time, use that instant, not now. The offset is a function of the instant, so feeding it the wrong instant gives the wrong offset, and the mistake is invisible until the code runs on a date on the other side of a transition.
import { Temporal } from '@js-temporal/polyfill';
const zdt = Temporal.Now.instant().toZonedDateTimeISO('America/New_York');
zdt.offset; // '-04:00' or '-05:00' depending on the date
Full production version
A production helper takes both the instant and the zone as explicit parameters and returns the offset, making it clear at every call site which moment the offset is valid for. You might expose the offset as a string ('-04:00') for display, or as a number of minutes for arithmetic, or as offsetNanoseconds for precise calculations — but whichever form, it is always paired with the instant that produced it. For features that show "current offset" widgets or build a zone picker labelled with offsets, recompute the offsets for the current instant rather than caching them, because a cached offset silently goes stale at the next transition.
The deeper production lesson is to prefer storing the IANA zone identifier over storing an offset at all. An offset is a lossy snapshot; the zone is the full rule set. If you persist America/New_York you can compute the correct offset for any instant, past or future, including after a rule change; if you persist -05:00 you have thrown away the ability to recompute and will be wrong for half the year. Reach for the raw offset only for display or for a specific arithmetic step, and keep the zone as the durable record — the same discipline that governs storing zoned times generally.
import { Temporal } from '@js-temporal/polyfill';
function offsetAt(instant: Temporal.Instant, zone: string) {
const z = instant.toZonedDateTimeISO(zone);
return { text: z.offset, minutes: z.offsetNanoseconds / 60e9 };
}
Verification snippet
Common pitfalls
The first pitfall is treating a zone as having one fixed offset, hard-coding -05:00 for New York, which is wrong during daylight time. Always compute the offset for a specific instant through the zone. The second is using Date.getTimezoneOffset() and forgetting its sign is inverted (it returns positive minutes for zones behind UTC), producing arithmetic that is backwards. Temporal's offset string has the conventional sign, removing the confusion. The third is reading the offset for the wrong instant — using "now" when you needed the offset at a historical or future moment — which silently gives the wrong side of a transition.
A fourth pitfall is caching or persisting an offset and reusing it after a transition, when it has become stale; recompute from the zone and the relevant instant, or better, persist the zone and derive the offset on demand. Finally, remember that some zones have non-whole-hour offsets (India at +05:30, Nepal at +05:45) and have changed their rules historically, so any logic that assumes offsets are whole hours or constant over time will eventually meet a counterexample. Deriving the offset from the IANA data for the specific instant handles all of these without special cases.
Frequently Asked Questions
How do I get the UTC offset of a time zone in JavaScript?
Build a Temporal.ZonedDateTime for the instant you care about in that zone and read its offset ('-04:00') or offsetNanoseconds. The offset must be tied to an instant because daylight saving changes it during the year.
Why can't I store a single UTC offset for a time zone?
Because a zone has different offsets at different times — New York is -05:00 in winter and -04:00 in summer — so a stored number goes stale at the next DST transition. Store the IANA identifier and compute the offset per instant.
How do I get the UTC offset for a time zone in JavaScript?
Project an instant into the zone and read its offset: instant.toZonedDateTimeISO(zone).offset gives a string like '-04:00' or '-05:00'. Use Temporal.Now.instant() for the current offset, or a specific instant for a historical or future moment. The offset must be tied to an instant because it changes across daylight-saving transitions — a zone has a rule set, not a single fixed offset.
Why does a time zone not have a single fixed offset?
Because a zone is a rule set mapping each instant to an offset, and that offset changes — twice a year for zones observing daylight saving, and occasionally when governments rewrite the rules. New York is -05:00 in winter and -04:00 in summer, so 'the offset for New York' is only meaningful for a specific instant. Hard-coding one offset is correct for at most half the year; always compute it for the instant you care about.
Why is Date.getTimezoneOffset() confusing?
Because it only reports the host machine's zone, and it returns the value with an inverted sign — positive minutes for zones behind UTC — so New York in winter reports +300, not -300. That sign convention trips up arithmetic constantly. Temporal's offset string uses the conventional sign ('-05:00') and works for any zone at any instant, not just the host, removing both problems.