export type CalendarMode = 'month' | 'week' /** End times are exclusive; a slot ending at midnight does not occupy the next day. */ export function occursOnDate(startDate: string, endAt: string | undefined, date: string): boolean { if (date === startDate) return true if (date < startDate || !endAt) return false const endDate = endAt.slice(0, 10) return date < endDate || (date === endDate && /[1-9]/.test(endAt.slice(11))) } /** Calendar dates are warehouse-local dates, never UTC conversions. */ export function dateKey(date: Date): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` } function parseDate(value: string): Date { const date = new Date(`${value}T12:00:00`) if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(date.getTime()) || dateKey(date) !== value) { throw new Error('无效的日历日期') } return date } export function addDays(value: string, days: number): string { const date = parseDate(value) date.setDate(date.getDate() + days) return dateKey(date) } export function calendarRange(anchor: string, mode: CalendarMode): { from: string; to: string } { const date = parseDate(anchor) const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1, 12) if (mode === 'month') date.setDate(1) date.setDate(date.getDate() - (date.getDay() + 6) % 7) const from = dateKey(date) // 月视图只补齐月末所在周,不追加整周的下月日期;结束日期仍为开区间。 return { from, to: mode === 'month' ? addDays(dateKey(nextMonth), (8 - nextMonth.getDay()) % 7) : addDays(from, 7) } } export function calendarDates(anchor: string, mode: CalendarMode): string[] { const { from, to } = calendarRange(anchor, mode) const dates: string[] = [] for (let date = from; date < to; date = addDays(date, 1)) dates.push(date) return dates } export function moveCalendar(anchor: string, mode: CalendarMode, offset: number): string { if (mode === 'week') return addDays(anchor, offset * 7) const date = parseDate(anchor) const day = date.getDate() date.setDate(1) date.setMonth(date.getMonth() + offset) const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() date.setDate(Math.min(day, lastDay)) return dateKey(date) }