UNPKG

@heinlein-video/rrule

Version:

rrule fork. Includes the src/ folder for typescript sourceMaps

148 lines (127 loc) 3.75 kB
import { Options, Frequency } from './types' import { Weekday } from './weekday' import { untilStringToDate } from './dateutil' import { Days } from './rrule' export function parseString(rfcString: string): Partial<Options> { const options = rfcString .split('\n') .map(parseLine) .filter((x) => x !== null) return { ...options[0], ...options[1] } } export function parseDtstart(line: string) { const options: Partial<Options> = {} const dtstartWithZone = /DTSTART(?:;TZID=([^:=]+?))?(?::|=)([^;\s]+)/i.exec( line ) if (!dtstartWithZone) { return options } const [, tzid, dtstart] = dtstartWithZone if (tzid) { options.tzid = tzid } options.dtstart = untilStringToDate(dtstart) return options } function parseLine(rfcString: string) { rfcString = rfcString.replace(/^\s+|\s+$/, '') if (!rfcString.length) return null const header = /^([A-Z]+?)[:;]/.exec(rfcString.toUpperCase()) if (!header) { return parseRrule(rfcString) } const [, key] = header switch (key.toUpperCase()) { case 'RRULE': case 'EXRULE': return parseRrule(rfcString) case 'DTSTART': return parseDtstart(rfcString) default: throw new Error(`Unsupported RFC prop ${key} in ${rfcString}`) } } function parseRrule(line: string) { const strippedLine = line.replace(/^RRULE:/i, '') const options = parseDtstart(strippedLine) const attrs = line.replace(/^(?:RRULE|EXRULE):/i, '').split(';') attrs.forEach((attr) => { const [key, value] = attr.split('=') switch (key.toUpperCase()) { case 'FREQ': options.freq = Frequency[value.toUpperCase() as keyof typeof Frequency] break case 'WKST': options.wkst = Days[value.toUpperCase() as keyof typeof Days] break case 'COUNT': case 'INTERVAL': case 'BYSETPOS': case 'BYMONTH': case 'BYMONTHDAY': case 'BYYEARDAY': case 'BYWEEKNO': case 'BYHOUR': case 'BYMINUTE': case 'BYSECOND': const num = parseNumber(value) const optionKey = key.toLowerCase() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options[optionKey] = num break case 'BYWEEKDAY': case 'BYDAY': options.byweekday = parseWeekday(value) break case 'DTSTART': case 'TZID': // for backwards compatibility const dtstart = parseDtstart(line) options.tzid = dtstart.tzid options.dtstart = dtstart.dtstart break case 'UNTIL': options.until = untilStringToDate(value) break case 'BYEASTER': options.byeaster = Number(value) break default: throw new Error("Unknown RRULE property '" + key + "'") } }) return options } function parseNumber(value: string) { if (value.indexOf(',') !== -1) { const values = value.split(',') return values.map(parseIndividualNumber) } return parseIndividualNumber(value) } function parseIndividualNumber(value: string) { if (/^[+-]?\d+$/.test(value)) { return Number(value) } return value } function parseWeekday(value: string) { const days = value.split(',') return days.map((day) => { if (day.length === 2) { // MO, TU, ... return Days[day as keyof typeof Days] // wday instanceof Weekday } // -1MO, +3FR, 1SO, 13TU ... const parts = day.match(/^([+-]?\d{1,2})([A-Z]{2})$/) if (!parts || parts.length < 3) { throw new SyntaxError(`Invalid weekday string: ${day}`) } const n = Number(parts[1]) const wdaypart = parts[2] as keyof typeof Days const wday = Days[wdaypart].weekday return new Weekday(wday, n) }) }