Skip to main content

Timezone support

All seven pickers (DatePicker, RangePicker, TimePicker, DateTimePicker, MonthPicker, YearPicker, WeekPicker) accept a displayTimezone prop. When set, Kalyx interprets the user's input and the displayed value as civil time in that IANA zone while continuing to emit the plain UTC-ISO strings you already store.

This is Kalyx's structural answer to react-datepicker #1018 — the "day off by one" bug that has haunted timezone-sensitive apps for a decade.

The problem in one snippet

Without a deliberate zone, picking "April 15" in Seoul stores April 14:

const picked = new Date(2026, 3, 15); // UTC+9 → "2026-04-14T15:00:00.000Z"
await save(picked.toISOString()); // server stores April 14

With displayTimezone, the same click always stores the same civil day:

<DatePicker displayTimezone="Asia/Seoul" onChange={save}>
<DatePicker.Input />
<DatePicker.Popover>
<DatePicker.Calendar />
</DatePicker.Popover>
</DatePicker>
// click "April 15" → onChange("2026-04-14T15:00:00.000Z")
// which represents Seoul April 15 00:00 exactly

The ISO string is still UTC — but it is the UTC instant that equals civil midnight in the display zone, not civil midnight in the server's runtime zone.

When to use it

SituationRecommendation
Calendar dates for logs, birthdays, anniversaries (single civil day)Set displayTimezone to the user's zone.
Booking slots where a server renders for multiple regionsStore in UTC, render with the customer's displayTimezone.
Times of day in a single regionSet displayTimezone once on the root.
All users in one zone (e.g., a Korean-only product)Setting displayTimezone="Asia/Seoul" makes timezone behavior explicit and safe against server runtime drift.
Never leaves UTC (analytics, audit trails)Leave it unset — Kalyx's default semantics are UTC.

Component-level behavior

<DatePicker displayTimezone="Asia/Seoul" value={iso} onChange={setIso}>
<DatePicker.Input /> {/* formats `iso` as seen in Seoul */}
<DatePicker.Popover>
<DatePicker.Calendar /> {/* highlights today/selected by civil Seoul day */}
</DatePicker.Popover>
</DatePicker>
SurfaceEffect
DatePicker.Input / RangePicker.Input / DateTimePicker.Inputformat runs in displayTimezone.
Calendartoday and isSelected comparisons use civil-day equality in the zone.
onChange on calendar clickThe stored ISO represents civil midnight of the clicked day in the zone.
TimePicker.HourList / MinuteListRead and write time-of-day as observed in the zone (DST-aware).

DST handling

Kalyx delegates to Intl.DateTimeFormat for offset lookups, so transitions are handled correctly for all IANA zones.

// Spring forward 2026-03-08 in America/New_York: 02:00 EST → 03:00 EDT
startOfDayInTimezone('2026-03-08T12:00:00.000Z', 'America/New_York');
// → '2026-03-08T05:00:00.000Z' (EST — midnight is still pre-transition)

startOfDayInTimezone('2026-03-09T12:00:00.000Z', 'America/New_York');
// → '2026-03-09T04:00:00.000Z' (EDT — full day in daylight time)

Low-level helpers

Import from @kalyx/core when you need to do the same math in your own code:

import {
civilMidnightFromUtcDay, // Calendar-cell UTC iso → civil midnight ISO in tz
getTimeInTimezone, // UTC iso → { hours, minutes, seconds } as seen in tz
setTimeInTimezone, // UTC iso + TimeValue → UTC iso with new time in tz
formatInTimezone,
startOfDayInTimezone,
isSameDayInTimezone,
todayInTimezone,
getTimezoneOffsetMinutes,
} from '@kalyx/core';

Migrating from no-timezone code

  1. Decide on the display zone (user preference, account setting, or server-known region).
  2. Add displayTimezone={userZone} on the root. Nothing else changes in the call site.
  3. Run existing tests — they keep passing because the contract (value, onChange with UTC ISO strings) is unchanged; only the content of those strings now has a single, unambiguous meaning.
<DatePicker
value={iso}
onChange={setIso}
+ displayTimezone={user.timezone ?? 'UTC'}
>
<DatePicker.Input />
<DatePicker.Popover>
<DatePicker.Calendar />
</DatePicker.Popover>
</DatePicker>

Gotchas

  • Omitting the prop keeps UTC semantics. Existing code continues to work.
  • Adapter contract unchanged. Custom adapters implementing the DateAdapter interface should honor the timezone?: string parameter on format, isSameDay, startOfDay, and today. The built-in DateFnsAdapter already does.
  • IANA zone only. Offsets like "+09:00" are not supported — use "Asia/Seoul".

Next