Skip to content

Commit

Permalink
Merge branch 'master' into sujal/correct-multiple-tzids
Browse files Browse the repository at this point in the history
  • Loading branch information
jens-maus committed Feb 28, 2024
2 parents 6e75f29 + ae5f786 commit 7bd0e62
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 33 deletions.
56 changes: 28 additions & 28 deletions ical.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,12 @@ const geoParameter = function (name) {
};

const categoriesParameter = function (name) {
const separatorPattern = /\s*,\s*/g;
return function (value, parameters, curr) {
storeParameter(value, parameters, curr);
if (curr[name] === undefined) {
curr[name] = value ? value.split(separatorPattern) : [];
curr[name] = value ? value.split(',').map(s => s.trim()) : [];
} else if (value) {
curr[name] = curr[name].concat(value.split(separatorPattern));
curr[name] = curr[name].concat(value.split(',').map(s => s.trim()));
}

return curr;
Expand All @@ -362,9 +361,8 @@ const categoriesParameter = function (name) {
// TODO: See if this causes any problems with events that recur multiple times a day.
const exdateParameter = function (name) {
return function (value, parameters, curr) {
const separatorPattern = /\s*,\s*/g;
curr[name] = curr[name] || [];
const dates = value ? value.split(separatorPattern) : [];
const dates = value ? value.split(',').map(s => s.trim()) : [];
for (const entry of dates) {
const exdate = [];
dateParameter(name)(entry, parameters, exdate);
Expand Down Expand Up @@ -455,33 +453,30 @@ module.exports = {
const par = stack.pop();

if (!curr.end) { // RFC5545, 3.6.1
if (curr.datetype === 'date-time') {
curr.end = new Date(curr.start.getTime());
// If the duration is not set
} else if (curr.duration === undefined) {
// Set the end to the start plus one day RFC5545, 3.6.1
curr.end = moment.utc(curr.start).add(1, 'days').toDate(); // New Date(moment(curr.start).add(1, 'days'));
} else {
// Set the end according to the datetype of event
curr.end = (curr.datetype === 'date-time') ? new Date(curr.start.getTime()) : moment.utc(curr.start).add(1, 'days').toDate();

// If there was a duration specified
if (curr.duration !== undefined) {
const durationUnits =
{
// Y: 'years',
// M: 'months',
W: 'weeks',
D: 'days',
H: 'hours',
M: 'minutes',
S: 'seconds'
};
{
// Y: 'years',
// M: 'months',
W: 'weeks',
D: 'days',
H: 'hours',
M: 'minutes',
S: 'seconds'
};
// Get the list of duration elements
const r = curr.duration.match(/-?\d+[YMWDHS]/g);
const r = curr.duration.match(/-?\d{1,10}[YMWDHS]/g);

// Use the duration to create the end value, from the start
let newend = moment.utc(curr.start);
// Is the 1st character a negative sign?
const indicator = curr.duration.startsWith('-') ? -1 : 1;
// Process each element
for (const d of r) {
newend = newend.add(Number.parseInt(d, 10) * indicator, durationUnits[d.slice(-1)]);
}

newend = newend.add(Number.parseInt(r, 10) * indicator, durationUnits[r.toString().slice(-1)]);
// End is a Date type, not moment
curr.end = newend.toDate();
}
}
Expand Down Expand Up @@ -558,9 +553,14 @@ module.exports = {

// One more specific fix - in the case that an RRULE entry shows up after a RECURRENCE-ID entry,
// let's make sure to clear the recurrenceid off the parent field.
if (typeof par[curr.uid].rrule !== 'undefined' && typeof par[curr.uid].recurrenceid !== 'undefined') {
if (curr.uid !== '__proto__' &&
typeof par[curr.uid].rrule !== 'undefined' &&
typeof par[curr.uid].recurrenceid !== 'undefined') {
delete par[curr.uid].recurrenceid;
}
} else if (component === 'VALARM' && (par.type === 'VEVENT' || par.type === 'VTODO')) {
par.alarms ??= [];
par.alarms.push(curr);
} else {
const id = uuid();
par[id] = curr;
Expand Down
44 changes: 44 additions & 0 deletions node-ical.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,48 @@ declare module 'node-ical' {

type TimeZoneDictionary = Record<string, TimeZoneDef | undefined>;

/**
* Example :
* TRIGGER:-P15M
* TRIGGER;RELATED=END:P5M
* TRIGGER;VALUE=DATE-TIME:19980101T050000Z
*/
type Trigger = string;

/**
* https://www.kanzaki.com/docs/ical/valarm.html
*/
export interface VAlarm extends BaseComponent {
type: 'VALARM';
action: 'AUDIO' | 'DISPLAY' | 'EMAIL' | 'PROCEDURE';
trigger: Trigger;
description?: string;
/**
* https://www.kanzaki.com/docs/ical/repeat.html
*/
repeat?: number;
/**
* Time between repeated alarms (if repeat is set)
* DURATION:PT15M
*/
duration?;
/**
* Everything except DISPLAY
* https://www.kanzaki.com/docs/ical/attach.html
*/
attach;
/**
* For action = email
*/
summary?: string;

/**
* For action = email
*/
attendee?: Attendee;

}

export interface VEvent extends BaseComponent {
type: 'VEVENT';
method: Method;
Expand Down Expand Up @@ -91,6 +133,8 @@ declare module 'node-ical' {
exdate: any;
geo: any;
recurrenceid: any;

alarms?: VAlarm[];
}

/**
Expand Down
33 changes: 32 additions & 1 deletion test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,38 @@ vows
}
}
}

},
'with test_date_time_duration.ics': {
topic() {
return ical.parseFile('./test/test_date_time_duration.ics');
},
'using an event containing a start date but no end, only duration': {
'topic'(events) {
return _.select(_.values(events), x => {
return x.summary === 'period test2';
})[0];
},
'it uses the start/end of the event'(event) {
assert.equal(event.start.toJSON(), '2024-02-15T09:00:00.000Z');
assert.equal(event.end.toJSON(), '2024-02-15T09:15:00.000Z');
}
}
},
'with test_date_duration.ics': {
topic() {
return ical.parseFile('./test/test_date_duration.ics');
},
'using an event containing a start date but no end, only duration': {
'topic'(events) {
return _.select(_.values(events), x => {
return x.summary === 'period test2';
})[0];
},
'ends one week later'(event) {
assert.equal(event.start.toDateString(), new Date(2024, 1, 15).toDateString());
assert.equal(event.end.toDateString(), new Date(2024, 1, 22).toDateString());
}
}
}
})
.export(module);
43 changes: 41 additions & 2 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ vows
},
'task completed'(task) {
assert.equal(task.summary, 'Event with an alarm');
assert.equal(task.alarms?.length, 1);
const alarm = task.alarms[0];
assert.equal(alarm.description, 'Reminder');
assert.equal(alarm.action, 'DISPLAY');
assert.equal(alarm.trigger.val, '-PT5M');
}
}
},
Expand All @@ -318,7 +323,9 @@ vows
return ical.parseFile('./test10.ics');
},
'grabbing custom properties': {
topic() {}
topic() {
//
}
}
},

Expand Down Expand Up @@ -883,7 +890,7 @@ vows
topic() {
ical.fromURL('http://255.255.255.255/', {}, this.callback);
},
'are passed back to the callback'(error, result) {
'are passed back to the callback': (error, result) => {
assert.instanceOf(error, Error);
if (!error) {
console.log('>E:', error, result);
Expand Down Expand Up @@ -999,6 +1006,38 @@ vows
assert.equal(topic.end.toJSON(), '2024-06-28T06:00:00.000Z');
}
}
},
'with test_date_time_duration.ics': {
topic() {
return ical.parseFile('./test/test_date_time_duration.ics');
},
'using an event containing a start date but no end, only duration': {
'topic'(events) {
return _.select(_.values(events), x => {
return x.summary === 'period test2';
})[0];
},
'it uses the start/end of the event'(event) {
assert.equal(event.start.toJSON(), '2024-02-15T09:00:00.000Z');
assert.equal(event.end.toJSON(), '2024-02-15T09:15:00.000Z');
}
}
},
'with test_date_duration.ics': {
topic() {
return ical.parseFile('./test/test_date_duration.ics');
},
'using an event containing a start date but no end, only duration': {
'topic'(events) {
return _.select(_.values(events), x => {
return x.summary === 'period test2';
})[0];
},
'ends one week later'(event) {
assert.equal(event.start.toDateString(), new Date(2024, 1, 15).toDateString());
assert.equal(event.end.toDateString(), new Date(2024, 1, 22).toDateString());
}
}
}
})
.export(module);
4 changes: 2 additions & 2 deletions test/test2.ics
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ DTSTAMP:20021028T011706Z
SUMMARY:Coffee with Jason
UID:EC9439B1-FF65-11D6-9973-003065F99D04
DTEND;TZID=US/Pacific:20021028T150000
END:VEVENT
BEGIN:VALARM
TRIGGER;VALUE=DURATION:-P1D
ACTION:DISPLAY
DESCRIPTION:Event reminder
END:VALARM
END:VEVENT
BEGIN:VEVENT
SEQUENCE:1
DTSTAMP:20021128T012034Z
Expand All @@ -43,7 +43,6 @@ ATTENDEE;PARTSTAT=ACCEPTED:MAILTO:jqpublic@host.com
DUE:19980415T235959
STATUS:NEEDS-ACTION
SUMMARY:Submit Income Taxes
END:VTODO
BEGIN:VALARM
ACTION:AUDIO
TRIGGER:19980403T120000
Expand All @@ -52,6 +51,7 @@ ATTACH;FMTTYPE=audio/basic:http://host.com/pub/audio-
REPEAT:4
DURATION:PT1H
END:VALARM
END:VTODO
BEGIN:VJOURNAL
DTSTAMP:19970324T120000Z
UID:uid5@host1.com
Expand Down
18 changes: 18 additions & 0 deletions test/test_date_duration.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
PRODID:-//Synology//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20240215T101655
LAST-MODIFIED:20240215T101655
DTSTAMP:20240215T101655
UID:20240215T101655-b6e4bbe9@192.168.178.100
SEQUENCE:2
SUMMARY:period test2
TRANSP:OPAQUE
DESCRIPTION:
LOCATION:Kriftel
X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-TITLE=Kriftel: geo:50.083558\,8.4693855
DTSTART:20240215
DURATION:PT1W
END:VEVENT
END:VCALENDAR
18 changes: 18 additions & 0 deletions test/test_date_time_duration.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
PRODID:-//Synology//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20240215T101655
LAST-MODIFIED:20240215T101655
DTSTAMP:20240215T101655
UID:20240215T101655-b6e4bbe9@192.168.178.100
SEQUENCE:2
SUMMARY:period test2
TRANSP:OPAQUE
DESCRIPTION:
LOCATION:Kriftel
X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-TITLE=Kriftel: geo:50.083558\,8.4693855
DTSTART:20240215T090000Z
DURATION:PT15M
END:VEVENT
END:VCALENDAR

0 comments on commit 7bd0e62

Please sign in to comment.