Skip to main content

@kalyx/core

Platform-independent date logic. Usually consumed transitively through @kalyx/react.

pnpm add @kalyx/core

The examples that use DateFnsAdapter also require its adapter package and underlying date library:

pnpm add @kalyx/adapter-date-fns date-fns

Types​

type ISODateString = string;

type DisabledRule =
| { date: ISODateString }
| { before: ISODateString }
| { after: ISODateString }
| { dayOfWeek: number[] }
| { filter: (iso: ISODateString) => boolean };

type DateRange = {
start: ISODateString | null;
end: ISODateString | null;
};

type CalendarDay = {
isoString: ISODateString;
dayNumber: number;
isCurrentMonth: boolean;
isToday: boolean;
isSelected: boolean;
isDisabled: boolean;
isFocused: boolean;
isRangeStart: boolean;
isRangeEnd: boolean;
isInRange: boolean;
};

type CalendarWeek = CalendarDay[];
type CalendarGrid = CalendarWeek[];

type WeekStartsOn = 0 | 1;

type CalendarOptions = {
weekStartsOn?: WeekStartsOn;
today?: ISODateString;
selected?: ISODateString | null;
focusedDate?: ISODateString;
disabled?: DisabledRule[];
range?: DateRange | null;
rangeHover?: ISODateString | null;
timezone?: string;
fixedWeeks?: boolean;
};

type TimeValue = {
hours: number;
minutes: number;
seconds: number;
};

DateAdapter​

See the Adapters concept → for the full interface.

DateFnsAdapter​

Default adapter. UTC-safe, built on date-fns v4.

import { DateFnsAdapter } from '@kalyx/adapter-date-fns';

Calendar utilities​

getCalendarDays(viewMonth, adapter, options)​

Build a 4-6 week grid for a month. Set fixedWeeks: true when the layout requires exactly 6 weeks.

import { DateFnsAdapter } from '@kalyx/adapter-date-fns';
import { getCalendarDays } from '@kalyx/core';

const grid = getCalendarDays(
'2026-04-01T00:00:00.000Z',
DateFnsAdapter,
{ weekStartsOn: 0, today: '2026-04-16T00:00:00.000Z', fixedWeeks: true },
);

Returns CalendarGrid (4-6 arrays of 7 CalendarDays). Leading and trailing days belong to neighboring months (isCurrentMonth: false). With fixedWeeks: true, the result is always 6×7.

isDateDisabled(iso, rules, adapter, timezone?)​

import { DateFnsAdapter } from '@kalyx/adapter-date-fns';
import { isDateDisabled } from '@kalyx/core';

isDateDisabled(
'2026-04-18T00:00:00.000Z',
[{ dayOfWeek: [0, 6] }],
DateFnsAdapter,
); // → true (Saturday)

// With `timezone`, `{ date }` / `{ dayOfWeek }` rules match by the civil day in
// that zone. Pass the civil-midnight-in-timezone instant the pickers emit (the
// same value `onChange` gives you), not a raw `…T00:00:00Z` grid coordinate:
isDateDisabled(
'2026-01-15T05:00:00.000Z', // civil Jan 15 in America/New_York
[{ date: '2026-01-15T05:00:00.000Z' }],
DateFnsAdapter,
'America/New_York',
); // → true

iso is the point-in-time value being tested, not a hand-built UTC-midnight grid coordinate: under a negative UTC offset, 2026-01-15T00:00:00.000Z is still the 14th locally. { before } / { after } are instant comparisons and ignore timezone. When you just need per-cell disabled state for a calendar, read the precomputed isDisabled flag from getCalendarDays(...) instead; it normalizes each cell for you.

getISOWeekNumber(iso)​

ISO 8601 week number (1-53) of the instant's UTC day. Weeks start Monday and week 1 is the one containing the first Thursday of the year, so early-January and late-December dates can belong to the neighboring year's numbering. WeekPicker uses this for its week labels.

import { getISOWeekNumber } from '@kalyx/core';

getISOWeekNumber('2026-01-01T00:00:00.000Z'); // → 1 (a Thursday, so ISO week 1)
getISOWeekNumber('2026-04-15T00:00:00.000Z'); // → 16
getISOWeekNumber('2026-12-31T00:00:00.000Z'); // → 53

