icalendar-events
Version:
A RFC5545 compliant parser for iCalendar VEVENT with time zone support and accurate recurring events generation.
68 lines (50 loc) • 2.61 kB
text/typescript
import { DateTime } from 'luxon'
export function parseICalDateTime(line: string): DateTime[] {
let dateTimeList: DateTime[] = []
const lineUC: string = line.trim().toUpperCase()
if(lineUC.includes("UNTIL=")) {
//case UNTIL=20130130 or UNTIL=20130130T230000 or UNTIL=20130130T230000Z
const parts: string[] = lineUC.split("UNTIL=")
if(parts.length < 2) throw new Error(`parseICalDateTime: Invalid DateTime ${line}`)
// UNTIL can have only 1 value
dateTimeList.push(parseDateString(parts[1]))
} else if(line.includes(":")) {
// Case DTSTART;TZID=Asia/Tokyo:20201027T163000 or DTSTART:20201027T163000 or DTSTART:20201027T163000Z
// or RDATE;VALUE=DATE:19970304,19970504,19970704,19970904
// or EXDATE:20201027T163000Z,20201127T163000Z,20201227T163000Z
// or RDATE;TZID=Asia/Tokyo:20201027T163000,20201127T163000,20201227T163000 for example
// (case RDATE;VALUE=PERIOD is handled in parseICalPeriod function)
const parts: string[] = line.split(":")
if(parts.length < 2) throw new Error(`parseICalDateTime: Invalid DateTime ${line}`)
let tzid: string | undefined = process.env.ICALEVENTS_LOCAL_TZ
if (parts[0].replace(/tzid/,"TZID").includes("TZID=")) {
tzid = parts[0].split("TZID=")[1].split(";")[0].split(":")[0].trim()
}
// Multiple values separated by ","
parts[1].split(",").forEach( (dateString: string): void => {
dateTimeList.push(parseDateString(dateString, tzid))
})
} else {
throw new Error(`parseICalDateTime: datetime could not be parsed ${line}`)
}
if(dateTimeList.length === 0 || !dateTimeList.every((dateTime: DateTime) => dateTime !== null && dateTime.isValid)) {
throw new Error(`parseICalDateTime: Invalid DateTime ${line}`)
}
return dateTimeList
}
export function parseDateString(dateString: string, tzid?: string): DateTime {
let dateStringTrimmed: string = dateString.trim().toUpperCase()
if(dateStringTrimmed.match(/^\d{8}T\d{6}Z$/) !== null) {
return DateTime.fromFormat(dateStringTrimmed, "yyyyMMdd'T'HHmmss'Z'", {zone: 'UTC'})
} else if(dateStringTrimmed.match(/^\d{8}T\d{6}$/) !== null) {
tzid = tzid ?? 'local'
return DateTime.fromFormat(dateStringTrimmed, "yyyyMMdd'T'HHmmss", {zone: tzid})
} else if(dateStringTrimmed.match(/^\d{8}$/) !== null) {
//Date so Time zone is set to UTC
const date: DateTime = DateTime.fromFormat(dateStringTrimmed, "yyyyMMdd", {zone: 'UTC'})
date.isDate = true
return date
} else {
throw new Error(`parseICalDateTime: datetime could not be parsed: ${dateString}`)
}
}