This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212import { addDays, differenceInCalendarDays, eachDayOfInterval, endOfMonth, endOfWeek, format, isValid, parseISO, startOfMonth, startOfWeek,} from "date-fns";import { fromZonedTime } from "date-fns-tz";import { formatZonedDate } from "./zonedDate";import type { CalendarEventRecord } from "../types/calendar";
export function formatLocalDate(date: Date): string { return format(date, "yyyy-MM-dd");}export function formatLocalTime(date: Date): string { return format(date, "HH:mm");}export function toISOFromLocal(dateStr: string, timeStr = "00:00"): string { const [year, month, day] = dateStr.split("-").map(Number); const [hour, minute] = timeStr.split(":").map(Number); return new Date(year, month - 1, day, hour || 0, minute || 0).toISOString();}export function toISOFromZoned( dateStr: string, timeStr: string, timeZone: string,): string { const wall = `${dateStr}T${timeStr}:00`; const instant = fromZonedTime(wall, timeZone); if ( !isValid(instant) || formatZonedDate(instant, timeZone, "yyyy-MM-dd'T'HH:mm") !== `${dateStr}T${timeStr}` ) throw new Error("That time does not exist in the selected time zone."); return instant.toISOString();}export function isAllDayEvent(record: CalendarEventRecord): boolean { return record.allDay === true;}function startDate(record: CalendarEventRecord): string { if (record.startDate) return record.startDate; const start = new Date(record.startsAt); return isValid(start) ? formatZonedDate(start, 'UTC', 'yyyy-MM-dd') : '';}function endDate(record: CalendarEventRecord): string { if (record.endDate) return record.endDate; const end = new Date(record.endsAt || record.startsAt); return isValid(end) ? formatZonedDate(end, 'UTC', 'yyyy-MM-dd') : '';}function validDateString(value: string): boolean { return /^\d{4}-\d{2}-\d{2}$/.test(value) && isValid(parseISO(value));}export function eventOccursOnDay( record: CalendarEventRecord, day: Date,): boolean { if (!isValid(day)) return false; const ds = formatLocalDate(day); if (isAllDayEvent(record)) { const start = startDate(record); const end = endDate(record); return ( validDateString(start) && validDateString(end) && end > start && ds >= start && ds < end ); } const start = new Date(record.startsAt); const end = record.endsAt ? new Date(record.endsAt) : new Date(start.getTime() + 3600000); if (!isValid(start) || !isValid(end) || end < start) return false; const dayStart = new Date(day.getFullYear(), day.getMonth(), day.getDate()); if (end.getTime() === start.getTime()) return start >= dayStart && start < addDays(dayStart, 1); return start < addDays(dayStart, 1) && end > dayStart;}export function getEventDaySegment( record: CalendarEventRecord, day: Date,): { startMinute: number; endMinute: number; allDay: boolean } | null { if (!eventOccursOnDay(record, day)) return null; if (isAllDayEvent(record)) return { startMinute: 0, endMinute: 1440, allDay: true }; const start = new Date(record.startsAt); const end = record.endsAt ? new Date(record.endsAt) : new Date(start.getTime() + 3600000); const dayString = formatLocalDate(day); const startString = formatLocalDate(start); const endString = formatLocalDate(end); const wallMinutes = (date: Date) => date.getHours() * 60 + date.getMinutes(); const startMinute = startString < dayString ? 0 : wallMinutes(start); const endMinute = endString > dayString ? 1440 : wallMinutes(end); return { startMinute, endMinute: Math.max(startMinute, endMinute), allDay: false, };}export function getEventTimeLabel( record: CalendarEventRecord, day?: Date,): string { if (isAllDayEvent(record)) return "All Day"; const start = new Date(record.startsAt); const end = record.endsAt ? new Date(record.endsAt) : new Date(start.getTime() + 3600000); if (!isValid(start) || !isValid(end) || end <= start) return ""; if (!day) return `${format(start, "HH:mm")} – ${format(end, "HH:mm")}`; const segment = getEventDaySegment(record, day); if (!segment) return ""; const label = (minutes: number) => `${String(Math.floor(minutes / 60)).padStart(2, "0")}:${String(minutes % 60).padStart(2, "0")}`; return `${label(segment.startMinute)} – ${label(segment.endMinute)}`;}export function formatEventTimeRange( startsAt: string, endsAt?: string,): string { return getEventTimeLabel({ startsAt, endsAt, name: "", $type: "community.lexicon.calendar.event", createdAt: "", });}export function isEndTimeValid( startDateStr: string, startTimeStr: string, endDateStr: string, endTimeStr: string, timeZone?: string,): boolean { try { const start = timeZone ? toISOFromZoned(startDateStr, startTimeStr, timeZone) : toISOFromLocal(startDateStr, startTimeStr); const end = timeZone ? toISOFromZoned(endDateStr, endTimeStr, timeZone) : toISOFromLocal(endDateStr, endTimeStr); return new Date(end).getTime() >= new Date(start).getTime(); } catch { return false; }}export function rescheduleEvent( record: CalendarEventRecord, targetDay: Date, targetHour?: number,): Partial<CalendarEventRecord> { if (isAllDayEvent(record)) { const duration = Math.max( 1, differenceInCalendarDays( parseISO(endDate(record)), parseISO(startDate(record)), ), ); const start = formatLocalDate(targetDay); const end = formatLocalDate(addDays(targetDay, duration)); return { allDay: true, startDate: start, endDate: end, startsAt: `${start}T00:00:00.000Z`, endsAt: `${end}T00:00:00.000Z`, }; } const start = new Date(record.startsAt); const end = record.endsAt ? new Date(record.endsAt) : new Date(start.getTime() + 3600000); const target = new Date(targetDay); target.setHours( targetHour ?? start.getHours(), start.getMinutes(), start.getSeconds(), start.getMilliseconds(), ); return { startsAt: target.toISOString(), endsAt: new Date( target.getTime() + end.getTime() - start.getTime(), ).toISOString(), };}export function getMonthGrid(currentDate: Date) { const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(monthStart); const startDate = startOfWeek(monthStart); const endDate = endOfWeek(monthEnd); const days = eachDayOfInterval({ start: startDate, end: endDate }); return { monthStart, monthEnd, startDate, endDate, days, weeksCount: Math.ceil(days.length / 7), };}