This reads the UTC day. Under a displayTimezone the civil day can differ, so convert first with calendarDayFromInstant(iso, timeZone) if you need the week number the user sees.

minDate(a, b, adapter) / maxDate(a, b, adapter)​

import { DateFnsAdapter } from '@kalyx/adapter-date-fns';
import { minDate } from '@kalyx/core';

minDate(
'2026-04-15T00:00:00.000Z',
'2026-04-10T00:00:00.000Z',
DateFnsAdapter,
);
// → "2026-04-10T00:00:00.000Z"

Date string utilities​

normalizeISO(value)​

Lenient normalizer. Expands a date-only value like 2026-04-15 to a full UTC-midnight ISO string. Full ISO datetimes and unrecognized strings are returned unchanged; an empty string stays empty.

parseInputValue(input, adapter)​

Parse yyyy-MM-dd, yyyy/MM/dd, or an eight-digit yyyyMMdd user input.

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

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

Time utilities​

setTime(iso, partial) / getTime(iso)​

import { setTime, getTime } from '@kalyx/core';

setTime('2026-04-15T00:00:00.000Z', { hours: 9, minutes: 30 });
// → "2026-04-15T09:30:00.000Z"

getTime('2026-04-15T09:30:00.000Z');
// → { hours: 9, minutes: 30, seconds: 0 }

parseTimeString(input) / formatTimeString(time, withSeconds?)​

import { formatTimeString, parseTimeString } from '@kalyx/core';

parseTimeString('09:30'); // → { hours: 9, minutes: 30, seconds: 0 }
parseTimeString('09:30:45'); // → { hours: 9, minutes: 30, seconds: 45 }
formatTimeString({ hours: 9, minutes: 30, seconds: 0 }); // → "09:30"
formatTimeString({ hours: 9, minutes: 30, seconds: 0 }, true); // → "09:30:00"

formatTimeFromISO(iso, format)​

Format an ISO datetime in UTC using HH:mm, HH:mm:ss, h:mm a, or h:mm:ss a.

import { formatTimeFromISO } from '@kalyx/core';

formatTimeFromISO('2026-04-15T13:30:00.000Z', 'h:mm a');
// → "1:30 PM"

12h helpers​

import { to12Hour, to24Hour } from '@kalyx/core';

to12Hour(13); // → { hours12: 1, period: 'PM' }
to24Hour(1, 'PM'); // → 13

Option generators​

import { generateHours, generateMinutes } from '@kalyx/core';

generateHours('24h'); // → [0, 1, 2, …, 23]
generateHours('12h'); // → [1, 2, …, 12]
generateMinutes(15); // → [0, 15, 30, 45]

isSameTime(a, b)​

import { isSameTime } from '@kalyx/core';

isSameTime({ hours: 9, minutes: 0, seconds: 0 }, { hours: 9, minutes: 0, seconds: 0 });
// → true

Locale utilities​

import { formatFullDate, formatMonthYear, getMonthName, getWeekdayNames } from '@kalyx/core';

getMonthName(3, 'en-US'); // → "April"
formatMonthYear(2026, 3, 'en-US'); // → "April 2026"
getWeekdayNames('en-US', 0);
// → [{ short: 'Sun', full: 'Sunday' }, …]
formatFullDate('2026-04-15T00:00:00.000Z', 'en-US');
// → "Wednesday, April 15, 2026"

getWeekStartForLocale(locale?)​

The first day of the week the locale conventionally uses, narrowed to a WeekStartsOn: 0 (Sunday) or 1 (Monday). DatePicker and RangePicker call this when you don't pass weekStartsOn; an explicit prop always wins.

:::note Saturday- and Friday-start locales are reported as Monday

The runtime distinguishes more than two starts: Intl reports Saturday for ar-AF and fa-IR, and Friday for dv-MV. The public WeekStartsOn type is 0 | 1, so every non-Sunday result is narrowed to 1. Those locales therefore render a Monday-start grid, not their conventional one. If you need an exact start, pass weekStartsOn explicitly.

:::

import { getWeekStartForLocale } from '@kalyx/core';

getWeekStartForLocale('en-US'); // → 0 (Sunday)
getWeekStartForLocale('de-DE'); // → 1 (Monday)

