Time Zones
This guide covers how to display events in a specific IANA time zone and switch the displayed zone at runtime while keeping the underlying instants stable.
The functionality is available in PRO Edition only
PROHow time zones work
The calendar has no time-zone prop. It stores and renders ordinary JavaScript Date objects, and a Date carries no attached IANA zone - only an epoch value and the local getters (getHours(), getDate(), and so on) that read it in the runtime's zone.
To show events in another zone, you convert the dates before handing them to the widget. PRO builds re-export two projection helpers for this from @svar-ui/react-calendar:
import { toTimeZone, fromTimeZone } from "@svar-ui/react-calendar";
toTimeZone(date: Date, timeZone: string): Date;
fromTimeZone(date: Date, timeZone: string): Date;
The wall-clock carrier model
toTimeZone(date, zone) returns a shifted Date whose local getters show the target zone's wall clock. A 9:00 meeting in America/New_York comes back as a Date that reads 9:00 from getHours() in the runtime's own zone. The returned epoch is not the original instant - the value is a wall-clock carrier the calendar can render, not a real point in time.
fromTimeZone(date, zone) is the inverse. It treats the carrier's local fields as wall time in zone and returns the matching instant.
const projected = toTimeZone(instant, "America/New_York");
const restored = fromTimeZone(projected, "America/New_York");
// for ordinary, unambiguous times, restored is the same instant as `instant`
Because the calendar only ever holds carriers while a zone is displayed, any date the user creates or edits in the grid is also a carrier. Interpret it with fromTimeZone() before you persist a true instant.
Displaying events in a zone
Project each event's start and end into the target zone before passing them as the events prop:
import { Calendar, toTimeZone } from "@svar-ui/react-calendar";
const zone = "America/New_York";
const events = rawEvents.map(ev => ({
...ev,
start: toTimeZone(ev.start, zone),
end: toTimeZone(ev.end, zone),
}));
<Calendar events={events} view="week" />
Here rawEvents holds real instants and the mapped array holds carriers for New York wall time.
Switching the displayed zone
To change the displayed zone without shifting event instants, first interpret the current carriers in the old zone to recover their instants, then project those instants into the new zone:
function reproject(events, fromZone, toZone) {
return events.map(event => ({
...event,
start: toTimeZone(fromTimeZone(event.start, fromZone), toZone),
end: toTimeZone(fromTimeZone(event.end, fromZone), toZone),
}));
}
Your application owns this transformation and replaces the events prop with the result. Read the current data - including any edits the user made in the grid - through api.getEvents() so those edits survive the switch:
const [data, setData] = useState(initialEvents);
const [zone, setZone] = useState("America/New_York");
function changeZone(next) {
if (next === zone) return;
setData(reproject(api.getEvents(), zone, next));
setZone(next);
}
Treat the runtime's own zone (Intl.DateTimeFormat().resolvedOptions().timeZone) as just another entry in the list. Local-to-local is a no-op, so a "Local Time" option needs no special case.
Offset and DST lookup
For lower-level work - computing an offset or finding the annual DST transitions of a zone - @svar-ui/calendar-store exports getOffset and getTimeZone. These are not re-exported by the widget package, so import them from the store directly:
import { getOffset, getTimeZone } from "@svar-ui/calendar-store";
getOffset(date, "America/New_York"); // → { offset, utc }
getTimeZone(2026, "America/New_York"); // → { offset, dts }
getOffset returns the zone's offset for a given instant. The sign follows Date#getTimezoneOffset() - west of UTC is positive (America/New_York in winter is 300), east of UTC is negative (Europe/Berlin in winter is -60). getTimeZone describes a year as an outer offset plus a single middle DST interval (dts), or dts: null for zones with no transitions.
Limitations
- DST edges are not disambiguated. Ambiguous wall times during a fall-back transition and nonexistent wall times during a spring-forward transition have no explicit option to resolve them.
- Recurring expansion stays UTC-field based. The recurring engine is not made zone-aware by these helpers. You decide whether recurring data represents fixed instants or wall-clock schedules.
- Platform requirements. Zone ids are passed straight to
Intl.DateTimeFormat; invalid ids throw, and the runtime must supporttimeZoneName: "longOffset". - The annual model covers one middle interval. Historical or political years with an unusual number of transitions fall outside its full-fidelity contract.