Get the Current Date in a Specific Timezone in JavaScript
To get today's date in a user's zone, call Temporal.Now.plainDateISO(timeZone) with their IANA id — never the host's clock. Part of Getting the Current Time with Temporal.Now.
Why this scenario is tricky
"Today" feels like the most basic thing a program can know, and that is exactly why it is so often wrong. A calendar date is not a property of the universe; it is a property of a place. At the same instant it can already be Tuesday in Auckland while it is still Monday in Los Angeles, so "today's date" has no meaning until you say today where. The server almost never runs where the user is — it runs in a datacentre whose clock is set to UTC — so code that reads the date from the host is answering "what day is it in UTC," the right answer to a question nobody asked. The failure is quiet because it only shows up in a slice of the day: for a New York user, a UTC host agrees with their wall calendar from mid-morning to midnight and disagrees for the first several hours of each day, when UTC has ticked over but New York has not. So the bug reproduces only in the evening, only for users far from UTC, and only sometimes — the worst possible combination for catching it in tests.
A server almost always runs in UTC, so new Date().getDate() gives the UTC calendar day — which is yesterday or tomorrow for a large share of the world at any given moment. Computing "today" for a user means reading the clock in the user's zone, not the host's.
Minimal working solution
The whole fix is to make the zone an explicit argument rather than an ambient default. Temporal.Now.plainDateISO(timeZone) takes an IANA identifier and returns the calendar date currently in effect there as a zoneless Temporal.PlainDate, with no host-clock involvement and no offset arithmetic for you to get wrong. The timezone database resolves the current offset — including whatever daylight-saving rule applies today — and reports the resulting civil date. Because the return value is a pure date with no time attached, it is exactly the right shape for "today": you want the day, not a particular instant within it.
import { Temporal } from '@js-temporal/polyfill';
// Pass the user's IANA zone, not the host's.
`Temporal.Now.plainDateISO(timeZone)` cuts straight through this by making the zone an explicit argument. You hand it an IANA identifier — `'America/New_York'`, `'Asia/Tokyo'`, `'Pacific/Kiritimati'` — and it returns the calendar date currently in effect there, as a zoneless `Temporal.PlainDate`. There is no host-clock involvement and no offset arithmetic to get wrong; the timezone database resolves the current offset, including whatever daylight-saving rule is in effect today, and reports the civil date that results. The value it returns is a pure date with no time attached, which is exactly right for "today" — you want the day, not a particular instant within it.
const today = Temporal.Now.plainDateISO('America/New_York');
console.log(today.toString()); // e.g. '2026-07-22'
Full production version
In production the zone is data, and data can be missing or malformed, so the helper validates. Constructing anything zone-aware with a bad identifier throws a RangeError, which you catch to tell "the caller sent garbage" apart from "the caller sent nothing," defaulting to UTC only in the latter case. The pattern that scales across an application is to resolve the user's zone once — detected on the client and persisted against their account — and then thread it into every date computation, so "today," "this month," and "the last 30 days" are all computed in their frame rather than the server's. Returning a PlainDate from the helper keeps that value immune to accidental re-zoning: it is a civil date and stays put no matter where it is later formatted, which is precisely the guarantee you want for something as load-bearing as the current date.
Validate the zone and fall back to UTC when the user's zone is unknown.
In production the zone is data, and data can be missing or malformed, so the helper should validate. Constructing anything zone-aware with a bad identifier throws a RangeError, which you can catch to distinguish "the caller sent us garbage" from "the caller sent us nothing." When the zone is genuinely unknown — a first server render before the client has reported it — defaulting to UTC is a defensible, documented choice, as long as it is a choice and not an accident. The pattern that scales is to resolve the user's zone once, on the client, persist it against their account or in a cookie, and pass it to every date computation from then on, so "today" is always their today.
import { Temporal } from '@js-temporal/polyfill';
function todayIn(zone: string | undefined): Temporal.PlainDate {
try {
return Temporal.Now.plainDateISO(zone ?? 'UTC');
} catch {
throw new RangeError(`Invalid time zone: ${zone}`);
}
}
Verification snippet
The assertions worth writing are the ones that expose the zone dependency. Pick an instant in the window where UTC and a target zone disagree — early morning in New York, which is still "yesterday" there while UTC shows the next date — and assert that plainDateISO('America/New_York') returns the New York date, not the UTC one. Assert that two far-apart zones can return dates a day apart for the same instant, that an invalid zone identifier throws, and that an undefined zone routed through your helper falls back to UTC. Those cases pin down the behaviour that a naive host-clock implementation gets wrong.
Common pitfalls
The headline pitfall is reading the date from the host — new Date().getDate() — on a server, which reports the UTC day and is wrong for a slice of every user's day. The second is trying to derive the date from a raw numeric offset, which breaks the twice-a-year moment daylight saving shifts that offset. The third is converting "today" into an instant too early and storing it, which reintroduces the day-shift when it is read back in another zone. Keep the value as a PlainDate in the user's zone, computed with Temporal.Now.plainDateISO, and attach a zone only at the edges where you genuinely need a moment, such as the start of that day for a query.
The bottom line is a single habit: never ask the host what day it is when you mean the user's day. Pass the user's IANA zone to Temporal.Now.plainDateISO, keep the result as a civil PlainDate, and every downstream feature that turns on "today" — greetings, streaks, daily limits, report windows — lines up with the calendar the user is actually looking at.
Frequently Asked Questions
Why does the server show yesterday's or tomorrow's date?
Servers usually run in UTC, so new Date().getDate() returns the UTC calendar day. For users west or far east of UTC that is a different day, so always compute today with the user's IANA zone via Temporal.Now.plainDateISO.
What if I don't know the user's time zone?
Default to UTC and detect the zone client-side with Intl.DateTimeFormat().resolvedOptions().timeZone, then persist it. Rendering with the user's stored zone gives a correct 'today' on subsequent server renders.
Why does my server show yesterday's or tomorrow's date for some users?
Because it is reading the date from the host clock, which is almost always UTC. For users far from UTC, the UTC calendar date differs from their local one for part of the day, so a server-stamped 'today' is a day off for them during those hours. Compute the date with Temporal.Now.plainDateISO(userZone) using the user's IANA zone instead.
What should I use as the date when I don't yet know the user's time zone?
Default to UTC and treat it as a temporary placeholder. Detect the real zone on the client with Intl.DateTimeFormat().resolvedOptions().timeZone, persist it against the user, and use that stored zone on subsequent server renders. Make the UTC fallback an explicit, documented default rather than an accidental consequence of reading the host clock.
Does Temporal.Now.plainDateISO account for daylight saving time?
Yes. It asks the IANA timezone database for the offset currently in effect in the given zone, including any daylight-saving rule active today, and returns the civil date that results. You never do offset arithmetic yourself, so there is no DST edge case to get wrong.
Why does my server show yesterday's or tomorrow's date for some users?
Because it is reading the date from the host clock, which is almost always UTC. For users far from UTC, the UTC calendar date differs from their local one for part of the day, so a server-stamped today is a day off for them during those hours. Compute the date with Temporal.Now.plainDateISO(userZone) using the user's IANA zone instead.
What should I use as the date when I don't yet know the user's time zone?
Default to UTC as a temporary placeholder, detect the real zone on the client with Intl.DateTimeFormat().resolvedOptions().timeZone, persist it against the user, and use that stored zone on later server renders. Make the UTC fallback an explicit documented default rather than an accident of reading the host clock.
Does Temporal.Now.plainDateISO account for daylight saving time?
Yes. It asks the IANA timezone database for the offset currently in effect in the given zone, including any daylight-saving rule active today, and returns the civil date that results. You never do offset arithmetic yourself, so there is no DST edge case to get wrong.