getDayPeriodName(period, locale?)​

Localized AM/PM name, used by TimePicker.AmPmToggle.

import { getDayPeriodName } from '@kalyx/core';

getDayPeriodName('AM', 'en-US'); // → "AM"
getDayPeriodName('PM', 'ko-KR'); // → "오후"

Timezone utilities​

Used internally by every picker when displayTimezone is set. Exposed publicly so you can run the same math yourself.

formatInTimezone(iso, formatStr, timeZone)​

Format a UTC instant in the requested zone. Handles DST transitions.

import { formatInTimezone } from '@kalyx/core';

formatInTimezone('2026-03-08T07:30:00.000Z', 'yyyy-MM-dd HH:mm', 'America/New_York');
// → '2026-03-08 03:30' (post spring-forward EDT)

startOfDayInTimezone(iso, timeZone)​

Civil midnight of the given UTC instant's day, expressed as a UTC ISO string.

import { startOfDayInTimezone } from '@kalyx/core';

startOfDayInTimezone('2026-01-15T12:00:00.000Z', 'Asia/Seoul');
// → '2026-01-14T15:00:00.000Z'

isSameDayInTimezone(a, b, timeZone)​

Civil-day equality in the zone. Timezone-safe alternative to comparing iso.slice(0, 10).

todayInTimezone(timeZone)​

"Today" expressed as civil midnight in the zone.

getTimezoneOffsetMinutes(iso, timeZone)​

UTC offset (minutes east of UTC) at the given instant. Differs before and after DST transitions.

civilMidnightFromUtcDay(gridUtcIso, timeZone)​

The bridge Calendar uses: maps a UTC-midnight grid cell ISO to civil midnight of the same calendar day in the zone. You rarely need this directly; it is exported for custom calendar renderers.

import { civilMidnightFromUtcDay } from '@kalyx/core';

civilMidnightFromUtcDay('2026-01-15T00:00:00.000Z', 'Asia/Seoul');
// → '2026-01-14T15:00:00.000Z' (Seoul Jan 15, 00:00)

calendarDayFromInstant(iso, timeZone)​

The inverse of civilMidnightFromUtcDay: takes any instant and returns the UTC-midnight coordinate of the civil day that instant falls on in the zone. Use it to answer "which calendar cell does this value belong to?"

import { calendarDayFromInstant } from '@kalyx/core';

calendarDayFromInstant('2025-12-31T15:00:00.000Z', 'Asia/Seoul');
// → '2026-01-01T00:00:00.000Z' (already Jan 1 in Seoul)
calendarDayFromInstant('2026-01-15T05:00:00.000Z', 'America/New_York');
// → '2026-01-15T00:00:00.000Z'

The two functions round-trip in every IANA zone: calendarDayFromInstant(civilMidnightFromUtcDay(c, tz), tz) === c. That property is enforced across all zones in the core test suite.

getTimeInTimezone(iso, timeZone) / setTimeInTimezone(iso, partial, timeZone)​

Read and write time-of-day as observed in the zone. setTimeInTimezone preserves the civil date and replaces the time portion, iterating once to absorb DST offsets.

import { setTimeInTimezone } from '@kalyx/core';

setTimeInTimezone('2026-01-15T00:00:00.000Z', { hours: 10 }, 'Asia/Seoul');
// → '2026-01-15T01:00:00.000Z' (Seoul 10:00 = UTC 01:00)

See the Timezone concept page for usage patterns.

Accessibility labels​

Default ARIA label sets. Override via the labels prop on any picker Root.

import {
DEFAULT_DATEPICKER_LABELS,
DEFAULT_RANGEPICKER_LABELS,
DEFAULT_TIMEPICKER_LABELS,
DEFAULT_DATETIMEPICKER_LABELS,
} from '@kalyx/core';

import type {
DatePickerLabels,
RangePickerLabels,
TimePickerLabels,
DateTimePickerLabels,
} from '@kalyx/core';

Each label set provides keys like triggerOpen, prevMonth, nextMonth, hourOption(h), etc. Pass a Partial<*Labels> to override only what you need:

import { DatePicker } from '@kalyx/react';

<DatePicker labels={{ triggerOpen: 'Open calendar', triggerClose: 'Close calendar' }}>
<span />
</DatePicker>;

See also​