From 2da86b009c76bb7688103da49a9125d35d3a1ed8 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 28 Jul 2026 02:37:23 +0000 Subject: fix: Google Tasks due dates shifted a day earlier in negative-offset timezones Google Tasks' due field is date-only but always serialized as an RFC3339 timestamp fixed at midnight UTC regardless of the user's timezone. Converting that instant into displayTZ (dueDate.In(tz)) reinterpreted it as a real moment in time instead of re-anchoring the same calendar date to local midnight -- midnight UTC becomes 2pm the previous day in UTC-10 (Pacific/Honolulu), so every dated Google Task silently landed one day earlier than its actual due date. A task due tomorrow showed up in today's section. Fix: extract the Y/M/D from the UTC-anchored timestamp (which IS the intended calendar date) and rebuild midnight in displayTZ from those components, instead of converting the instant. --- internal/api/google_tasks.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'internal/api/google_tasks.go') diff --git a/internal/api/google_tasks.go b/internal/api/google_tasks.go index b580be4..1f9aefb 100644 --- a/internal/api/google_tasks.go +++ b/internal/api/google_tasks.go @@ -95,14 +95,25 @@ func (c *GoogleTasksClient) getTasksFromList(ctx context.Context, listID string) // Parse due date if present (RFC3339 format, but date-only) if item.Due != "" { - // Google Tasks due dates are in RFC3339 format but typically just the date part + // Google Tasks due dates are date-only, but the API always + // serializes them as an RFC3339 timestamp fixed at midnight UTC + // regardless of the user's timezone. Converting that instant + // into displayTZ (dueDate.In(c.displayTZ)) reinterprets it as a + // real moment in time and shifts the calendar date backward for + // any timezone behind UTC -- midnight UTC becomes 2pm the + // PREVIOUS day in UTC-10, silently moving every dated Google + // Task one day earlier than its actual due date. The Y/M/D of + // the UTC-anchored timestamp IS the intended calendar date, so + // re-anchor those same components to displayTZ midnight instead + // of converting the instant. dueDate, err := time.Parse(time.RFC3339, item.Due) if err != nil { // Try date-only format dueDate, err = time.ParseInLocation("2006-01-02", item.Due[:10], c.displayTZ) } if err == nil { - dueInTZ := dueDate.In(c.displayTZ) + y, m, d := dueDate.Date() + dueInTZ := time.Date(y, m, d, 0, 0, 0, 0, c.displayTZ) task.DueDate = &dueInTZ } } -- cgit v1.2.3