/** Presentation only: exact opening, availability and event boundaries remain authoritative. */ export function calendarAxis(from: string | null, to: string | null) { const empty = { start: 0, end: 0, ticks: [] as string[], padding: [] as { starts_at: string; ends_at: string; }[] }; if (!from || !to) return empty; const first = Date.parse(from), last = Date.parse(to); if (!Number.isFinite(first) || !Number.isFinite(last) || last <= first) return empty; const step = 30 * 60000; // Use local wall-time minutes from the API ISO offsets, including DST folds. const remainder = (iso: string) => Number(iso.slice(14, 16)) % 30 * 60000 + Number(iso.slice(17, 19)) * 1000; const start = first - remainder(from), end = last + (step - remainder(to)) % step; const ticks: string[] = []; for (let at = start; at < end; at += step) ticks.push(new Date(at).toISOString()); const padding = []; if (start < first) padding.push({ starts_at: new Date(start).toISOString(), ends_at: from }); if (last < end) padding.push({ starts_at: to, ends_at: new Date(end).toISOString() }); return { start, end, ticks, padding }; }