import { addDays, eachDayOfInterval, endOfMonth, endOfWeek, getHours, getMinutes, isSameDay, isToday, startOfDay, startOfMonth, startOfWeek, } from "date-fns"; import type { CalendarMode, WeekStartsOn } from "../types"; /** The seven dates of the week containing `date`, starting on `weekStartsOn`. */ export const getWeekDays = (date: Date, weekStartsOn: WeekStartsOn): Date[] => { const start = startOfWeek(date, { weekStartsOn }); return Array.from({ length: 7 }, (_, index) => addDays(start, index)); }; /** How many day columns a time-grid mode shows. `custom` uses `numberOfDays`. */ export const viewDayCount = (mode: CalendarMode, numberOfDays = 1): number => { switch (mode) { case "week": return 7; case "3days": return 3; case "custom": return Math.max(1, Math.floor(numberOfDays)); default: return 1; // 'day' } }; /** * Days in the inclusive span from `weekStartsOn` to `weekEndsOn` (1–7), * wrapping when the end precedes the start (e.g. Sat→Wed). Mirrors * react-native-big-calendar's `weekDaysCount`. */ export const weekDaysCount = (weekStartsOn: WeekStartsOn, weekEndsOn: WeekStartsOn): number => { if (weekEndsOn < weekStartsOn) { let count = 1; let i = weekStartsOn; while (i !== weekEndsOn && count <= 7) { i = (i + 1) % 7; count++; } return count; } if (weekEndsOn > weekStartsOn) return weekEndsOn - weekStartsOn + 1; return 1; }; /** * The day columns to render for a time-grid page. `week` spans the calendar week * (honouring `weekStartsOn`). `custom` with a `weekEndsOn` spans the partial week * from `weekStartsOn` to `weekEndsOn` (anchored to `date`'s week, paging by week); * otherwise every mode shows `viewDayCount` consecutive days starting at `date`. */ export const getViewDays = ( mode: CalendarMode, date: Date, weekStartsOn: WeekStartsOn, numberOfDays = 1, isRTL = false, weekEndsOn?: WeekStartsOn, hiddenDays?: number[], ): Date[] => { // A full hidden week would loop forever below; treat it as "hide nothing". const hidden = hiddenDays && hiddenDays.length > 0 && hiddenDays.length < 7 ? hiddenDays : []; let days: Date[]; if (mode === "week") { days = filterHiddenDays(getWeekDays(date, weekStartsOn), hidden); } else if (mode === "custom" && weekEndsOn != null) { // Mirror big-calendar: anchor to `date`'s week and take the partial-week span. const subject = startOfDay(date); const offset = weekStartsOn - subject.getDay(); days = filterHiddenDays( Array.from({ length: weekDaysCount(weekStartsOn, weekEndsOn) }, (_, index) => addDays(subject, index + offset), ), hidden, ); } else { // Count-based views keep their column count by walking forward over hidden // days (an anchor on a hidden day starts at the next visible one). const count = viewDayCount(mode, numberOfDays); days = []; let cursor = startOfDay(date); while (days.length < count) { if (!hidden.includes(cursor.getDay())) days.push(cursor); cursor = addDays(cursor, 1); } } return isRTL ? days.reverse() : days; }; /** * The calendar weeks covering `month`, padded to whole weeks starting on * `weekStartsOn`. `showSixWeeks` always returns six rows (42 days) for a * fixed-height grid; `isRTL` reverses each week's day order. */ export const buildMonthWeeks = ( month: Date, weekStartsOn: WeekStartsOn, { showSixWeeks = false, isRTL = false, hiddenDays, }: { showSixWeeks?: boolean; isRTL?: boolean; hiddenDays?: number[] } = {}, ): Date[][] => { const start = startOfWeek(startOfMonth(month), { weekStartsOn }); const end = showSixWeeks ? addDays(start, 41) : endOfWeek(endOfMonth(month), { weekStartsOn }); const days = eachDayOfInterval({ start, end }); const weeks: Date[][] = []; for (let index = 0; index < days.length; index += 7) { const week = filterHiddenDays(days.slice(index, index + 7), hiddenDays); if (week.length > 0) weeks.push(isRTL ? week.reverse() : week); } return weeks; }; /** * Drop the days whose weekday (0=Sunday…6=Saturday) is listed in `hiddenDays`. * Hiding all seven weekdays is treated as hiding nothing (matching * `getViewDays`), so a misconfiguration degrades to the full grid, not a blank. */ export const filterHiddenDays = (days: Date[], hiddenDays?: number[]): Date[] => hiddenDays && hiddenDays.length > 0 && new Set(hiddenDays).size < 7 ? days.filter((day) => !hiddenDays.includes(day.getDay())) : days; /** True when `date` is a Saturday or Sunday. */ export const isWeekend = (date: Date): boolean => { const day = date.getDay(); return day === 0 || day === 6; }; /** True when `date` falls on the current calendar day. */ export const getIsToday = (date: Date): boolean => isToday(date); /** True when `a` and `b` are the same calendar day (ignoring the time of day). */ export const isSameCalendarDay = (a: Date, b: Date): boolean => isSameDay(a, b); /** Minutes elapsed since midnight (0–1439). */ export const minutesIntoDay = (date: Date): number => getHours(date) * 60 + getMinutes(date); /** The twelve month anchors (first-of-month) of `date`'s year, January first. */ export const getYearMonths = (date: Date): Date[] => Array.from({ length: 12 }, (_, i) => new Date(date.getFullYear(), i, 1));