refactor: fix bugs in calendar logic across duration parsing, RFC compliance, and event handling

- Fix buildDuration() trailing "T" producing invalid ISO 8601 durations
- Fix DURATION_RE missing week (W) support in alerts and invitation parsing
- Fix computeFireTime() end fallback when utcEnd is missing
- Fix recurrenceOverrides patch escaping per RFC 6901 (updateEvent/rsvpEvent)
- Fix layoutOverlappingEvents endMin overflow past 1440
- Fix addDurationToDate() to support weeks and use UTC methods for UTC inputs
- Fix getEffectiveAlerts() null guard on calendarIds
- Fix buildAllDayDuration() DST-safe day calculation using differenceInCalendarDays
- Fix participant matching to check calendarAddress and sendTo (not just email)
- Fix buildParticipantMap() using crypto.randomUUID() instead of hardcoded IDs
- Fix overnight preview negative endMin in week view
- Fix sendImipInvitation() to emit DURATION when utcEnd is absent
- Fix sendImipCancellation() to validate status before sending
- Fix createEvent() to remap all calendarIds for shared calendars
- Fix getCalendarTasks() to clone before mutating @type
- Fix importEvents() error matching to include 'duplicate' and 'conflict'
- Fix looksLikeReply() false positive by requiring organizer + responded attendee
- Fix alert offset regex to require T before minutes
- Fix handleDuplicate() to generate new UID
- Fix formatSnapTime() input clamping
- Replace console.log/error with debug.log/error/warn in iMIP functions
This commit is contained in:
Linus Rath
2026-03-23 16:22:15 +01:00
parent f7ee204262
commit 0effb97691
8 changed files with 153 additions and 80 deletions
+5 -6
View File
@@ -40,9 +40,7 @@ export function normalizeAllDayDuration(duration: string | undefined): string |
}
export function buildAllDayDuration(start: Date, inclusiveEnd: Date): string {
const startDay = startOfDay(start);
const endDay = startOfDay(inclusiveEnd);
const dayCount = Math.max(1, Math.round((endDay.getTime() - startDay.getTime()) / 86400000) + 1);
const dayCount = Math.max(1, differenceInCalendarDays(startOfDay(inclusiveEnd), startOfDay(start)) + 1);
return `P${dayCount}D`;
}
@@ -113,7 +111,7 @@ export function layoutOverlappingEvents(
for (const event of sorted) {
const start = parseISO(event.start);
const startMin = start.getHours() * 60 + start.getMinutes();
const endMin = startMin + Math.max(15, parseDuration(event.duration));
const endMin = Math.min(1440, startMin + Math.max(15, parseDuration(event.duration)));
let placed = false;
for (let col = 0; col < columns.length; col++) {
if (columns[col].every(e => e.end <= startMin)) {
@@ -135,8 +133,9 @@ export function layoutOverlappingEvents(
}
export function formatSnapTime(minutes: number, timeFormat: "12h" | "24h"): string {
const h = Math.floor(minutes / 60);
const m = minutes % 60;
const clamped = Math.max(0, Math.min(1440, minutes));
const h = Math.floor(clamped / 60) % 24;
const m = clamped % 60;
if (timeFormat === "12h") {
return `${h % 12 || 12}:${String(m).padStart(2, "0")} ${h < 12 ? "AM" : "PM"}`;
}