From fa460a4313cb56c16f25b22259d0c6d84d4f752e Mon Sep 17 00:00:00 2001 From: Fg Date: Tue, 18 Aug 2026 10:00:37 +0000 Subject: [PATCH] fix(Extract From File Node): Parse date-only RRULE UNTIL values (#29522) --- .../actions/moveTo.operation.ts | 5 ++- .../actions/parseIcsCalendar.ts | 11 +++++ .../test/parseIcsCalendar.test.ts | 45 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.ts create mode 100644 packages/nodes-base/nodes/Files/ExtractFromFile/test/parseIcsCalendar.test.ts diff --git a/packages/nodes-base/nodes/Files/ExtractFromFile/actions/moveTo.operation.ts b/packages/nodes-base/nodes/Files/ExtractFromFile/actions/moveTo.operation.ts index fb55317389f..1a3ba815812 100644 --- a/packages/nodes-base/nodes/Files/ExtractFromFile/actions/moveTo.operation.ts +++ b/packages/nodes-base/nodes/Files/ExtractFromFile/actions/moveTo.operation.ts @@ -15,11 +15,12 @@ import { jsonParse, BINARY_MODE_COMBINED, } from 'n8n-workflow'; -import { icsCalendarToObject } from 'ts-ics'; import { encodeDecodeOptions } from '@utils/descriptions'; import { updateDisplayOptions } from '@utils/utilities'; +import { parseIcsCalendar } from './parseIcsCalendar'; + export const properties: INodeProperties[] = [ { displayName: 'Input Binary Field', @@ -152,7 +153,7 @@ export async function execute( } if (operation === 'fromIcs') { - convertedValue = icsCalendarToObject(convertedValue as string); + convertedValue = parseIcsCalendar(convertedValue as string); } const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string; diff --git a/packages/nodes-base/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.ts b/packages/nodes-base/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.ts new file mode 100644 index 00000000000..516331113dc --- /dev/null +++ b/packages/nodes-base/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.ts @@ -0,0 +1,11 @@ +import { icsCalendarToObject } from 'ts-ics'; +import type { VCalendar } from 'ts-ics'; + +export function parseIcsCalendar(calendarString: string): VCalendar { + // ts-ics < 2.4.5 does not parse date-only RRULE UNTIL values. Remove this after upgrading. + const normalizedCalendar = calendarString.replace( + /(^|\r?\n)(RRULE[^\r\n]*?\bUNTIL=)(\d{8})(?=;|\r?\n|$)/g, + '$1$2$3T000000Z', + ); + return icsCalendarToObject(normalizedCalendar); +} diff --git a/packages/nodes-base/nodes/Files/ExtractFromFile/test/parseIcsCalendar.test.ts b/packages/nodes-base/nodes/Files/ExtractFromFile/test/parseIcsCalendar.test.ts new file mode 100644 index 00000000000..ad844cefc70 --- /dev/null +++ b/packages/nodes-base/nodes/Files/ExtractFromFile/test/parseIcsCalendar.test.ts @@ -0,0 +1,45 @@ +import { parseIcsCalendar } from '../actions/parseIcsCalendar'; + +describe('parseIcsCalendar', () => { + it('parses date-only RRULE UNTIL values', () => { + const calendar = parseIcsCalendar(`BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//n8n test//EN +BEGIN:VEVENT +UID:mDceAWXluoi-MADvoh-u2SFjv3jv@proton.me +DTSTAMP:20250915T225621Z +SUMMARY:test recurring +DTSTART;VALUE=DATE:20250917 +DTEND;VALUE=DATE:20250918 +SEQUENCE:1 +RRULE:FREQ=WEEKLY;UNTIL=20250926 +STATUS:CONFIRMED +END:VEVENT +END:VCALENDAR`); + + const until = calendar.events?.[0]?.recurrenceRule?.until?.date; + + expect(until).toBeInstanceOf(Date); + expect(until?.toISOString()).toBe('2025-09-26T00:00:00.000Z'); + }); + + it('keeps date-time RRULE UNTIL values valid', () => { + const calendar = parseIcsCalendar(`BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//n8n test//EN +BEGIN:VEVENT +UID:test +DTSTAMP:20250915T225621Z +SUMMARY:test recurring +DTSTART:20250917T100000Z +DTEND:20250917T110000Z +RRULE:FREQ=WEEKLY;UNTIL=20250926T120000Z +END:VEVENT +END:VCALENDAR`); + + const until = calendar.events?.[0]?.recurrenceRule?.until?.date; + + expect(until).toBeInstanceOf(Date); + expect(until?.toISOString()).toBe('2025-09-26T12:00:00.000Z'); + }); +});