From bee17dd6cc4eaabc252602a02d4ec109f42ef926 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:52:21 +0200 Subject: [PATCH] fix(Google Calendar Node): Errors with after/before options (#8628) --- .../nodes/Google/Calendar/GenericFunctions.ts | 54 ++++++++++++------- .../Google/Calendar/GoogleCalendar.node.ts | 25 +++++++-- .../Calendar/test/GeneircFunctions.test.ts | 20 +++++++ 3 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 packages/nodes-base/nodes/Google/Calendar/test/GeneircFunctions.test.ts diff --git a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts index 63b9ba1012..819e67cd46 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts @@ -154,28 +154,44 @@ type RecurentEvent = { export function addNextOccurrence(items: RecurentEvent[]) { for (const item of items) { if (item.recurrence) { - const rrule = RRule.fromString(item.recurrence[0]); - const until = rrule.options?.until; + let eventRecurrence; + try { + eventRecurrence = item.recurrence.find((r) => r.toUpperCase().startsWith('RRULE')); + if (!eventRecurrence) continue; - const now = new Date(); - if (until && until < now) { - continue; + const rrule = RRule.fromString(eventRecurrence); + const until = rrule.options?.until; + + const now = new Date(); + if (until && until < now) { + continue; + } + + const nextOccurrence = rrule.after(new Date()); + + item.nextOccurrence = { + start: { + dateTime: moment(nextOccurrence).format(), + timeZone: item.start.timeZone, + }, + end: { + dateTime: moment(nextOccurrence) + .add(moment(item.end.dateTime).diff(moment(item.start.dateTime))) + .format(), + timeZone: item.end.timeZone, + }, + }; + } catch (error) { + console.log(`Error adding next occurrence ${eventRecurrence}`); } - - const nextOccurrence = rrule.after(new Date()); - item.nextOccurrence = { - start: { - dateTime: moment(nextOccurrence).format(), - timeZone: item.start.timeZone, - }, - end: { - dateTime: moment(nextOccurrence) - .add(moment(item.end.dateTime).diff(moment(item.start.dateTime))) - .format(), - timeZone: item.end.timeZone, - }, - }; } } return items; } + +const hasTimezone = (date: string) => date.endsWith('Z') || /\+\d{2}:\d{2}$/.test(date); + +export function addTimezoneToDate(date: string, timezone: string) { + if (hasTimezone(date)) return date; + return moment.tz(date, timezone).utc().format(); +} diff --git a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts index 52955237c5..82d1719730 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts @@ -14,6 +14,7 @@ import moment from 'moment-timezone'; import { v4 as uuid } from 'uuid'; import { addNextOccurrence, + addTimezoneToDate, encodeURIComponentOnce, getCalendars, getTimezones, @@ -33,7 +34,7 @@ export class GoogleCalendar implements INodeType { name: 'googleCalendar', icon: 'file:googleCalendar.svg', group: ['input'], - version: 1, + version: [1, 1.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Google Calendar API', defaults: { @@ -69,6 +70,13 @@ export class GoogleCalendar implements INodeType { ...calendarFields, ...eventOperations, ...eventFields, + { + displayName: + 'This node will use the time zone set in n8n’s settings, but you can override this in the workflow settings', + name: 'useN8nTimeZone', + type: 'notice', + default: '', + }, ], }; @@ -127,9 +135,12 @@ export class GoogleCalendar implements INodeType { const length = items.length; const qs: IDataObject = {}; let responseData; + const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); const timezone = this.getTimezone(); + const nodeVersion = this.getNode().typeVersion; + for (let i = 0; i < length; i++) { try { if (resource === 'calendar') { @@ -414,16 +425,16 @@ export class GoogleCalendar implements INodeType { qs.singleEvents = options.singleEvents as boolean; } if (options.timeMax) { - qs.timeMax = options.timeMax as string; + qs.timeMax = addTimezoneToDate(options.timeMax as string, tz || timezone); } if (options.timeMin) { - qs.timeMin = options.timeMin as string; + qs.timeMin = addTimezoneToDate(options.timeMin as string, tz || timezone); } if (tz) { qs.timeZone = tz; } if (options.updatedMin) { - qs.updatedMin = options.updatedMin as string; + qs.updatedMin = addTimezoneToDate(options.updatedMin as string, tz || timezone); } if (returnAll) { responseData = await googleApiRequestAllItems.call( @@ -458,7 +469,11 @@ export class GoogleCalendar implements INodeType { const eventId = this.getNodeParameter('eventId', i) as string; const useDefaultReminders = this.getNodeParameter('useDefaultReminders', i) as boolean; const updateFields = this.getNodeParameter('updateFields', i); - const updateTimezone = updateFields.timezone as string; + let updateTimezone = updateFields.timezone as string; + + if (nodeVersion > 1 && updateTimezone === undefined) { + updateTimezone = timezone; + } if (updateFields.maxAttendees) { qs.maxAttendees = updateFields.maxAttendees as number; diff --git a/packages/nodes-base/nodes/Google/Calendar/test/GeneircFunctions.test.ts b/packages/nodes-base/nodes/Google/Calendar/test/GeneircFunctions.test.ts new file mode 100644 index 0000000000..1da9122e2c --- /dev/null +++ b/packages/nodes-base/nodes/Google/Calendar/test/GeneircFunctions.test.ts @@ -0,0 +1,20 @@ +import { addTimezoneToDate } from '../GenericFunctions'; + +describe('addTimezoneToDate', () => { + it('should add timezone to date', () => { + const dateWithTimezone = '2021-09-01T12:00:00.000Z'; + const result1 = addTimezoneToDate(dateWithTimezone, 'Europe/Prague'); + expect(result1).toBe('2021-09-01T12:00:00.000Z'); + + const dateWithoutTimezone = '2021-09-01T12:00:00'; + const result2 = addTimezoneToDate(dateWithoutTimezone, 'Europe/Prague'); + expect(result2).toBe('2021-09-01T10:00:00Z'); + + const result3 = addTimezoneToDate(dateWithoutTimezone, 'Asia/Tokyo'); + expect(result3).toBe('2021-09-01T03:00:00Z'); + + const dateWithDifferentTimezone = '2021-09-01T12:00:00.000+08:00'; + const result4 = addTimezoneToDate(dateWithDifferentTimezone, 'Europe/Prague'); + expect(result4).toBe('2021-09-01T12:00:00.000+08:00'); + }); +});