Guides
Views & ranges
Day, week, days, month and agenda from one range object, and the slot rows a DST transition lands in.
The range
A range turns an anchor date into the days a calendar draws, and the rows it draws them on. It is a plain object you own — the components never change it.
type CalendarRange = { view: 'day' | 'week' | 'days' | 'month' | 'agenda' currentDate: string timeZone: string weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 dayCount?: number slotMinutes?: number disambiguation?: Disambiguation }
The five views
dayis one day andweekis seven fromweekStartsOn, Monday by default.daysandagendaspandayCount, which defaults to a week and to thirty days.monthcovers the anchor’s month padded out to whole weeks; the padding days are markedinCurrentPeriod: false.
import { buildCalendar } from '@midstem/chronous' import type { CalendarRange } from '@midstem/chronous' const ranges: CalendarRange[] = [ // Monday 16 to Sunday 22 — the week that contains the 18th { view: 'week', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv' }, // a working week, counted from the anchor { view: 'days', dayCount: 5, currentDate: '2026-03-18', timeZone: 'Europe/Kyiv' }, // March, padded out to whole weeks { view: 'month', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv' }, // the next thirty days as a list { view: 'agenda', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv' }, ] for (const range of ranges) { const calendar = buildCalendar(range, []) console.log(range.view, calendar.days.length, calendar.days[0].date) } // week 7 2026-03-16 // days 5 2026-03-18 // month 42 2026-02-23 // agenda 30 2026-03-18
Slots
A time grid is built for day, week and days; month and agenda carry no slots. Slots are wall-clock rows: a day always has 1440 / slotMinutes of them, whatever the time zone does that day.
import { buildCalendar } from '@midstem/chronous' const calendar = buildCalendar( { view: 'day', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv', // half-hour rows instead of the default hour slotMinutes: 30, }, [], ) const [day] = calendar.days console.log(day.slots.length) // 48 — always 1440 / slotMinutes console.log(day.slots[19]) // { // minuteOfDay: 570, // start: '2026-03-18T09:30:00+02:00', // end: '2026-03-18T10:00:00+02:00', // minutes: 30 // }
A DST transition is absorbed by its row
A row is placed by the wall clock and sized by elapsed time, so the hour a zone repeats is one two-hour row and the hour it skips is one zero-length row. No row is ever negative, and a day’s rows always add up to its real length.
import { buildCalendar } from '@midstem/chronous' import type { CalendarRange } from '@midstem/chronous' const on = (currentDate: string): CalendarRange => ({ view: 'day', currentDate, timeZone: 'Europe/Kyiv', }) // The night Kyiv loses an hour: the 03:00 row runs none. const spring = buildCalendar(on('2026-03-29'), []).days[0] console.log(spring.slots[3].minutes) // 0 console.log(spring.slots.length) // 24 console.log(spring.minutes) // 1380 — a 23-hour day // The night it gains one: the same row runs two hours. const autumn = buildCalendar(on('2026-10-25'), []).days[0] console.log(autumn.slots[3].minutes) // 120 console.log(autumn.slots.length) // 24 console.log(autumn.minutes) // 1500
disambiguation is what the events are read with and is never applied to a row — leave it alone unless a schedule has to round one way by policy. A currentDate or time zone that cannot be read, a slotMinutes outside 1 to 1440 or a dayCount below one throws InvalidRangeError.