Parse iCal
Parse iCal/ICS calendar files into structured events
Source Code
import fs from "fs";
import path from "path";
import ICAL from "ical.js";
const [inputPath, outputPath, startDate = "", endDate = ""] = process.argv.slice(2);
if (!inputPath || !outputPath) {
console.error("Usage: inputPath outputPath [startDate] [endDate]");
process.exit(1);
}
try {
console.log(`Parsing iCal: ${inputPath}...`);
const icsContent = fs.readFileSync(inputPath, "utf-8");
const jcalData = ICAL.parse(icsContent);
const comp = new ICAL.Component(jcalData);
const vevents = comp.getAllSubcomponents("vevent");
console.log(` Found ${vevents.length} events`);
const startFilter = startDate ? new Date(startDate) : null;
const endFilter = endDate ? new Date(endDate) : null;
const events = [];
for (const vevent of vevents) {
const event = new ICAL.Event(vevent);
const eventStart = event.startDate?.toJSDate();
const eventEnd = event.endDate?.toJSDate();
// Apply date filters
if (startFilter && eventEnd && eventEnd < startFilter) continue;
if (endFilter && eventStart && eventStart > endFilter) continue;
// Extract attendees
const attendees = vevent.getAllProperties("attendee").map((att) => {
const params = att.jCal[1] || {};
return {
email: att.getFirstValue()?.replace("mailto:", "") || null,
name: params.cn || null,
role: params.role || null,
status: params.partstat || null,
};
});
events.push({
uid: event.uid || null,
summary: event.summary || null,
description: event.description || null,
location: event.location || null,
start: eventStart?.toISOString() || null,
end: eventEnd?.toISOString() || null,
allDay: event.startDate?.isDate || false,
organizer: event.organizer || null,
attendees,
recurrence: event.isRecurring() ? "recurring" : "single",
status: vevent.getFirstPropertyValue("status") || null,
});
}
// Sort by start date
events.sort((a, b) => {
if (!a.start) return 1;
if (!b.start) return -1;
return new Date(a.start) - new Date(b.start);
});
const result = {
calendar: {
name: comp.getFirstPropertyValue("x-wr-calname") || null,
timezone: comp.getFirstPropertyValue("x-wr-timezone") || null,
},
events,
};
// Ensure output directory exists
const dir = path.dirname(outputPath);
if (dir && dir !== ".") {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
console.log(`\nā Parsed iCal`);
console.log(` Events: ${events.length}`);
if (startFilter || endFilter) {
console.log(` Date range: ${startDate || "any"} to ${endDate || "any"}`);
}
console.log(` Written to: ${outputPath}`);
console.log(
JSON.stringify({
success: true,
inputPath,
outputPath,
eventCount: events.length,
calendarName: result.calendar.name,
})
);
} catch (error) {
console.error("Error:", error.message);
process.exit(1);
}