From 76af24a912bb59b05ff63d2def496989c31bad83 Mon Sep 17 00:00:00 2001 From: nvtoan0201-swe Date: Wed, 30 Sep 2026 11:40:18 +0700 Subject: [PATCH] fix: list calendars without an explicit time zone Google marks CalendarListEntry.timeZone as optional. Requiring it made one zone-less calendar fail the whole listCalendars read, which also failed the workspace snapshot. Default the entry to UTC and keep the rest of the list readable. Add a fixture test for an entry with no timeZone. --- packages/integrations/src/google.ts | 6 ++++-- tests/google.test.ts | 33 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/integrations/src/google.ts b/packages/integrations/src/google.ts index cb21f2c5..899c4234 100644 --- a/packages/integrations/src/google.ts +++ b/packages/integrations/src/google.ts @@ -481,7 +481,8 @@ export class GoogleClient { id: z.string().min(1), summary: z.string().default("(Untitled calendar)"), summaryOverride: z.string().optional(), - timeZone: z.string(), + // Google marks the calendar time zone as optional. + timeZone: z.string().default("UTC"), accessRole: z.string(), }), ) @@ -533,7 +534,8 @@ export class GoogleClient { const timeZone = current.start.timeZone ?? z - .object({ timeZone: z.string().min(1) }) + // Google marks the calendar time zone as optional. + .object({ timeZone: z.string().min(1).default("UTC") }) .parse( await this.request(`${CALENDAR}/users/me/calendarList/${encodeURIComponent(calendarId)}`), ).timeZone; diff --git a/tests/google.test.ts b/tests/google.test.ts index 9b2e6456..2ac11069 100644 --- a/tests/google.test.ts +++ b/tests/google.test.ts @@ -719,6 +719,39 @@ test("calendar discovery follows pagination and retains names, zones, and access ]); }); +test("calendars without an explicit time zone still list with a UTC fallback", async () => { + const client = clientWith(() => + json({ + items: [ + { + id: "shared@example.com", + summary: "Family", + accessRole: "reader", + }, + ], + }), + ); + assert.deepEqual(await client.listCalendars(), [ + { + id: "shared@example.com", + name: "Family", + timeZone: "UTC", + accessRole: "reader", + }, + ]); +}); + +test("event review falls back to UTC when neither the event nor its calendar has a zone", async () => { + const client = clientWith((request) => { + const path = new URL(request.url).pathname; + if (path.endsWith("/calendarList/primary")) + return json({ id: "primary", summary: "Personal", accessRole: "owner" }); + return json({ ...eventResponse, start: { dateTime: "2026-10-10T10:00:00-07:00" } }); + }); + const { event } = await client.reviewEvent("primary", "event-1"); + assert.equal(event.timeZone, "UTC"); +}); + test("recurring masters and occurrences are rejected from fresh Google data before updates or deletes", async () => { for (const recurrence of [ { recurrence: ["RRULE:FREQ=WEEKLY"] },