/** * Calendar arithmetic for the scheduler's time axis. * * Pure and DOM-free, so it runs under the package's `node` test environment. * * ## Why epoch milliseconds, not `Date` * * Every time in the scheduler's model, index and axes is a `number`. `Date` * appears only inside this module and in formatting. Four reasons: * * - The event index performs hundreds of thousands of comparisons; `number` * compares are a single machine op and pack into `Float64Array`, where `Date` * would mean a heap object and a `.getTime()` call per comparison. * - "Did the visible range change?" memoizes as `===`. * - `Date` is mutable. An event's `start` handed to a host callback could be * mutated in place and silently corrupt the index; a `number` cannot. * - Datasources deliver ISO strings, which are parsed exactly once at ingest. * * ## Why iteration, never division * * It is tempting to compute slot counts as `(end - start) / slotDuration`. That * is wrong for every unit except minute and hour: * * - A calendar day is 23 or 25 hours across a DST transition, not 24. * - Months are 28–31 days; quarters 90–92; years 365 or 366. * * {@link ticksBetween} therefore *walks* the range using {@link add}, which is * wall-clock correct. This is the single most important correctness rule here: * a scheduler that divides will drift by an hour twice a year and by whole days * across month boundaries. */ /** Granularity of one timeline slot. */ export type TimeUnit = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; /** A half-open time interval `[start, end)`, in epoch milliseconds. */ export interface TimeRange { readonly start: number; readonly end: number; } /** Day index a week starts on: `0` Sunday, `1` Monday (ISO). */ export type WeekStart = 0 | 1; export declare const MINUTE_MS = 60000; export declare const HOUR_MS = 3600000; /** * Options threaded through every calendar operation. * * Kept as one object rather than positional arguments because `weekStartsOn` * silently changes `startOf('week', …)` by up to six days, and a bare boolean * or number at a call site is exactly the kind of argument that gets passed * wrong. */ export interface CalendarOptions { /** @default 1 (Monday, ISO 8601) */ readonly weekStartsOn?: WeekStart; } /** * Truncates `t` to the start of the containing `unit`, in local wall-clock time. * * Uses the `Date` constructor's local-time component form throughout, so the * result is the true local midnight/hour boundary even across a DST transition * — where naive millisecond flooring would land an hour off. * * @param unit - Granularity to truncate to. * @param t - Epoch milliseconds. * @returns Epoch milliseconds of the unit boundary at or before `t`. */ export declare function startOf(unit: TimeUnit, t: number, options?: CalendarOptions): number; /** * Adds `n` whole units to `t`, in local wall-clock time. * * `minute` and `hour` are genuinely fixed-duration and use plain arithmetic. * Everything from `day` upward goes through the `Date` component setters, which * is what makes the result correct across DST (adding one day at a spring-forward * boundary advances 23 hours, landing on the same local clock time) and across * uneven month lengths (Jan 31 + 1 month clamps to Feb 28/29 rather than * overflowing into March). * * @param n - May be negative. */ export declare function add(unit: TimeUnit, n: number, t: number, options?: CalendarOptions): number; /** Number of days in a given month. Day 0 of the next month is the last of this one. */ export declare function daysInMonth(year: number, monthIndex: number): number; /** * Whole units between `a` and `b`, truncated toward zero. * * For calendar units this counts *boundaries crossed*, not elapsed duration — * so two timestamps an hour apart that straddle midnight are one day apart, and * a DST-shortened day still counts as one. */ export declare function diffIn(unit: TimeUnit, a: number, b: number, options?: CalendarOptions): number; /** * Generates the slot boundaries covering `range`, as **fence posts**. * * The returned array has `slotCount + 1` entries: slot `i` spans * `[ticks[i], ticks[i + 1])`. Returning posts rather than slot starts is what * removes the off-by-one from every downstream consumer — the axis gets its * widths by differencing adjacent entries, and the header gets its labels from * all but the last. * * The first post is truncated to the unit boundary at or before `range.start`, * so a range beginning mid-day still yields whole, aligned slots. The last post * is the first boundary at or after `range.end`, so the range is always fully * covered. * * Walks with {@link add} rather than dividing — see the module doc. * * @param step - Units per slot; `{ unit: 'minute', step: 15 }` gives quarter-hour * slots. Must be a positive integer. * @returns Ascending epoch milliseconds, length ≥ 2 for any non-empty range. */ export declare function ticksBetween(range: TimeRange, unit: TimeUnit, step: number, options?: CalendarOptions): number[]; /** * `true` when the local UTC offset differs anywhere in `range` — i.e. a DST * transition falls inside it. * * The timeline uses this to decide whether day/week slots are uniform (allowing * the allocation-free arithmetic axis) or must be materialised. Probing is far * cheaper than generating ticks: it samples the endpoints plus each month * boundary, which is ~120 probes for a decade and cannot miss a transition, * since no jurisdiction changes offset more than twice per month. */ export declare function hasOffsetChange(range: TimeRange): boolean; //# sourceMappingURL=calendar.d.ts.map