From 426d6d1dc6ec237fd0a010a486a1c9836722b8da Mon Sep 17 00:00:00 2001 From: sstent Date: Fri, 20 Feb 2026 11:37:48 -0800 Subject: [PATCH] fix(icalendar): Support object rrule by converting to string --- icalendar.ts | 21 +++++++++++++++------ icalendar_test.ts | 8 ++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/icalendar.ts b/icalendar.ts index 6ad640e..98a702a 100644 --- a/icalendar.ts +++ b/icalendar.ts @@ -154,14 +154,23 @@ export function expandRecurrences(icsEvent: any, windowDays = 365): any[] { const rruleStr = icsEvent.rrule || (icsEvent as any).recurrenceRule; if (!rruleStr) return [icsEvent]; - if (typeof rruleStr !== "string") { - console.warn(`[iCalendar] Invalid rrule type (${typeof rruleStr}) for event "${icsEvent.summary || "Untitled"}". Treating as non-recurring.`); - return [icsEvent]; - } - try { const set = new RRuleSet(); - const cleanRule = rruleStr.replace(/^RRULE:/i, ""); + + let cleanRule = ""; + if (typeof rruleStr === "string") { + cleanRule = rruleStr.replace(/^RRULE:/i, ""); + } else if (typeof rruleStr === "object" && rruleStr !== null) { + // Handle object rrule (e.g. from ts-ics) by converting back to string + cleanRule = Object.entries(rruleStr) + .map(([k, v]) => `${k.toUpperCase()}=${v}`) + .join(";"); + } else { + console.warn(`[iCalendar] Invalid rrule type (${typeof rruleStr}) for event "${icsEvent.summary || "Untitled"}". Treating as non-recurring.`); + return [icsEvent]; + } + + // We need to provide DTSTART if it's not in the string // We need to provide DTSTART if it's not in the string const dtstart = new Date(icsEvent.start.includes("Z") ? icsEvent.start : icsEvent.start + "Z"); diff --git a/icalendar_test.ts b/icalendar_test.ts index ad97bcd..262455e 100644 --- a/icalendar_test.ts +++ b/icalendar_test.ts @@ -196,13 +196,13 @@ Deno.test("expandRecurrences - object rrule (Reproduction of missing events)", ( try { const results = expandRecurrences(icsEvent, 30); - // Currently, it returns [icsEvent] (length 1) and logs a warning. - // This confirms that it is NOT expanding it. - assertEquals(results.length, 1); + // Should now return multiple occurrences + assert(results.length > 1, `Expected > 1 occurrences, got ${results.length}`); + assertEquals(results[0].recurrent, true); } finally { console.warn = originalConsoleWarn; } - assert(warningLogged, "Should have logged a warning for object rrule"); + assert(!warningLogged, "Should NOT have logged a warning for object rrule"); });