@absoluteui/helpers/timezone
IANA time-zone conversion, offsets, and explicit daylight-saving gap and overlap handling.
import { ... } from '@absoluteui/helpers/timezone';How to use
Section titled “How to use”Convert between exact instants and wall-clock times in IANA time zones, with an explicit decision whenever daylight saving skips or repeats a local time. Reach for it when people in different offices see the same event, when a user picks a local time that must become an instant, or when you store times with their offset. Offsets come from the browser’s Intl data. The Dates and time zones guide explains the value types and the disambiguation policy; the Business calendars guide covers working hours per office.
How do I show a meeting time in each attendee’s zone?
Section titled “How do I show a meeting time in each attendee’s zone?”Turn the organiser’s wall-clock time into one instant with zonedToInstant, then show that instant in every attendee’s zone. Combine it with each office’s business calendar to flag attendees for whom the slot is outside working hours or on a non-working day.
import { formatPlainDateTime, plainDateTime, plainTime } from '@absoluteui/helpers/date';import { formatInZone, instantToZoned, zonedToInstant } from '@absoluteui/helpers/timezone';import { createBusinessCalendar, fixedHoliday, isWorkingTime } from '@absoluteui/helpers/business-calendar';
const meeting = zonedToInstant(plainDateTime('2026-10-02', plainTime(9, 0)), 'America/New_York');const offices = [ { name: 'New York', cal: createBusinessCalendar({ timeZone: 'America/New_York' }) }, { name: 'London', cal: createBusinessCalendar({ timeZone: 'Europe/London' }) }, { name: 'Bengaluru', cal: createBusinessCalendar({ timeZone: 'Asia/Kolkata', holidays: [fixedHoliday('Gandhi Jayanti', 10, 2)] }) },];const view = offices.map((o) => ({ office: o.name, local: formatPlainDateTime(instantToZoned(meeting, o.cal.timeZone)), working: isWorkingTime(meeting, o.cal) }));view; // => [{ office: 'New York', local: '2026-10-02T09:00:00', working: true }, { office: 'London', local: '2026-10-02T14:00:00', working: true }, { office: 'Bengaluru', local: '2026-10-02T18:30:00', working: false }]formatInZone(meeting, { timeZone: 'Asia/Kolkata', locale: 'en-IN', timeStyle: 'short' }); // => '6:30 pm'Bengaluru is out twice over: 18:30 is after hours, and 2 October is a holiday there.
How do I handle a local time that daylight saving skips or repeats?
Section titled “How do I handle a local time that daylight saving skips or repeats?”Check the picked time with getWallTimeStatus before converting. For a skipped time (spring forward) or a repeated one (fall back), either let the user choose or apply a policy: compatible (the default, as in Temporal), earlier, later, or reject.
import { plainDateTime, plainTime } from '@absoluteui/helpers/date';import { formatZonedIso, getPossibleInstants, getWallTimeStatus, zonedToInstant } from '@absoluteui/helpers/timezone';
const zone = 'America/New_York';const skipped = plainDateTime('2026-03-08', plainTime(2, 30));const repeated = plainDateTime('2026-11-01', plainTime(1, 30));getWallTimeStatus(skipped, zone); // => 'nonexistent'formatZonedIso(zonedToInstant(skipped, zone), zone); // => '2026-03-08T03:30:00-04:00' // moved forward by the gap
getWallTimeStatus(repeated, zone); // => 'ambiguous'getPossibleInstants(repeated, zone).map((i) => formatZonedIso(i, zone)); // => ['2026-11-01T01:30:00-04:00', '2026-11-01T01:30:00-05:00'] // offer bothformatZonedIso(zonedToInstant(repeated, zone, { disambiguation: 'later' }), zone); // => '2026-11-01T01:30:00-05:00'zonedToInstant(skipped, zone, { disambiguation: 'reject' }); // => throws HLP_TZ_NONEXISTENT_TIMEHow do I store and exchange a time without losing the zone?
Section titled “How do I store and exchange a time without losing the zone?”Send instants as RFC 3339 text with the offset (formatZonedIso), and keep the IANA zone next to it when the local time matters later, for example for a recurring shift. parseInstant reads such text back and rejects text without an offset.
import { formatPlainDateTime, parseInstant } from '@absoluteui/helpers/date';import { formatZonedIso, instantToZoned } from '@absoluteui/helpers/timezone';
const shiftStart = Date.UTC(2026, 9, 5, 3, 30);const payload = { startsAt: formatZonedIso(shiftStart, 'Asia/Kolkata'), timeZone: 'Asia/Kolkata' };payload.startsAt; // => '2026-10-05T09:00:00+05:30'parseInstant(payload.startsAt) === shiftStart; // => trueformatPlainDateTime(instantToZoned(shiftStart, 'Europe/Berlin')); // => '2026-10-05T05:30:00'parseInstant('2026-10-05T09:00:00'); // => undefined // no offset: not an instantHow do I build a time-zone picker and warn about clock changes?
Section titled “How do I build a time-zone picker and warn about clock changes?”listTimeZones gives the zones the runtime supports (current names such as Asia/Kolkata), and timeZoneName a localized label. nextTransition finds the next offset change, useful to warn that a recurring meeting will move for some attendees.
import { formatZonedIso, isValidTimeZone, listTimeZones, nextTransition, timeZoneName } from '@absoluteui/helpers/timezone';
const options = listTimeZones().filter((z) => z.startsWith('Europe/')).map((z) => ({ value: z, label: timeZoneName(z, { locale: 'en', style: 'longGeneric' }) }));options.some((o) => o.value === 'Europe/London'); // => trueisValidTimeZone('Mars/Olympus_Mons'); // => false
const change = nextTransition(Date.UTC(2026, 8, 30), 'Europe/London');change === undefined ? undefined : formatZonedIso(change, 'Europe/London'); // => '2026-10-25T01:00:00+00:00' // clocks go backnextTransition(Date.UTC(2026, 8, 30), 'Asia/Kolkata'); // => undefined // no daylight savingWhat goes wrong?
Section titled “What goes wrong?”- The device zone is not the user’s work zone: pass zones explicitly and use
getLocalTimeZone()only as a default. - A day is not always 24 hours. Add calendar days with
addDurationInZonefrom@absoluteui/helpers/duration, not+ 86_400_000. - “Which business day is it?” depends on the office’s zone: use
businessDayAtwith the calendar’stimeZone, not the UTC date.
import { instantToZoned } from '@absoluteui/helpers/timezone';import { businessDayAt, createBusinessCalendar } from '@absoluteui/helpers/business-calendar';
instantToZoned(Date.UTC(2026, 9, 1), 'Not/AZone'); // => throws HLP_TZ_INVALID_ZONEconst lateFriday = Date.UTC(2026, 9, 2, 20); // Friday 20:00 UTCbusinessDayAt(lateFriday, createBusinessCalendar({ timeZone: 'Asia/Kolkata' })).working; // => false // already Saturday in KolkataAPI reference
Section titled “API reference”General
Section titled “General”Functions
Section titled “Functions”convertWallTime
Converts a wall-clock time in one zone to the wall-clock time in another.
function convertWallTime(dateTime: PlainDateTime, fromTimeZone: string, toTimeZone: string, options?: ZonedToInstantOptions): PlainDateTime;Example:
import { plainDateTime, plainTime } from '@absoluteui/helpers/date';convertWallTime(plainDateTime('2026-09-29', plainTime(9, 0)), 'Asia/Kolkata', 'America/New_York'); // => '2026-09-28T23:30:00'formatInZone
Formats an instant in a zone with a cached Intl.DateTimeFormat. Default
{ dateStyle: 'medium', timeStyle: 'short' }.
function formatInZone(instant: InstantInput, options?: ZonedFormatOptions): string;Example:
formatInZone(Date.UTC(2026, 0, 1), { timeZone: 'Asia/Tokyo', locale: 'en', timeStyle: 'short' }) // => '9:00 AM'formatOffset
Formats an offset in milliseconds as +/-HH:MM (with :SS when seconds are non-zero, as for
historic local mean time). Rounds to whole seconds; zero is +00:00.
function formatOffset(offsetMs: number): string;Example:
formatOffset(19_800_000) // => '+05:30'formatZonedIso
RFC 3339 / ISO 8601 string of an instant as wall time in a zone with its offset,
for example 2026-03-08T03:00:00-04:00.
function formatZonedIso(instant: InstantInput, timeZone?: string): string;Example:
formatZonedIso(Date.UTC(2026, 2, 8, 7), 'America/New_York') // => '2026-03-08T03:00:00-04:00'isDstAt
True when the zone observes daylight saving time at the instant: its offset is larger than
standardOffset for that year. (Zones with “negative DST” such as Europe/Dublin report summer
as DST, matching common usage; Africa/Casablanca reports its Ramadan offset as non-DST.)
function isDstAt(instant: InstantInput, timeZone?: string): boolean;Example:
isDstAt(Date.UTC(2026, 6, 1), 'Europe/London') // => truelistTimeZones
Zone identifiers the runtime supports (Intl.supportedValuesOf('timeZone')), otherwise a
small fallback list of common zones. Legacy ICU names are replaced by current IANA names
(Asia/Calcutta -> Asia/Kolkata) when the runtime accepts them, unless modernNames: false.
Always includes UTC. Sorted, without duplicates.
function listTimeZones(options?: { readonly modernNames?: boolean;}): string[];Example:
listTimeZones().includes('Asia/Kolkata') // => trueparseOffset
Parses Z or +/-HH:MM[:SS] to milliseconds; undefined when invalid.
function parseOffset(text: string): number | undefined;Example:
parseOffset('-04:00') // => -14400000standardOffset
Standard (non-DST) offset of a zone in a year: the smaller of the January 1 and July 1 offsets.
function standardOffset(year: number, timeZone?: string): number;Example:
standardOffset(2026, 'America/New_York') // => -18000000timeZoneName
Localized display name of a zone.
function timeZoneName(timeZone: string, options?: TimeZoneNameOptions): string;Example:
timeZoneName('Asia/Kolkata', { locale: 'en' }) // => 'India Standard Time'Example:
timeZoneName('America/New_York', { locale: 'en', at: Date.UTC(2026, 6, 1) }); // => 'Eastern Daylight Time'timeZoneName('America/New_York', { locale: 'en', style: 'longGeneric', at: Date.UTC(2026, 0, 1) }); // => 'Eastern Time'Interfaces
Section titled “Interfaces”TimeZoneNameOptions
interface TimeZoneNameOptions { readonly locale?: string; readonly style?: 'long' | 'short' | 'shortOffset' | 'longOffset' | 'shortGeneric' | 'longGeneric'; readonly at?: InstantInput;}ZonedFormatOptions
Intl.DateTimeFormat options plus the zone and locale.
type ZonedFormatOptions = Omit<Intl.DateTimeFormatOptions, 'timeZone'> & { readonly timeZone?: string; readonly locale?: string;};Related types
Section titled “Related types”Functions
Section titled “Functions”getLocalTimeZone
The runtime’s IANA time zone (for example 'Europe/London').
function getLocalTimeZone(): string;Example:
getLocalTimeZone() // e.g. 'Asia/Kolkata' (depends on the device)getPossibleInstants
Every instant at which a wall-clock time occurs: one (normal), two (overlap, clocks fell back), or none (gap, clocks sprang forward). Sorted ascending, epoch milliseconds.
function getPossibleInstants(dateTime: PlainDateTime, timeZone?: string): number[];Example:
import { plainDateTime, plainTime } from '@absoluteui/helpers/date';getPossibleInstants(plainDateTime('2026-11-01', plainTime(1, 30)), 'America/New_York'); // => [Date.UTC(2026, 10, 1, 5, 30), Date.UTC(2026, 10, 1, 6, 30)] // EDT, then ESTgetPossibleInstants(plainDateTime('2026-03-08', plainTime(2, 30)), 'America/New_York'); // => [] // skippedgetWallTimeStatus
'exact', 'ambiguous' (overlap), or 'nonexistent' (gap) for a wall-clock time in a zone.
function getWallTimeStatus(dateTime: PlainDateTime, timeZone?: string): 'exact' | 'ambiguous' | 'nonexistent';Example:
import { plainDateTime, plainTime } from '@absoluteui/helpers/date';getWallTimeStatus(plainDateTime('2026-03-08', plainTime(2, 30)), 'America/New_York'); // => 'nonexistent'getWallTimeStatus(plainDateTime('2026-11-01', plainTime(1, 30)), 'America/New_York'); // => 'ambiguous'getWallTimeStatus(plainDateTime('2026-11-01', plainTime(9, 0)), 'America/New_York'); // => 'exact'instantToZoned
Wall-clock date-time of an instant in timeZone (default: runtime zone).
function instantToZoned(instant: InstantInput, timeZone?: string): PlainDateTime;Example:
instantToZoned(Date.UTC(2026, 0, 1), 'Asia/Kolkata') // => '2026-01-01T05:30:00'isValidTimeZone
True when timeZone is an identifier the runtime’s Intl accepts (IANA names, UTC,
and on newer engines fixed offsets such as '+05:30').
function isValidTimeZone(timeZone: string): boolean;Example:
isValidTimeZone('Asia/Kolkata') // => truenextTransition
Next UTC-offset change strictly after instant (epoch ms of the first instant with the new
offset), or undefined within searchDays. Scans day by day, then binary-searches to the
second, so two changes less than a day apart can be missed.
function nextTransition(instant: InstantInput, timeZone?: string, options?: TransitionSearchOptions): number | undefined;Example:
nextTransition(Date.UTC(2026, 0, 1), 'America/New_York'); // => 1772953200000 // 2026-03-08T07:00:00Z, clocks spring forwardnextTransition(Date.UTC(2026, 0, 1), 'Asia/Kolkata'); // => undefined // no DSToffsetAt
UTC offset in milliseconds of timeZone (default: runtime zone) at an instant.
function offsetAt(instant: InstantInput, timeZone?: string): number;Example:
offsetAt(Date.UTC(2026, 0, 1), 'Asia/Kolkata') // => 19800000previousTransition
Latest UTC-offset change strictly before instant, or undefined within searchDays.
function previousTransition(instant: InstantInput, timeZone?: string, options?: TransitionSearchOptions): number | undefined;Example:
const t = previousTransition(Date.UTC(2026, 5, 1), 'America/New_York'); // => 1772953200000t === undefined ? undefined : new Date(t).toISOString(); // => '2026-03-08T07:00:00.000Z'zonedToInstant
Converts a wall-clock date-time in timeZone (default: runtime zone) to epoch milliseconds.
| Case | compatible | earlier | later | reject |
|---|---|---|---|---|
| overlap | earlier instant | earlier instant | later instant | throws |
| gap | forward by gap | backward by gap | forward by gap | throws |
function zonedToInstant(dateTime: PlainDateTime, timeZone?: string, options?: ZonedToInstantOptions): number;Example:
import { plainDateTime, plainTime } from '@absoluteui/helpers/date';const gap = plainDateTime('2026-03-08', plainTime(2, 30)); // skipped in New YorkzonedToInstant(gap, 'America/New_York'); // => Date.UTC(2026, 2, 8, 7, 30) // 03:30 EDTzonedToInstant(gap, 'America/New_York', { disambiguation: 'earlier' }); // => Date.UTC(2026, 2, 8, 6, 30) // 01:30 ESTzonedToInstant(gap, 'America/New_York', { disambiguation: 'reject' }); // => throws HLP_TZ_NONEXISTENT_TIMEInterfaces
Section titled “Interfaces”TransitionSearchOptions
interface TransitionSearchOptions { readonly searchDays?: number;}ZonedToInstantOptions
interface ZonedToInstantOptions { readonly disambiguation?: Disambiguation;}Disambiguation
How a wall-clock time that is skipped (gap) or repeated (overlap) maps to an instant.
type Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject';