diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/api/google_calendar.go | 74 | ||||
| -rw-r--r-- | internal/config/config.go | 33 |
2 files changed, 71 insertions, 36 deletions
diff --git a/internal/api/google_calendar.go b/internal/api/google_calendar.go index 1b1971a..d2d4355 100644 --- a/internal/api/google_calendar.go +++ b/internal/api/google_calendar.go @@ -17,38 +17,51 @@ import ( type GoogleCalendarClient struct { srv *calendar.Service calendarIDs []string + displayTZ *time.Location } -// parseEventTime extracts start/end times from a Google Calendar event, converting to local timezone -func parseEventTime(item *calendar.Event) (start, end time.Time) { +// parseEventTime extracts start/end times from a Google Calendar event +func (c *GoogleCalendarClient) parseEventTime(item *calendar.Event) (start, end time.Time) { + displayTZ := c.displayTZ + if displayTZ == nil { + displayTZ = time.UTC + } + if item.Start.DateTime == "" { - // All-day event - parse in local timezone - start, _ = time.ParseInLocation("2006-01-02", item.Start.Date, time.Local) - end, _ = time.ParseInLocation("2006-01-02", item.End.Date, time.Local) + // All-day event - parse in display timezone + start, _ = time.ParseInLocation("2006-01-02", item.Start.Date, displayTZ) + end, _ = time.ParseInLocation("2006-01-02", item.End.Date, displayTZ) } else { - // Timed event - use the event's timezone if specified - var loc *time.Location - if item.Start.TimeZone != "" { - loc, _ = time.LoadLocation(item.Start.TimeZone) - } - if loc == nil { - loc = time.Local + // Try RFC3339 first (includes timezone offset like "2006-01-02T15:04:05-10:00" or "Z") + var err error + start, err = time.Parse(time.RFC3339, item.Start.DateTime) + if err != nil { + // No timezone in string - use event's timezone or display timezone + var loc *time.Location + if item.Start.TimeZone != "" { + loc, _ = time.LoadLocation(item.Start.TimeZone) + } + if loc == nil { + loc = displayTZ + } + start, _ = time.ParseInLocation("2006-01-02T15:04:05", item.Start.DateTime, loc) } - // Try parsing with timezone offset first (RFC3339) - start, _ = time.Parse(time.RFC3339, item.Start.DateTime) - end, _ = time.Parse(time.RFC3339, item.End.DateTime) - - // If the parsed time is zero (parse failed) or missing timezone info, - // parse in the event's timezone - if start.IsZero() || item.Start.DateTime[len(item.Start.DateTime)-1] != 'Z' && !strings.Contains(item.Start.DateTime, "+") && !strings.Contains(item.Start.DateTime, "-") { - start, _ = time.ParseInLocation("2006-01-02T15:04:05", item.Start.DateTime, loc) + end, err = time.Parse(time.RFC3339, item.End.DateTime) + if err != nil { + var loc *time.Location + if item.End.TimeZone != "" { + loc, _ = time.LoadLocation(item.End.TimeZone) + } + if loc == nil { + loc = displayTZ + } end, _ = time.ParseInLocation("2006-01-02T15:04:05", item.End.DateTime, loc) } - // Convert to local timezone for display - start = start.In(time.Local) - end = end.In(time.Local) + // Convert to display timezone + start = start.In(displayTZ) + end = end.In(displayTZ) } return } @@ -72,7 +85,8 @@ func deduplicateEvents(events []models.CalendarEvent) []models.CalendarEvent { // NewGoogleCalendarClient creates a client that fetches from multiple calendars. // calendarIDs can be comma-separated (e.g., "cal1@group.calendar.google.com,cal2@group.calendar.google.com") -func NewGoogleCalendarClient(ctx context.Context, credentialsFile, calendarIDs string) (*GoogleCalendarClient, error) { +// timezone is the IANA timezone name for display (e.g., "Pacific/Honolulu") +func NewGoogleCalendarClient(ctx context.Context, credentialsFile, calendarIDs, timezone string) (*GoogleCalendarClient, error) { srv, err := calendar.NewService(ctx, option.WithCredentialsFile(credentialsFile)) if err != nil { return nil, fmt.Errorf("unable to retrieve Calendar client: %v", err) @@ -87,9 +101,17 @@ func NewGoogleCalendarClient(ctx context.Context, credentialsFile, calendarIDs s } } + // Load display timezone + displayTZ, err := time.LoadLocation(timezone) + if err != nil { + log.Printf("Warning: invalid timezone %q, using UTC: %v", timezone, err) + displayTZ = time.UTC + } + return &GoogleCalendarClient{ srv: srv, calendarIDs: trimmedIDs, + displayTZ: displayTZ, }, nil } @@ -106,7 +128,7 @@ func (c *GoogleCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults } for _, item := range events.Items { - start, end := parseEventTime(item) + start, end := c.parseEventTime(item) allEvents = append(allEvents, models.CalendarEvent{ ID: item.Id, Summary: item.Summary, @@ -139,7 +161,7 @@ func (c *GoogleCalendarClient) GetEventsByDateRange(ctx context.Context, start, } for _, item := range events.Items { - evtStart, evtEnd := parseEventTime(item) + evtStart, evtEnd := c.parseEventTime(item) allEvents = append(allEvents, models.CalendarEvent{ ID: item.Id, Summary: item.Summary, diff --git a/internal/config/config.go b/internal/config/config.go index 62e733a..cf3af49 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,10 +9,11 @@ import ( // Config holds all application configuration type Config struct { // API Keys - TodoistAPIKey string - PlanToEatAPIKey string - TrelloAPIKey string - TrelloToken string + TodoistAPIKey string + PlanToEatAPIKey string + PlanToEatSession string // Session cookie for web scraping + TrelloAPIKey string + TrelloToken string // Google Calendar GoogleCredentialsFile string @@ -28,16 +29,20 @@ type Config struct { Port string CacheTTLMinutes int Debug bool + + // Display + Timezone string // IANA timezone name (e.g., "Pacific/Honolulu") } // Load reads configuration from environment variables func Load() (*Config, error) { cfg := &Config{ // API Keys - TodoistAPIKey: os.Getenv("TODOIST_API_KEY"), - PlanToEatAPIKey: os.Getenv("PLANTOEAT_API_KEY"), - TrelloAPIKey: os.Getenv("TRELLO_API_KEY"), - TrelloToken: os.Getenv("TRELLO_TOKEN"), + TodoistAPIKey: os.Getenv("TODOIST_API_KEY"), + PlanToEatAPIKey: os.Getenv("PLANTOEAT_API_KEY"), + PlanToEatSession: os.Getenv("PLANTOEAT_SESSION"), + TrelloAPIKey: os.Getenv("TRELLO_API_KEY"), + TrelloToken: os.Getenv("TRELLO_TOKEN"), // Google Calendar GoogleCredentialsFile: os.Getenv("GOOGLE_CREDENTIALS_FILE"), @@ -53,6 +58,9 @@ func Load() (*Config, error) { Port: getEnvWithDefault("PORT", "8080"), CacheTTLMinutes: getEnvAsInt("CACHE_TTL_MINUTES", 5), Debug: getEnvAsBool("DEBUG", false), + + // Display + Timezone: getEnvWithDefault("TIMEZONE", "Pacific/Honolulu"), } // Validate required fields @@ -81,9 +89,14 @@ func (c *Config) Validate() error { return nil } -// HasPlanToEat checks if PlanToEat is configured +// HasPlanToEat checks if PlanToEat is configured (API key or session) func (c *Config) HasPlanToEat() bool { - return c.PlanToEatAPIKey != "" + return c.PlanToEatAPIKey != "" || c.PlanToEatSession != "" +} + +// HasPlanToEatSession checks if PlanToEat session cookie is configured +func (c *Config) HasPlanToEatSession() bool { + return c.PlanToEatSession != "" } // HasTrello checks if Trello is configured |
