Skip to main content

ISO 8601 UTC strings

Every Kalyx value (value, defaultValue, onChange's argument, every item in a DateRange) is an ISO 8601 UTC string or null. Never a Date object.

type ISODateString = string; // e.g. "2026-04-15T00:00:00.000Z"

Why not Date?​

Date objects are mutable, timezone-ambiguous, and serialize inconsistently. The classic bug:

// User in Seoul picks April 15
const picked = new Date(2026, 3, 15); // "2026-04-14T15:00:00.000Z" in KST 🤦

// Save to DB
await save(picked);

// Read back from DB in a UTC server
new Date(picked.toISOString()).getDate(); // 14: off by one

ISO strings eliminate the ambiguity at the boundary:

<DatePicker
value="2026-04-15T00:00:00.000Z" // always UTC midnight
onChange={(iso) => save(iso)} // always a UTC string
/>

Kalyx guarantees:

  1. Dates are stored as UTC midnight (T00:00:00.000Z) unless a time is explicitly set.
  2. onChange never fires with a Date, only a string or null.
  3. Internal arithmetic uses the DateAdapter which is UTC-safe end to end.

Displaying in local time​

The value is UTC. Display is a rendering concern, handled by displayFormat and locale on each root:

<DatePicker
value={iso}
onChange={setIso}
displayFormat="yyyy-MM-dd"
locale="en-US"
>
<DatePicker.Input />
<DatePicker.Popover>
<DatePicker.Calendar />
</DatePicker.Popover>
</DatePicker>

displayFormat is not a full date-fns format string. The adapter does a literal token substitution over exactly eight tokens (yyyy, MM, dd, HH, mm, ss, M, d) and leaves everything else untouched. Month names are not among them: "MMM d, yyyy" renders "044 15, 2026", because MM and M each match inside MMM. All three shipped adapters share this limit. For a localized month name, format the value yourself with Intl.DateTimeFormat and render your own trigger text.

locale drives the calendar's month and weekday names (and the AM/PM labels on TimePicker), not displayFormat.

Converting to and from Date​

When you must bridge to a Date (for example, in a legacy form library), do it at the edge:

// Date → ISO
const iso = new Date(2026, 3, 15).toISOString();
// → "2026-04-14T15:00:00.000Z" (still KST-affected!)

// Safer: construct a UTC midnight directly
const iso = new Date(Date.UTC(2026, 3, 15)).toISOString();
// → "2026-04-15T00:00:00.000Z"

// ISO → Date
const date = new Date(iso);

For fine-grained conversion Kalyx exports helpers from @kalyx/core:

import { normalizeISO, parseInputValue } from '@kalyx/core';
import { DateFnsAdapter } from '@kalyx/adapter-date-fns';

normalizeISO('2026-04-15'); // "2026-04-15T00:00:00.000Z"
parseInputValue('2026-04-15', DateFnsAdapter); // "2026-04-15T00:00:00.000Z"
parseInputValue('nope', DateFnsAdapter); // null

These two live in @kalyx/core, not @kalyx/react. Only DateFnsAdapter is re-exported from the React package. parseInputValue takes the adapter as its second argument and reads the input format from it; there is no separate format parameter.

Times and time zones​

TimePicker and DateTimePicker also return ISO strings. The date part of a pure TimePicker value is a stable placeholder; consume only the time fields (getTime(iso) in @kalyx/core helps).

For IANA time-zone-aware display and input (rendering "2026-04-15T00:00:00Z" as "2026-04-15 09:00 KST", or making a calendar click emit the civil midnight of that day in the user's zone), use the displayTimezone prop. See the dedicated Timezone concept page.

Next​