icalendar-events
Version:
A RFC5545 compliant parser for iCalendar VEVENT with time zone support and accurate recurring events generation.
56 lines (55 loc) • 2.67 kB
JavaScript
import { Duration, Interval } from 'luxon';
import { parseDateString } from './parse-ical-datetime.js';
export function parseICalPeriod(line) {
let intervalList = [];
const lineUC = line.trim().toUpperCase();
if (lineUC.includes("VALUE=PERIOD")) {
// Case RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H
// or RDATE;VALUE=PERIOD;TZID=Asia/Tokyo:20230918T090000/20230918T100000,20230920T140000/20230920T150000,20230922T090000/20230922T100000
// or RDATE;VALUE=PERIOD;TZID=Asia/Tokyo:20230918T090000/20230918T100000,20230920T140000/20230920T150000,20230922T090000/PT1H
// for example
const parts = line.split(":");
if (parts.length < 2)
throw new Error(`parseICalDateTime: Invalid DateTime ${line}`);
let tzid = process.env.ICALEVENTS_LOCAL_TZ;
const firstPartUC = parts[0].replace(/tzid/, "TZID");
if (firstPartUC.includes("TZID=")) {
tzid = firstPartUC.split("TZID=")[1].split(";")[0].split(":")[0].trim();
}
// Multiple values separated by ","
parts[1].split(",").forEach((dateString) => {
intervalList.push(parsePeriodString(dateString, tzid));
});
}
else {
throw new Error(`parseICalPeriod: parsed data is not a period: ${line}`);
}
if (intervalList.length === 0 || !intervalList.every((interval) => interval !== null && interval.isValid)) {
throw new Error(`parseICalPeriod: Invalid Period ${line}`);
}
return intervalList;
}
function parsePeriodString(periodString, tzid) {
if (!periodString.includes("/"))
throw new Error(`parsePeriodString: period could not be parsed: ${periodString}`);
const parts = periodString.split("/");
if (parts.length < 2)
throw new Error(`parsePeriodString: invalid period: ${periodString}`);
const periodStart = parseDateString(parts[0], tzid);
let interval = null;
const secondPart = parts[1].trim().toUpperCase();
if (secondPart.startsWith('P')) {
// Case period-start of the form 19970101T180000Z/PT5H30M
const duration = Duration.fromISO(secondPart);
const periodEnd = periodStart.plus(duration);
interval = Interval.fromDateTimes(periodStart, periodEnd);
}
else {
// Case period-explicit of the form 19970101T180000Z/19970102T070000Z
const periodEnd = parseDateString(secondPart, tzid);
interval = Interval.fromDateTimes(periodStart, periodEnd);
}
if (!interval || !interval.isValid)
throw new Error(`parsePeriodString: invalid period: ${periodString}`);
return interval;
}