fix(Google Calendar Node): Allow switching an event between all-day and timed (#35802)

Co-authored-by: Ayyub Bhoraniya <314098225+ayyubbhoraniya@users.noreply.github.com>
Co-authored-by: yehorkardash <yehor.kardash@n8n.io>
This commit is contained in:
Ayyub Bhoraniya
2026-08-21 15:12:29 +00:00
committed by GitHub
parent 14cd525465
commit 34de8b3998
2 changed files with 75 additions and 1 deletions
@@ -623,16 +623,21 @@ export class GoogleCalendar implements INodeType {
qs.sendUpdates = updateFields.sendUpdates as string;
}
const body: IEvent = {};
// PATCH merges nested objects, so an event that is currently all-day keeps
// its `date` unless we clear it, and Google rejects a start/end carrying
// both `date` and `dateTime`. Same in reverse for the all-day branch below.
if (updateFields.start) {
body.start = {
dateTime: moment.tz(updateFields.start, updateTimezone).utc().format(),
timeZone: updateTimezone,
date: null,
};
}
if (updateFields.end) {
body.end = {
dateTime: moment.tz(updateFields.end, updateTimezone).utc().format(),
timeZone: updateTimezone,
date: null,
};
}
// nodeVersion < 1.2
@@ -725,11 +730,13 @@ export class GoogleCalendar implements INodeType {
date: updateTimezone
? moment.tz(updateFields.start, updateTimezone).utc(true).format('YYYY-MM-DD')
: moment.tz(updateFields.start, moment.tz.guess()).utc(true).format('YYYY-MM-DD'),
dateTime: null,
};
body.end = {
date: updateTimezone
? moment.tz(updateFields.end, updateTimezone).utc(true).format('YYYY-MM-DD')
: moment.tz(updateFields.end, moment.tz.guess()).utc(true).format('YYYY-MM-DD'),
dateTime: null,
};
}
//example: RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10;UNTIL=20110701T170000Z
@@ -1,6 +1,6 @@
import type { MockProxy } from 'vitest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { INode, IExecuteFunctions } from 'n8n-workflow';
import type { IDataObject, INode, IExecuteFunctions } from 'n8n-workflow';
import * as genericFunctions from '../../GenericFunctions';
import { GoogleCalendar } from '../../GoogleCalendar.node';
@@ -126,5 +126,72 @@ describe('Google Calendar Node', () => {
{},
);
});
describe('all-day <-> timed conversion', () => {
const setupUpdate = (updateFields: IDataObject) => {
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('event');
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('update');
mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin');
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 1.2 }));
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myCalendar');
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myEvent');
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(true);
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(updateFields);
};
const patchedBody = () =>
(genericFunctions.googleApiRequest as Mock).mock.calls[0][2] as IDataObject;
it('should null out `date` when updating an event to a timed slot', async () => {
setupUpdate({
start: '2026-08-10T09:00:00',
end: '2026-08-10T10:00:00',
timezone: 'Europe/Berlin',
});
await googleCalendar.execute.call(mockExecuteFunctions);
const body = patchedBody();
// Without `date: null` the event keeps its all-day date and Google rejects
// the start/end for carrying both keys.
expect(body.start).toEqual({
dateTime: '2026-08-10T07:00:00Z',
timeZone: 'Europe/Berlin',
date: null,
});
expect(body.end).toEqual({
dateTime: '2026-08-10T08:00:00Z',
timeZone: 'Europe/Berlin',
date: null,
});
});
it('should null out `dateTime` when updating an event to all-day', async () => {
setupUpdate({
allday: 'yes',
start: '2026-08-10T09:00:00',
end: '2026-08-11T10:00:00',
timezone: 'Europe/Berlin',
});
await googleCalendar.execute.call(mockExecuteFunctions);
const body = patchedBody();
expect(body.start).toEqual({ date: '2026-08-10', dateTime: null });
expect(body.end).toEqual({ date: '2026-08-11', dateTime: null });
});
it('should not send start or end when neither is being updated', async () => {
setupUpdate({ summary: 'Renamed event' });
await googleCalendar.execute.call(mockExecuteFunctions);
const body = patchedBody();
expect(body).not.toHaveProperty('start');
expect(body).not.toHaveProperty('end');
expect(body.summary).toBe('Renamed event');
});
});
});
});