Skip to content

Commit 0c6d416

Browse files
committed
Fix recurring date parsing
1 parent f32afef commit 0c6d416

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

.changeset/sparkly-camels-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@plotday/tool-google-calendar": patch
3+
---
4+
5+
Fixed: Recurring date parsing

tools/google-calendar/src/google-api.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,53 @@ export function parseRRule(recurrence?: string[]): string | undefined {
120120
return rrule ? rrule.substring(6) : undefined; // Remove 'RRULE:' prefix
121121
}
122122

123+
/**
124+
* Parses an iCalendar date string into a JavaScript Date.
125+
* Handles formats: YYYYMMDD, YYYYMMDDTHHMMSS, YYYYMMDDTHHMMSSZ
126+
*/
127+
function parseICalDate(dateStr: string): Date | null {
128+
// Remove any whitespace
129+
const d = dateStr.trim();
130+
131+
// All-day date: YYYYMMDD (8 chars)
132+
if (/^\d{8}$/.test(d)) {
133+
const year = d.slice(0, 4);
134+
const month = d.slice(4, 6);
135+
const day = d.slice(6, 8);
136+
return new Date(`${year}-${month}-${day}`);
137+
}
138+
139+
// DateTime with or without Z: YYYYMMDDTHHMMSS or YYYYMMDDTHHMMSSZ
140+
if (/^\d{8}T\d{6}Z?$/.test(d)) {
141+
const year = d.slice(0, 4);
142+
const month = d.slice(4, 6);
143+
const day = d.slice(6, 8);
144+
const hour = d.slice(9, 11);
145+
const minute = d.slice(11, 13);
146+
const second = d.slice(13, 15);
147+
const isUtc = d.endsWith("Z");
148+
return new Date(
149+
`${year}-${month}-${day}T${hour}:${minute}:${second}${isUtc ? "Z" : ""}`
150+
);
151+
}
152+
153+
// Fallback: try native parsing
154+
const parsed = new Date(d);
155+
return isNaN(parsed.getTime()) ? null : parsed;
156+
}
157+
123158
export function parseExDates(recurrence?: string[]): Date[] {
124159
if (!recurrence?.length) return [];
125160

126161
return recurrence
127162
.filter((rule) => rule.startsWith("EXDATE"))
128163
.flatMap((rule) => {
129164
const dates = rule.split(":")[1];
130-
return dates ? dates.split(",").map((d) => new Date(d)) : [];
165+
if (!dates) return [];
166+
return dates
167+
.split(",")
168+
.map((d) => parseICalDate(d))
169+
.filter((d): d is Date => d !== null);
131170
});
132171
}
133172

@@ -138,7 +177,11 @@ export function parseRDates(recurrence?: string[]): Date[] {
138177
.filter((rule) => rule.startsWith("RDATE"))
139178
.flatMap((rule) => {
140179
const dates = rule.split(":")[1];
141-
return dates ? dates.split(",").map((d) => new Date(d)) : [];
180+
if (!dates) return [];
181+
return dates
182+
.split(",")
183+
.map((d) => parseICalDate(d))
184+
.filter((d): d is Date => d !== null);
142185
});
143186
}
144187

0 commit comments

Comments
 (0)