API
buildCalendar
The engine’s front door: a range and events in, one plain JSON layout out.
Signature
buildCalendar(range, events) is the engine’s front door: a range and a list of events in, one plain object out. It is exported from both packages and needs no framework.
typescript
import { buildCalendar } from '@midstem/chronous' import type { CalendarRange, EventInput } from '@midstem/chronous' const range: CalendarRange = { view: 'week', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv', } const events: EventInput[] = [ { id: 'standup', start: '2026-03-18T09:00', duration: 'PT30M' }, ] const calendar = buildCalendar(range, events) console.log(calendar.view) // 'week' console.log(calendar.days.length) // 7 console.log(calendar.rows.length) // 1 console.log(calendar.days[2].boxes[0].startMinute) // 540
What comes back
Type
type CalendarLayout<TData = unknown> = { view: ViewKind start: IsoDateTime end: IsoDateTime days: CalendarDay<TData>[] rows: CalendarRow<TData>[] } type CalendarDay<TData = unknown> = { date: IsoDate start: IsoDateTime end: IsoDateTime minutes: number inCurrentPeriod: boolean slots: CalendarSlot[] boxes: CalendarBox<TData>[] } type CalendarRow<TData = unknown> = { start: IsoDate end: IsoDate dayCount: number lanes: number bars: CalendarBar<TData>[] }
daysare the days the grid draws,rowsthe bands of bars above it.- Every box and bar carries the normalized event as
event, discriminated byallDay: a timed entry holds date-times, an all-day entry plain dates. - The range’s
timeZoneanddisambiguationare the ones the events are read with, so a calendar is built from one consistent point of view.
Layout & lanes has the full shape of a box and of a bar.
It is plain JSON
Everything crossing the boundary is a string or a number. Temporal types stay inside the engine, so a calendar survives JSON.stringify, a server-to-client payload and a React state update unchanged.
typescript
import { buildCalendar } from '@midstem/chronous' const calendar = buildCalendar( { view: 'day', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv' }, [{ id: 'standup', start: '2026-03-18T09:00', duration: 'PT30M' }], ) // It survives a round trip: a server payload, a state update, a cache console.log(JSON.parse(JSON.stringify(calendar)).days[0].date) // '2026-03-18' // A moment carries its offset, so this is exact without knowing the zone console.log(new Date(calendar.days[0].boxes[0].start).toISOString()) // '2026-03-18T07:00:00.000Z' // A date has no time and is never moved into a zone console.log(calendar.days[0].date) // '2026-03-18'