From bc274f47e964f977784f00142c026e6a04bd2bc6 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Wed, 10 Dec 2025 23:33:02 -0800 Subject: [PATCH 1/5] fix dates --- src/components/upload-zone.tsx | 4 ++-- src/server/api/routers/schedule.ts | 4 +++- src/server/services/google-calendar.ts | 25 ++++++++++++++++++++----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/components/upload-zone.tsx b/src/components/upload-zone.tsx index 359158c..37907e5 100644 --- a/src/components/upload-zone.tsx +++ b/src/components/upload-zone.tsx @@ -75,9 +75,9 @@ export function UploadZone() { // Remove the redirect flag after restoring sessionStorage.removeItem("schedulesync_oauth_redirect"); } - // eslint-disable-next-line react-hooks/exhaustive-deps // Intentionally only depend on isSignedIn. We check preview/fileName/events for null // to determine if restoration is needed, but don't want to re-run when they change. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSignedIn]); // Save state to sessionStorage before OAuth redirect @@ -324,7 +324,7 @@ export function UploadZone() { {analyzeSchedule.isPending ? ( <> - Analyzing schedule... + Analyzing schedule... Don't close the page. ) : ( <> diff --git a/src/server/api/routers/schedule.ts b/src/server/api/routers/schedule.ts index 17502aa..80227ee 100644 --- a/src/server/api/routers/schedule.ts +++ b/src/server/api/routers/schedule.ts @@ -82,15 +82,17 @@ export const scheduleRouter = createTRPCRouter({ } // Create a new calendar + const timezone = input.timezone ?? "UTC"; const calendarId = await createCalendar( accessToken, input.calendarName, + timezone, ); // Add events to the calendar await addEventsToCalendar(accessToken, calendarId, input.events, { repeatWeeks: input.repeatWeeks, - timezone: input.timezone ?? "UTC", + timezone, startDate: input.startDate, }); diff --git a/src/server/services/google-calendar.ts b/src/server/services/google-calendar.ts index 871af2a..b13a519 100644 --- a/src/server/services/google-calendar.ts +++ b/src/server/services/google-calendar.ts @@ -2,12 +2,27 @@ import { google } from "googleapis"; import type { ScheduleEvent } from "./schedule-analyzer"; import { parse, addWeeks } from "date-fns"; +/** + * Formats a Date as a local datetime string without timezone suffix. + * Google Calendar will interpret this time using the provided timeZone parameter. + */ +function formatDateTimeLocal(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; +} + /** * Creates a new Google Calendar */ export async function createCalendar( accessToken: string, calendarName: string, + timezone = "UTC", ): Promise { const calendar = google.calendar({ version: "v3" }); @@ -18,7 +33,7 @@ export async function createCalendar( auth, requestBody: { summary: calendarName, - timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + timeZone: timezone, }, }); @@ -95,11 +110,11 @@ async function createOneTimeEvent( location: event.location, description: buildEventDescription(event), start: { - dateTime: startDateTime.toISOString(), + dateTime: formatDateTimeLocal(startDateTime), timeZone: timezone, }, end: { - dateTime: endDateTime.toISOString(), + dateTime: formatDateTimeLocal(endDateTime), timeZone: timezone, }, }, @@ -162,11 +177,11 @@ async function createRecurringEvent( location: event.location, description: buildEventDescription(event), start: { - dateTime: startDateTime.toISOString(), + dateTime: formatDateTimeLocal(startDateTime), timeZone: timezone, }, end: { - dateTime: endDateTime.toISOString(), + dateTime: formatDateTimeLocal(endDateTime), timeZone: timezone, }, recurrence: [rrule], From 17a01e593da2e74608c9e48ab5d106c3803ec3c0 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Wed, 10 Dec 2025 23:39:20 -0800 Subject: [PATCH 2/5] Update src/server/services/google-calendar.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/server/services/google-calendar.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/server/services/google-calendar.ts b/src/server/services/google-calendar.ts index b13a519..733629e 100644 --- a/src/server/services/google-calendar.ts +++ b/src/server/services/google-calendar.ts @@ -1,19 +1,15 @@ import { google } from "googleapis"; import type { ScheduleEvent } from "./schedule-analyzer"; import { parse, addWeeks } from "date-fns"; +import { formatInTimeZone } from "date-fns-tz"; /** - * Formats a Date as a local datetime string without timezone suffix. + * Formats a Date as a datetime string in the specified timezone, without timezone suffix. * Google Calendar will interpret this time using the provided timeZone parameter. */ -function formatDateTimeLocal(date: Date): string { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - const hours = String(date.getHours()).padStart(2, "0"); - const minutes = String(date.getMinutes()).padStart(2, "0"); - const seconds = String(date.getSeconds()).padStart(2, "0"); - return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; +function formatDateTimeLocal(date: Date, timeZone: string): string { + // Format as 'yyyy-MM-dd\'T\'HH:mm:ss' in the target timezone + return formatInTimeZone(date, timeZone, "yyyy-MM-dd'T'HH:mm:ss"); } /** From ebbac87a47d22218fb806196fdfd9f377ca49bd5 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Wed, 10 Dec 2025 23:43:20 -0800 Subject: [PATCH 3/5] fix type error --- package.json | 1 + pnpm-lock.yaml | 15 +++++++++++++++ src/server/services/google-calendar.ts | 14 +++++++++----- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 83dc613..d513a1f 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", "drizzle-orm": "^0.41.0", "googleapis": "^167.0.0", "ical-generator": "^10.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a135723..eade624 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + date-fns-tz: + specifier: ^3.2.0 + version: 3.2.0(date-fns@4.1.0) drizzle-orm: specifier: ^0.41.0 version: 0.41.0(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.2.0)(postgres@3.4.7) @@ -3484,6 +3487,14 @@ packages: } engines: { node: ">= 0.4" } + date-fns-tz@3.2.0: + resolution: + { + integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==, + } + peerDependencies: + date-fns: ^3.0.0 || ^4.0.0 + date-fns@4.1.0: resolution: { @@ -8631,6 +8642,10 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-tz@3.2.0(date-fns@4.1.0): + dependencies: + date-fns: 4.1.0 + date-fns@4.1.0: {} debug@3.2.7: diff --git a/src/server/services/google-calendar.ts b/src/server/services/google-calendar.ts index 733629e..b13a519 100644 --- a/src/server/services/google-calendar.ts +++ b/src/server/services/google-calendar.ts @@ -1,15 +1,19 @@ import { google } from "googleapis"; import type { ScheduleEvent } from "./schedule-analyzer"; import { parse, addWeeks } from "date-fns"; -import { formatInTimeZone } from "date-fns-tz"; /** - * Formats a Date as a datetime string in the specified timezone, without timezone suffix. + * Formats a Date as a local datetime string without timezone suffix. * Google Calendar will interpret this time using the provided timeZone parameter. */ -function formatDateTimeLocal(date: Date, timeZone: string): string { - // Format as 'yyyy-MM-dd\'T\'HH:mm:ss' in the target timezone - return formatInTimeZone(date, timeZone, "yyyy-MM-dd'T'HH:mm:ss"); +function formatDateTimeLocal(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; } /** From 60eafc68107de0174af7c7fde5afde7c4bc16f86 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Wed, 10 Dec 2025 23:49:44 -0800 Subject: [PATCH 4/5] Update package.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d513a1f..83dc613 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", - "date-fns-tz": "^3.2.0", "drizzle-orm": "^0.41.0", "googleapis": "^167.0.0", "ical-generator": "^10.0.0", From 800f4f27242904763fa240e0c1b2d70d14122e42 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Wed, 10 Dec 2025 23:51:05 -0800 Subject: [PATCH 5/5] fix lockfile --- pnpm-lock.yaml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eade624..a135723 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,9 +55,6 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 - date-fns-tz: - specifier: ^3.2.0 - version: 3.2.0(date-fns@4.1.0) drizzle-orm: specifier: ^0.41.0 version: 0.41.0(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.2.0)(postgres@3.4.7) @@ -3487,14 +3484,6 @@ packages: } engines: { node: ">= 0.4" } - date-fns-tz@3.2.0: - resolution: - { - integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==, - } - peerDependencies: - date-fns: ^3.0.0 || ^4.0.0 - date-fns@4.1.0: resolution: { @@ -8642,10 +8631,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - date-fns-tz@3.2.0(date-fns@4.1.0): - dependencies: - date-fns: 4.1.0 - date-fns@4.1.0: {} debug@3.2.7: