ChronousDocumentation

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
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

  • day is one day and week is seven from weekStartsOn, Monday by default.
  • days and agenda span dayCount, which defaults to a week and to thirty days.
  • month covers the anchor’s month padded out to whole weeks; the padding days are marked inCurrentPeriod: false.
typescript
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.

typescript
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.

typescript
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.