Skip to content

Commit e19703a

Browse files
refactor: flatten parseAndExpandEvents return value, expose isFullDay, remove ri alias
parseAndExpandEvents now returns a flat array instead of {events, occurrences}. Events and occurrences have identical shapes; the split was a historical artefact of the ical.js migration. Add isFullDay: boolean to the event shape, computed from node-ical's event.isFullDay / start.dateOnly. Removes the format("HHmmss")==="000000" heuristic from node_helper.js. Remove the redundant `ri = item` alias in node_helper.js forEach.
1 parent be95ed9 commit e19703a

3 files changed

Lines changed: 41 additions & 47 deletions

File tree

lib/ical-utils.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ const parseAndExpandEvents = (iCalData, startDate, endDate, maxIterations = 1000
151151
location: stringValue(occurrence.location) || stringValue(occurrenceEvent.location),
152152
description: stringValue(occurrence.description) || stringValue(occurrenceEvent.description),
153153
uid: stringValue(occurrence.uid) || stringValue(occurrenceEvent.uid),
154+
isFullDay: Boolean(occurrence.isFullDay || occurrence.start?.dateOnly),
154155
isRecurring: true,
155156
duration: durationSeconds,
156157
status: eventProp(occurrence, "status") || eventProp(occurrenceEvent, "status"),
@@ -170,6 +171,7 @@ const parseAndExpandEvents = (iCalData, startDate, endDate, maxIterations = 1000
170171
location: stringValue(event.location),
171172
description: stringValue(event.description),
172173
uid: stringValue(event.uid),
174+
isFullDay: Boolean(event.isFullDay || event.start?.dateOnly),
173175
isRecurring: false,
174176
duration: getDurationSeconds(event),
175177
status: eventProp(event, "status"),
@@ -180,10 +182,7 @@ const parseAndExpandEvents = (iCalData, startDate, endDate, maxIterations = 1000
180182
}
181183
}
182184

183-
return {
184-
events,
185-
occurrences
186-
};
185+
return [...events, ...occurrences];
187186
};
188187

