Skip to content

Commit b168a8c

Browse files
committed
fix(google-calendar): require timeZone for recurring timed events, clarify recurrence replace semantics
1 parent ff6b425 commit b168a8c

3 files changed

Lines changed: 31 additions & 1 deletion

File tree

apps/sim/tools/google_calendar/create.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type GoogleCalendarEventRequestBody,
88
} from '@/tools/google_calendar/types'
99
import {
10+
assertRecurringTimeZone,
1011
buildEventDateTime,
1112
buildGoogleMeetConferenceData,
1213
normalizeAttendees,
@@ -128,6 +129,10 @@ export const createTool: ToolConfig<GoogleCalendarCreateParams, GoogleCalendarCr
128129
const recurrence = normalizeRecurrence(params.recurrence)
129130
const isRecurring = recurrence.length > 0
130131

132+
if (isRecurring) {
133+
assertRecurringTimeZone([params.startDateTime, params.endDateTime], params.timeZone)
134+
}
135+
131136
const eventData: GoogleCalendarEventRequestBody = {
132137
summary: params.summary,
133138
start: buildEventDateTime(params.startDateTime, params.timeZone),

apps/sim/tools/google_calendar/update.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type GoogleCalendarUpdateResponse,
88
} from '@/tools/google_calendar/types'
99
import {
10+
assertRecurringTimeZone,
1011
buildEventDateTime,
1112
buildGoogleMeetConferenceData,
1213
normalizeAttendees,
@@ -97,7 +98,7 @@ export const updateTool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarUp
9798
required: false,
9899
visibility: 'user-or-llm',
99100
description:
100-
'Recurrence rule(s) in RFC 5545 format (e.g., RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR). Separate multiple rules with newlines.',
101+
"Recurrence rule(s) in RFC 5545 format (e.g., RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR). Separate multiple rules with newlines. When provided, replaces the event's recurrence; leaving it empty keeps the existing recurrence unchanged. Requires a timeZone for timed events.",
101102
},
102103
addGoogleMeet: {
103104
type: 'boolean',
@@ -138,6 +139,10 @@ export const updateTool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarUp
138139
const recurrence = normalizeRecurrence(params.recurrence)
139140
const isRecurring = recurrence.length > 0
140141

142+
if (isRecurring) {
143+
assertRecurringTimeZone([params.startDateTime, params.endDateTime], params.timeZone)
144+
}
145+
141146
if (params.summary !== undefined) {
142147
updateData.summary = params.summary
143148
}

apps/sim/tools/google_calendar/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ export function normalizeAttendees(
4848
return list.filter((email) => email.length > 0).map((email) => ({ email }))
4949
}
5050

51+
/**
52+
* Recurring events require a named `timeZone` on their timed start/end — the Calendar API
53+
* rejects them otherwise, and an RFC3339 offset is not a substitute (an IANA zone cannot be
54+
* derived from a fixed offset). Throws a clear error so we fail fast with guidance instead of
55+
* silently guessing a zone (which would misalign the recurrence) or sending an invalid request.
56+
* All-day recurring events (date-only values) do not need a timezone and are allowed.
57+
*/
58+
export function assertRecurringTimeZone(
59+
dateTimes: Array<string | undefined>,
60+
timeZone: string | undefined
61+
): void {
62+
if (timeZone) return
63+
const hasTimedValue = dateTimes.some((value) => value?.includes('T'))
64+
if (hasTimedValue) {
65+
throw new Error(
66+
'Recurring events require a time zone. Provide the timeZone parameter (an IANA name, e.g. America/New_York).'
67+
)
68+
}
69+
}
70+
5171
/** Normalize recurrence rules (single string, newline-separated string, or array) into an array. */
5272
export function normalizeRecurrence(recurrence: string | string[] | undefined): string[] {
5373
if (!recurrence) return []

0 commit comments

Comments
 (0)