feat: implement parseICS with ical.js
This commit is contained in:
121
src/lib/ical-io.js
Normal file
121
src/lib/ical-io.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import ICAL from 'ical.js'
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Timezone registration (ical.js does not bundle IANA zones)
|
||||
// ------------------------------------------------------------------ //
|
||||
function registerTimezones(vcalendar) {
|
||||
for (const vtz of vcalendar.getAllSubcomponents('vtimezone')) {
|
||||
const tzid = vtz.getFirstPropertyValue('tzid')
|
||||
if (tzid) {
|
||||
ICAL.TimezoneService.register(tzid, new ICAL.Timezone({ component: vtz, tzid }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------ //
|
||||
function pad2(n) {
|
||||
return String(n).padStart(2, '0')
|
||||
}
|
||||
|
||||
function timeToISODate(t) {
|
||||
return `${t.year}-${pad2(t.month)}-${pad2(t.day)}`
|
||||
}
|
||||
|
||||
function timeToHHMM(t) {
|
||||
return `${pad2(t.hour)}:${pad2(t.minute)}`
|
||||
}
|
||||
|
||||
function getTzid(component, propName) {
|
||||
const prop = component.getFirstProperty(propName)
|
||||
if (!prop) return 'UTC'
|
||||
const tzid = prop.getParameter('tzid')
|
||||
return tzid || 'UTC'
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Parse a single VEVENT component -> plain EventModel
|
||||
// ------------------------------------------------------------------ //
|
||||
function parseEvent(veventComp) {
|
||||
const event = new ICAL.Event(veventComp)
|
||||
const start = event.startDate
|
||||
const end = event.endDate
|
||||
|
||||
// Determine tzid
|
||||
const tzid = getTzid(veventComp, 'dtstart')
|
||||
|
||||
// RRULE
|
||||
const recur = veventComp.getFirstPropertyValue('rrule')
|
||||
let rrule = null
|
||||
if (recur) {
|
||||
const bydayArr = recur.getComponent('byday') // string[]
|
||||
let untilDate = null
|
||||
if (recur.until) {
|
||||
const u = recur.until
|
||||
untilDate = `${u.year}-${pad2(u.month)}-${pad2(u.day)}`
|
||||
}
|
||||
rrule = {
|
||||
freq: recur.freq,
|
||||
interval: recur.interval || 1,
|
||||
byday: bydayArr.length > 0 ? bydayArr[0] : null,
|
||||
untilDate,
|
||||
}
|
||||
}
|
||||
|
||||
// EXDATEs
|
||||
const exdates = []
|
||||
for (const prop of veventComp.getAllProperties('exdate')) {
|
||||
for (const t of prop.getValues()) {
|
||||
exdates.push(timeToISODate(t))
|
||||
}
|
||||
}
|
||||
|
||||
// _raw: capture DTSTAMP and any other unmapped properties
|
||||
// Use toICALString() to preserve the compact RFC5545 form ("20260711T085008Z")
|
||||
// rather than toString()'s ISO 8601 form ("2026-07-11T08:50:08Z").
|
||||
const _raw = {}
|
||||
const dtstamp = veventComp.getFirstPropertyValue('dtstamp')
|
||||
if (dtstamp) _raw.dtstamp = dtstamp.toICALString()
|
||||
|
||||
return {
|
||||
uid: veventComp.getFirstPropertyValue('uid') || '',
|
||||
summary: veventComp.getFirstPropertyValue('summary') || '',
|
||||
location: veventComp.getFirstPropertyValue('location') || '',
|
||||
description: veventComp.getFirstPropertyValue('description') || '',
|
||||
dtstartDate: timeToISODate(start),
|
||||
dtstartTime: timeToHHMM(start),
|
||||
dtendTime: timeToHHMM(end),
|
||||
tzid,
|
||||
rrule,
|
||||
exdates,
|
||||
_raw,
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Parse full ICS text -> { meta, events }
|
||||
// ------------------------------------------------------------------ //
|
||||
export function parseICS(text) {
|
||||
const jcal = ICAL.parse(text)
|
||||
const vcalendar = new ICAL.Component(jcal)
|
||||
|
||||
// Register timezones from VTIMEZONEs before constructing zoned Times
|
||||
registerTimezones(vcalendar)
|
||||
|
||||
// Meta
|
||||
const meta = {
|
||||
prodid: vcalendar.getFirstPropertyValue('prodid') || '',
|
||||
version: vcalendar.getFirstPropertyValue('version') || '2.0',
|
||||
calscale: vcalendar.getFirstPropertyValue('calscale') || 'GREGORIAN',
|
||||
method: vcalendar.getFirstPropertyValue('method') || null,
|
||||
xWrCalname: vcalendar.getFirstPropertyValue('x-wr-calname') || null,
|
||||
xWrTimezone: vcalendar.getFirstPropertyValue('x-wr-timezone') || null,
|
||||
vtimezones: vcalendar.getAllSubcomponents('vtimezone'),
|
||||
}
|
||||
|
||||
// Events
|
||||
const events = vcalendar.getAllSubcomponents('vevent').map(parseEvent)
|
||||
|
||||
return { meta, events }
|
||||
}
|
||||
574
test/golden-org.ics
Normal file
574
test/golden-org.ics
Normal file
@@ -0,0 +1,574 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Allocate//iCal4j 1.0//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Australia/Melbourne
|
||||
LAST-MODIFIED:20260306T231828Z
|
||||
TZURL:https://www.tzurl.org/zoneinfo-outlook/Australia/Melbourne
|
||||
X-LIC-LOCATION:Australia/Melbourne
|
||||
BEGIN:STANDARD
|
||||
TZNAME:AEST
|
||||
TZOFFSETFROM:+1100
|
||||
TZOFFSETTO:+1000
|
||||
DTSTART:19700405T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
TZNAME:AEDT
|
||||
TZOFFSETFROM:+1000
|
||||
TZOFFSETTO:+1100
|
||||
DTSTART:19701004T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260727T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260727T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260803T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260803T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid1
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260810T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260810T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid2
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260817T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260817T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid3
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260824T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260824T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid4
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260831T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260831T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid5
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260907T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260907T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid6
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260914T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260914T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid7
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260928T180000
|
||||
DTEND;TZID=Australia/Melbourne:20260928T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid8
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261005T180000
|
||||
DTEND;TZID=Australia/Melbourne:20261005T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid9
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261012T180000
|
||||
DTEND;TZID=Australia/Melbourne:20261012T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid10
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261019T180000
|
||||
DTEND;TZID=Australia/Melbourne:20261019T200000
|
||||
SUMMARY:ACC INFO SYS\, Tutorial
|
||||
LOCATION:CA_B_B471
|
||||
DESCRIPTION:ACF2400_CA_S2_ON-CAMPUS\, Tutorial\, 04_OnCampus\nACC INFO SYS\nStaff: Staff_151\nLocation: CA_B_B471
|
||||
UID:uid11
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260727T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260727T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid12
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260803T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260803T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid13
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260810T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260810T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid14
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260817T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260817T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid15
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260824T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260824T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid16
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260907T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260907T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid17
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260914T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260914T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid18
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260928T093000
|
||||
DTEND;TZID=Australia/Melbourne:20260928T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid19
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261005T093000
|
||||
DTEND;TZID=Australia/Melbourne:20261005T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid20
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261012T093000
|
||||
DTEND;TZID=Australia/Melbourne:20261012T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid21
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261019T093000
|
||||
DTEND;TZID=Australia/Melbourne:20261019T110000
|
||||
SUMMARY:BUS LAW\, Tutorial
|
||||
LOCATION:CA_H_H212
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Tutorial\, 23_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_H_H212
|
||||
UID:uid22
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260812T130000
|
||||
DTEND;TZID=Australia/Melbourne:20260812T150000
|
||||
SUMMARY:BUS LAW\, Workshop
|
||||
LOCATION:CA_K_K309
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Workshop\, 01_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_K_K309
|
||||
UID:uid23
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260902T130000
|
||||
DTEND;TZID=Australia/Melbourne:20260902T150000
|
||||
SUMMARY:BUS LAW\, Workshop
|
||||
LOCATION:CA_K_K309
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Workshop\, 01_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_K_K309
|
||||
UID:uid24
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260930T130000
|
||||
DTEND;TZID=Australia/Melbourne:20260930T150000
|
||||
SUMMARY:BUS LAW\, Workshop
|
||||
LOCATION:CA_K_K309
|
||||
DESCRIPTION:BTF1010_CA_S2_ON-CAMPUS\, Workshop\, 01_OnCampus\nBUS LAW\nStaff: -\nLocation: CA_K_K309
|
||||
UID:uid25
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260729T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260729T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid26
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260805T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260805T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid27
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260812T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260812T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid28
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260819T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260819T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid29
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260826T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260826T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid30
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260909T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260909T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid31
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260916T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260916T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid32
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260930T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260930T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid33
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261007T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261007T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid34
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261014T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261014T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid35
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261021T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261021T120000
|
||||
SUMMARY:MICRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1100_CA_S2_ON-CAMPUS\, Workshop\, 04_OnCampus\nMICRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid36
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260731T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260731T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid37
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260807T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260807T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid38
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260814T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260814T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid39
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260821T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260821T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid40
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260828T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260828T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid41
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260904T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260904T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid42
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260911T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260911T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid43
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260918T120000
|
||||
DTEND;TZID=Australia/Melbourne:20260918T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid44
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261002T120000
|
||||
DTEND;TZID=Australia/Melbourne:20261002T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid45
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261009T120000
|
||||
DTEND;TZID=Australia/Melbourne:20261009T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid46
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261016T120000
|
||||
DTEND;TZID=Australia/Melbourne:20261016T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid47
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261023T120000
|
||||
DTEND;TZID=Australia/Melbourne:20261023T130000
|
||||
SUMMARY:MACRO\, Applied
|
||||
LOCATION:Online\, Melbourne
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Applied\, 01_OnlineRealTime\nMACRO\nStaff: -\nLocation: Online\, Melbourne
|
||||
UID:uid48
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260730T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260730T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid49
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260806T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260806T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid50
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260813T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260813T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid51
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260820T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260820T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid52
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260827T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260827T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid53
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260903T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260903T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid54
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260910T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260910T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid55
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20260917T100000
|
||||
DTEND;TZID=Australia/Melbourne:20260917T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid56
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261001T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261001T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid57
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261008T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261008T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid58
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261015T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261015T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid59
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20260711T085008Z
|
||||
DTSTART;TZID=Australia/Melbourne:20261022T100000
|
||||
DTEND;TZID=Australia/Melbourne:20261022T120000
|
||||
SUMMARY:MACRO\, Workshop
|
||||
LOCATION:CA_H_H116
|
||||
DESCRIPTION:ECF1200_CA_S2_ON-CAMPUS\, Workshop\, 05_OnCampus\nMACRO\nStaff: -\nLocation: CA_H_H116
|
||||
UID:uid60
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
50
test/ical-io.test.js
Normal file
50
test/ical-io.test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { parseICS } from '../src/lib/ical-io.js'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const GOLDEN = readFileSync(join(__dirname, 'golden-org.ics'), 'utf-8')
|
||||
|
||||
describe('parseICS', () => {
|
||||
const { meta, events } = parseICS(GOLDEN)
|
||||
|
||||
it('parses calendar meta', () => {
|
||||
expect(meta.prodid).toBe('-//Allocate//iCal4j 1.0//EN')
|
||||
expect(meta.version).toBe('2.0')
|
||||
expect(meta.calscale).toBe('GREGORIAN')
|
||||
expect(meta.vtimezones.length).toBe(1)
|
||||
})
|
||||
|
||||
it('parses all 61 events', () => {
|
||||
expect(events).toHaveLength(61)
|
||||
})
|
||||
|
||||
it('parses first event fields correctly', () => {
|
||||
const e = events[0]
|
||||
expect(e.summary).toBe('ACC INFO SYS, Tutorial')
|
||||
expect(e.location).toBe('CA_B_B471')
|
||||
expect(e.dtstartDate).toBe('2026-07-27')
|
||||
expect(e.dtstartTime).toBe('18:00')
|
||||
expect(e.dtendTime).toBe('20:00')
|
||||
expect(e.tzid).toBe('Australia/Melbourne')
|
||||
expect(e.rrule).toBeNull()
|
||||
expect(e.exdates).toEqual([])
|
||||
})
|
||||
|
||||
it('preserves description with escaped commas', () => {
|
||||
const e = events[0]
|
||||
expect(e.description).toContain('ACF2400_CA_S2_ON-CAMPUS')
|
||||
expect(e.description).toContain('ACC INFO SYS')
|
||||
})
|
||||
|
||||
it('captures dtstamp in _raw', () => {
|
||||
expect(events[0]._raw.dtstamp).toBe('20260711T085008Z')
|
||||
})
|
||||
|
||||
it('events have UIDs', () => {
|
||||
expect(events[0].uid).toBe('uid0')
|
||||
expect(events[60].uid).toBe('uid60')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user