189188
module.exports = {

node_helper.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,18 @@ module.exports = NodeHelper.create({
125125
return;
126126
}
127127

128-
const wholeEvents = [...events.events, ...events.occurrences];
128+
const wholeEvents = events;
129129
let eventPool = [];
130130

131131
wholeEvents.forEach((item) => {
132-
const ri = item;
133132
const ev = {};
134133
ev.calendarId = calendar.uid;
135-
ev.location = ri.location;
136-
ev.description = ri.description;
137-
ev.title = ri.summary;
138-
ev.isRecurring = ri.isRecurring;
139-
ev.attendees = ri.attendees || [];
140-
ev.isCancelled = ri.status?.toUpperCase() === "CANCELLED";
134+
ev.location = item.location;
135+
ev.description = item.description;
136+
ev.title = item.summary;
137+
ev.isRecurring = item.isRecurring;
138+
ev.attendees = item.attendees || [];
139+
ev.isCancelled = item.status?.toUpperCase() === "CANCELLED";
141140
if (
142141
Array.isArray(calendar.replaceTitle) &&
143142
calendar.replaceTitle.length > 0
@@ -165,7 +164,7 @@ module.exports = NodeHelper.create({
165164
ev.endDate = endDate.unix();
166165
ev.startDateJ = startDate.toJSON();
167166
ev.endDateJ = endDate.toJSON();
168-
ev.duration = ri.duration;
167+
ev.duration = item.duration;
169168
ev.isMoment = ev.duration === 0;
170169
ev.isPassed = Boolean(endDate.isBefore(dayjs()));
171170
if (ev.duration <= 86400) {
@@ -177,19 +176,15 @@ module.exports = NodeHelper.create({
177176
}
178177
ev.className = calendar.className;
179178
ev.icon = calendar.icon;
180-
const isFullday = Boolean(
181-
startDate.format("HHmmss") === "000000" &&
182-
endDate.format("HHmmss") === "000000"
183-
);
184-
ev.isFullday = isFullday;
179+
ev.isFullday = item.isFullDay;
185180

186181
// import the Microsoft property X-MICROSOFT-CDO-BUSYSTATUS, fall back to "BUSY" in case none was found
187182
// possible values are 'FREE'|'TENTATIVE'|'BUSY'|'OOF' according to
188183
// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcical/cd68eae7-ed65-4dd3-8ea7-ad585c76c736
189-
ev.ms_busystatus = ri.ms_busystatus || "BUSY";
184+
ev.ms_busystatus = item.ms_busystatus || "BUSY";
190185

191-
ev.uid = ri.uid
192-
? `${calendar.uid}:${ev.startDate}:${ev.endDate}:${ri.uid}`
186+
ev.uid = item.uid
187+
? `${calendar.uid}:${ev.startDate}:${ev.endDate}:${item.uid}`
193188
: `${calendar.uid}:${ev.startDate}:${ev.endDate}:${ev.title}`;
194189
ev.calendarName = calendar.name;
195190
if (calendar.filter) {

test/ical-utils.test.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,9 @@ test("parses a non-recurring event with attendee extraction and obfuscation", ()
3636
new Date("2026-03-02T00:00:00Z")
3737
);
3838

39-
assert.equal(result.events.length, 1);
40-
assert.equal(result.occurrences.length, 0);
39+
assert.equal(result.length, 1);
4140

42-
const [event] = result.events;
41+
const [event] = result;
4342
assert.equal(event.summary, "Team Sync");
4443
assert.equal(event.location, "Office");
4544
assert.equal(event.description, "Weekly team sync");
@@ -74,8 +73,7 @@ test("ignores non-recurring events outside requested window", () => {
7473
new Date("2026-04-02T00:00:00Z")
7574
);
7675

77-
assert.equal(result.events.length, 0);
78-
assert.equal(result.occurrences.length, 0);
76+
assert.equal(result.length, 0);
7977
});
8078

8179
test("expands recurring events and applies EXDATE exclusions", () => {
@@ -99,18 +97,17 @@ test("expands recurring events and applies EXDATE exclusions", () => {
9997
new Date("2026-02-07T00:00:00Z")
10098
);
10199

102-
assert.equal(result.events.length, 0);
103-
assert.equal(result.occurrences.length, 4);
100+
assert.equal(result.length, 4);
104101

105-
const starts = result.occurrences.map((occurrence) => occurrence.start.toISOString());
102+
const starts = result.map((occurrence) => occurrence.start.toISOString());
106103
assert.deepEqual(starts, [
107104
"2026-02-01T10:00:00.000Z",
108105
"2026-02-02T10:00:00.000Z",
109106
"2026-02-04T10:00:00.000Z",
110107
"2026-02-05T10:00:00.000Z"
111108
]);
112109

113-
for (const occurrence of result.occurrences) {
110+
for (const occurrence of result) {
114111
assert.equal(occurrence.isRecurring, true);
115112
}
116113
});
@@ -136,7 +133,7 @@ test("respects maxIterations for recurring expansion", () => {
136133
2
137134
);
138135

139-
assert.equal(result.occurrences.length, 2);
136+
assert.equal(result.length, 2);
140137
});
141138

142139
test("uses default attendee metadata when optional params are missing", () => {
@@ -159,8 +156,8 @@ test("uses default attendee metadata when optional params are missing", () => {
159156
new Date("2026-06-02T00:00:00Z")
160157
);
161158

162-
assert.equal(result.events.length, 1);
163-
assert.deepEqual(result.events[0].attendees[0], {
159+
assert.equal(result.length, 1);
160+
assert.deepEqual(result[0].attendees[0], {
164161
name: "someone@***.com",
165162
email: "someone@***.com",
166163
status: "NEEDS-ACTION",
@@ -187,10 +184,11 @@ test("parses all-day events with correct duration and date span", () => {
187184
new Date("2026-03-31T00:00:00Z")
188185
);
189186

190-
assert.equal(result.events.length, 1);
187+
assert.equal(result.length, 1);
191188

192-
const event = result.events[0];
189+
const event = result[0];
193190
assert.equal(event.summary, "Holiday");
191+
assert.equal(event.isFullDay, true, "all-day event should have isFullDay=true");
194192
assert.equal(event.duration, 86400, "all-day event should be exactly 86400s");
195193

196194
const startMs = event.start.getTime();
@@ -217,9 +215,10 @@ test("provides correct endDate and duration for timed events", () => {
217215
new Date("2026-03-02T00:00:00Z")
218216
);
219217

220-
assert.equal(result.events.length, 1);
218+
assert.equal(result.length, 1);
221219

222-
const event = result.events[0];
220+
const event = result[0];
221+
assert.equal(event.isFullDay, false, "timed event should have isFullDay=false");
223222
assert.equal(
224223
event.end.toISOString(),
225224
"2026-03-01T11:30:00.000Z"
@@ -247,9 +246,10 @@ test("expands recurring all-day events with correct duration", () => {
247246
new Date("2026-03-31T00:00:00Z")
248247
);
249248

250-
assert.equal(result.occurrences.length, 3);
249+
assert.equal(result.length, 3);
251250

252-
for (const occurrence of result.occurrences) {
251+
for (const occurrence of result) {
252+
assert.equal(occurrence.isFullDay, true, "recurring all-day should have isFullDay=true");
253253
assert.equal(occurrence.duration, 86400);
254254

255255
const startMs = occurrence.start.getTime();
@@ -281,8 +281,8 @@ test("returns plain strings for properties that carry ICAL parameters (e.g. SUMM
281281
new Date("2026-03-02T00:00:00Z")
282282
);
283283

284-
assert.equal(result.events.length, 1);
285-
const ev = result.events[0];
284+
assert.equal(result.length, 1);
285+
const ev = result[0];
286286
assert.equal(typeof ev.summary, "string", "summary must be a plain string");
287287
assert.equal(typeof ev.location, "string", "location must be a plain string");
288288
assert.equal(typeof ev.description, "string", "description must be a plain string");
@@ -311,8 +311,8 @@ test("exposes status=CANCELLED on a non-recurring event", () => {
311311
new Date("2026-03-02T00:00:00Z")
312312
);
313313

314-
assert.equal(result.events.length, 1);
315-
const ev = result.events[0];
314+
assert.equal(result.length, 1);
315+
const ev = result[0];
316316
assert.equal(ev.status, "CANCELLED");
317317
});
318318

@@ -336,8 +336,8 @@ test("exposes ms_busystatus for Microsoft X-CDO property", () => {
336336
new Date("2026-03-02T00:00:00Z")
337337
);
338338

339-
assert.equal(result.events.length, 1);
340-
assert.equal(result.events[0].ms_busystatus, "OOF");
339+
assert.equal(result.length, 1);
340+
assert.equal(result[0].ms_busystatus, "OOF");
341341
});
342342

343343
test("exposes status on a recurring occurrence (from parent event)", () => {
@@ -361,8 +361,8 @@ test("exposes status on a recurring occurrence (from parent event)", () => {
361361
new Date("2026-03-05T00:00:00Z")
362362
);
363363

364-
assert.equal(result.occurrences.length, 2);
365-
for (const occurrence of result.occurrences) {
364+
assert.equal(result.length, 2);
365+
for (const occurrence of result) {
366366
assert.equal(occurrence.status, "CANCELLED");
367367
}
368368
});

0 commit comments

Comments
 (0)