Dates
Month grids, ISO day strings, selection state, locale formatting and locale parsing, with no time zones and no DOM.
Reach for them for any calendar, scheduler or date grid, and for storing or comparing days without time zones. The helpers are pure: no DOM at import time or in the call path, so they run on the server and in tests. Each result below is computed when the page is built, by running the call shown.
Import
import {
buildCalendarMonth,
getWeekStartsOn,
addMonths,
getVisibleMonthViews,
isoToDate,
dateToIso,
isIsoInRange,
compareIso,
normalizeRange,
parseIsoRange,
serializeIsoRange,
parseMultipleIsos,
serializeMultipleIsos,
toggleMultipleIso,
advanceRangeSelection,
getActiveRangeBounds,
getDaySelectionAppearance,
isIsoInSpan,
formatDisplayDate,
formatDateRange,
formatMonthYear,
formatTodayLabel,
getWeekdayLabels,
getDateTimeFormat,
parseLocaleDateString,
getDatePartOrder,
getDateSeparators,
} from '@ecopages/radiant-ui/helpers';Month grids
buildCalendarMonth returns at most six weeks of day cells in the locale's week order, with isToday, isSelected, isDisabled and range flags. addMonths and getVisibleMonthViews move and list months; months are zero-based.
const weeks = buildCalendarMonth(2026, 10, {
locale: 'en-US',
value: '2026-11-14',
min: '2026-11-10',
today: new Date(2026, 10, 20),
});| Call | Result |
|---|---|
weeks[0][0].iso | "2026-11-01" |
weeks.flat().find((cell) => cell.iso === '2026-11-14')?.isSelected | true |
weeks.flat().find((cell) => cell.iso === '2026-11-09')?.isDisabled | true |
getWeekStartsOn('en-US') | 0 |
getWeekStartsOn('en-GB') | 1 |
addMonths(2026, 11, 1) | {"year":2027,"month":0} |
getVisibleMonthViews(2026, 10, 2) | [{"year":2026,"month":10,"offset":0},{"year":2026,"month":11,"offset":1}] |
ISO dates
YYYY-MM-DD strings to and from local dates, and a bounds check. An impossible date is null.
| Call | Result |
|---|---|
isoToDate('2026-11-14') | Date(2026-11-14) |
isoToDate('2026-02-30') | null |
dateToIso(new Date(2026, 10, 14)) | "2026-11-14" |
isIsoInRange('2026-11-14', '2026-11-01', '2026-11-30') | true |
compareIso('2026-11-14', '2026-11-20') | -1 |
Selection
Single, multiple and range selection as pure state transitions on ISO strings. A range is { start, end } and is serialized as start/end.
| Call | Result |
|---|---|
normalizeRange('2026-11-20', '2026-11-14') | {"start":"2026-11-14","end":"2026-11-20"} |
parseIsoRange('2026-11-20/2026-11-14') | {"start":"2026-11-14","end":"2026-11-20"} |
serializeIsoRange({ start: '2026-11-14', end: '2026-11-20' }) | "2026-11-14/2026-11-20" |
parseMultipleIsos('2026-11-20, 2026-11-14') | ["2026-11-14","2026-11-20"] |
toggleMultipleIso(['2026-11-14'], '2026-11-20') | ["2026-11-14","2026-11-20"] |
advanceRangeSelection(null, { anchor: null, hover: null }, '2026-11-14') | {"committed":null,"draft":{"anchor":"2026-11-14","hover":"2026-11-14"}} |
getDaySelectionAppearance('2026-11-15', 'range', { range: { start: '2026-11-14', end: '2026-11-16' } }) | {"isSelected":true,"isRangeStart":false,"isRangeEnd":false,"isRangeMiddle":true} |
Formatting
Localized labels, with a cached Intl.DateTimeFormat behind getDateTimeFormat.
| Call | Result |
|---|---|
formatDisplayDate(new Date(2026, 10, 14), 'en-US') | "Nov 14, 2026" |
formatMonthYear(new Date(2026, 10, 14), 'en-US') | "November 2026" |
formatDateRange(new Date(2026, 10, 14), new Date(2026, 10, 20), 'en-US') | "Nov 14 – 20, 2026" |
getWeekdayLabels('en-US', 0) | ["S","M","T","W","T","F","S"] |
formatTodayLabel('en-US') | "today" |
Parsing
Typed dates in locale order: ISO, masked, locale-ordered numbers or month names.
| Call | Result |
|---|---|
getDatePartOrder('en-GB') | ["day","month","year"] |
getDateSeparators('en-US') | ["/","/"] |
parseLocaleDateString('21/08/2002', 'en-GB') | Date(2002-08-21) |
parseLocaleDateString('Aug 21, 2002', 'en-US') | Date(2002-08-21) |
parseLocaleDateString('not a date', 'en-US') | null |