schedgrid/time

timeAxis maps a time range onto fractional indices, plus ticks.

import { timeAxis } from 'schedgrid/time';

The grid never sees a Date. timeAxis maps a continuous time range onto axis indices so that index i covers [start + i·slot, start + (i+1)·slot). Everything is plain millisecond arithmetic, so a day that contains a DST change simply has fewer or more slots than 24 hours would.

timeAxis(options)

Prop

Type

const day = timeAxis({
  start: new Date(2026, 8, 7),
  end: new Date(2026, 8, 8),
  slot: 15 * 60_000,
  size: 24,
});

day.count; // 96
day.axis; // { count: 96, size: 24 } — pass to createGrid or setColumns
day.toIndex(new Date(2026, 8, 7, 9, 37)); // 38.47
day.toRange(a, b); // [toIndex(a), toIndex(b)], ready for an Item
day.fromIndex(42); // Date at 10:30

The axis

Prop

Type

toIndex is not clamped: a moment before start maps to a negative index. That is deliberate, so an item that starts before the visible range still lands in the right place once you offset it.

timeAxis throws a RangeError if slot is not greater than zero — a zero-length slot has no meaningful index — and ticks throws the same for every. Nothing else in the library throws.

Ticks

ticks(every, { align }) returns the moments every every milliseconds, counted from align (default start), that fall inside the range. Each tick carries its fractional index, so drawing a gridline is columns.offsetAt(tick.index).

Prop

Type

Steps are fixed lengths, so use ticks for hours and minutes. Calendar steps (days across DST, months) are not fixed lengths. Compute those Dates yourself and map them with toIndex.

Offsetting for a label column

The grid's column 0 is often a pinned label column, which means time slot i lives in grid column i + 1. Keep that offset in one place:

const columns = {
  count: day.count + 1,
  size: (i: number) => (i === 0 ? 160 : 24),
  pinned: 1,
};

const [c0, c1] = day.toRange(start, end);
const item = { id, rows: [row, row + 1], columns: [c0 + 1, c1 + 1] };