ChronousDocumentation

Guides

Events

An event is ISO-8601 strings: what start, end, duration and allDay mean, and how a time zone is read.

The shape

An event is described with ISO-8601 strings and nothing else — no Date objects, no library types. Your own payload rides along under data, typed, and comes back untouched on every box and bar.

Type
type EventInput<TData = unknown> = {
  id: string
  start: string
  end?: string
  duration?: string
  allDay?: boolean
  timeZone?: string
  recurrence?: RecurrenceInput<TData>
  data?: TData
}
Events
import { buildCalendar } from '@midstem/chronous'
import type { CalendarRange, EventInput } from '@midstem/chronous'

type EventData = { title: string }

const range: CalendarRange = {
  view: 'day',
  currentDate: '2026-03-18',
  timeZone: 'Europe/Kyiv',
}

const events: EventInput<EventData>[] = [
  {
    id: 'standup',
    start: '2026-03-18T09:00',
    duration: 'PT30M',
    data: { title: 'Standup' },
  },
  {
    id: 'review',
    start: '2026-03-18T14:00',
    end: '2026-03-18T15:30',
    data: { title: 'Design review' },
  },
]

const calendar = buildCalendar(range, events)

console.log(calendar.days[0].boxes.map(box => box.event.data?.title))
// [ 'Standup', 'Design review' ]

How long an event is

  • end wins over duration; with neither, a timed event has zero length.
  • duration is an ISO-8601 duration. Date units are calendar arithmetic — P1D keeps the wall clock across a DST transition — while clock units are exact, so PT4H is always four real hours.
  • An event that ends before it starts throws InvalidEventError, carrying the offending eventId.
typescript
import { buildCalendar } from '@midstem/chronous'
import type { EventInput } from '@midstem/chronous'

const events: EventInput[] = [
  // clock units are exact — always four real hours
  { id: 'workshop', start: '2026-03-29T00:30', duration: 'PT4H' },
  // date units are calendar arithmetic — the wall clock survives the change
  { id: 'sprint', start: '2026-03-29T00:30', duration: 'P1D' },
]

// 29 March 2026 is the day Kyiv loses an hour
const calendar = buildCalendar(
  { view: 'day', currentDate: '2026-03-29', timeZone: 'Europe/Kyiv' },
  events,
)

console.log(calendar.days[0].boxes.map(box => [box.event.id, box.end]))
// [ [ 'workshop', '2026-03-29T05:30:00+03:00' ] ]
// 00:30 + four real hours is 05:30 on the wall clock, because 03:00 never
// happened. 'sprint' keeps its 00:30 and became a 23-hour bar instead.

All-day events

An event is all-day when allDay says so, or when its dates carry no time. All-day events are plain dates with no zone attached, and their end is exclusive.

typescript
import { buildCalendar } from '@midstem/chronous'
import type { EventInput } from '@midstem/chronous'

const events: EventInput[] = [
  // three days: 16, 17 and 18 March — end is exclusive
  { id: 'offsite', start: '2026-03-16', end: '2026-03-19' },
  // a single day
  { id: 'holiday', start: '2026-03-20', end: '2026-03-20' },
  // a timed event pushed into the all-day lanes anyway
  { id: 'launch', start: '2026-03-17T09:00', allDay: true },
]

const calendar = buildCalendar(
  { view: 'week', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv' },
  events,
)

console.log(calendar.rows[0].bars.map(bar => [bar.event.id, bar.dayCount]))
// [ [ 'offsite', 3 ], [ 'launch', 1 ], [ 'holiday', 1 ] ]
// Bars read across the row longest first, which is why 'launch' on the 17th
// comes before 'holiday' on the 20th.

Every all-day event is drawn as a bar above the grid rather than inside it — Layout & lanes covers how those bars are packed.

Time zones

Times are read in the calendar’s time zone unless the event carries its own — that is how a Kyiv office schedule stays correct when it is opened from Berlin.

typescript
import { buildCalendar } from '@midstem/chronous'
import type { CalendarRange, EventInput } from '@midstem/chronous'

const range: CalendarRange = {
  view: 'day',
  currentDate: '2026-03-18',
  timeZone: 'Europe/Kyiv',
}

const events: EventInput[] = [
  // read in Europe/Kyiv, the calendar's own zone
  { id: 'standup', start: '2026-03-18T09:00', duration: 'PT30M' },
  // read in Tokyo, and drawn where that lands in Kyiv
  {
    id: 'sync',
    start: '2026-03-18T09:00',
    duration: 'PT30M',
    timeZone: 'Asia/Tokyo',
  },
]

const calendar = buildCalendar(range, events)

console.log(calendar.days[0].boxes.map(box => [box.event.id, box.startMinute]))
// [ [ 'sync', 120 ], [ 'standup', 540 ] ] — 02:00 and 09:00 Kyiv time