From c156f6f52bb558f74037f9ab787d63c9d889fbdd Mon Sep 17 00:00:00 2001 From: Sean Date: Sun, 18 Oct 2026 10:31:52 +0800 Subject: [PATCH] fix: tolerate empty BYMONTHDAY values in tasks Signed-off-by: Sean --- src/models/task.js | 17 ++++++++++++++++- tests/javascript/unit/models/task.spec.js | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/models/task.js b/src/models/task.js index 0c3313bcf..433e7b4de 100644 --- a/src/models/task.js +++ b/src/models/task.js @@ -34,6 +34,21 @@ import { mapRecurrenceRuleValueToRecurrenceRuleObject, } from './recurrenceRule.js' +/** + * Removes empty values from BYMONTHDAY lists before parsing. Some CalDAV + * clients send values such as `BYMONTHDAY=,24`, which ical.js rejects. + * @param {string} vcalendar The vCalendar data + * @return {string} + */ +function normalizeEmptyByMonthDayValues(vcalendar) { + return vcalendar.replace(/(RRULE(?:;[^:\r\n]*)?:)([^\r\n]*)/gi, (line, prefix, rule) => { + return prefix + rule.replace(/(^|;)BYMONTHDAY=([^;]*)/i, (part, separator, days) => { + const normalizedDays = days.split(',').filter(Boolean).join(',') + return normalizedDays ? `${separator}BYMONTHDAY=${normalizedDays}` : '' + }) + }) +} + export default class Task { /** @@ -48,7 +63,7 @@ export default class Task { throw new Error('Invalid vCalendar') } - const jCal = ICAL.parse(vcalendar) + const jCal = ICAL.parse(normalizeEmptyByMonthDayValues(vcalendar)) if (jCal[0] !== 'vcalendar') { throw new Error('Only one task is allowed in the vCalendar data') } diff --git a/tests/javascript/unit/models/task.spec.js b/tests/javascript/unit/models/task.spec.js index a35babb3c..785bcddb2 100644 --- a/tests/javascript/unit/models/task.spec.js +++ b/tests/javascript/unit/models/task.spec.js @@ -43,6 +43,23 @@ describe('task', () => { expect(task.isRecurring).toEqual(true) }) + it('Should parse a BYMONTHDAY list with an empty value', () => { + const vcalendar = `BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VTODO +UID:empty-month-day +DTSTAMP:20261018T000000Z +DTSTART:20261001T020000Z +RRULE:FREQ=MONTHLY;BYMONTHDAY=,24;UNTIL=20261225T020000Z +SUMMARY:Repro +END:VTODO +END:VCALENDAR` + const task = new Task(vcalendar, {}) + + expect(task.isRecurring).toEqual(true) + expect(task.recurrenceRule.byMonthDay).toEqual([24]) + }) + it('Should set status to "COMPLETED" on completion.', () => { const task = new Task(loadICS('vcalendars/vcalendar-default'), {}) task.complete = 100