diff --git a/mcp/delegation/gcal_delegate.py b/mcp/delegation/gcal_delegate.py index 0da7a19..a03b6c7 100755 --- a/mcp/delegation/gcal_delegate.py +++ b/mcp/delegation/gcal_delegate.py @@ -19,6 +19,10 @@ import argparse import subprocess from datetime import datetime, timedelta, timezone from pathlib import Path +from zoneinfo import ZoneInfo + +# Local timezone for display +LOCAL_TZ = ZoneInfo('America/Los_Angeles') # OAuth setup - reuse Gmail credentials location CREDENTIALS_PATH = Path.home() / ".gmail-mcp" / "credentials.json" @@ -92,9 +96,11 @@ def fetch_events(start: datetime, end: datetime, max_results: int = 50) -> list[ start_raw = event['start'].get('dateTime', event['start'].get('date')) if 'T' in start_raw: start_dt = datetime.fromisoformat(start_raw.replace('Z', '+00:00')) + start_local = start_dt.astimezone(LOCAL_TZ) all_day = False else: start_dt = datetime.strptime(start_raw, '%Y-%m-%d') + start_local = start_dt # All-day events don't need TZ conversion all_day = True # Parse end time @@ -121,7 +127,7 @@ def fetch_events(start: datetime, end: datetime, max_results: int = 50) -> list[ 'id': event['id'], 'title': event.get('summary', '(no title)'), 'start': start_raw, - 'start_formatted': start_dt.strftime('%I:%M %p').lstrip('0') if not all_day else 'All day', + 'start_formatted': start_local.strftime('%I:%M %p').lstrip('0') if not all_day else 'All day', 'end': end_raw, 'duration_mins': duration_mins, 'all_day': all_day, @@ -129,8 +135,8 @@ def fetch_events(start: datetime, end: datetime, max_results: int = 50) -> list[ 'meeting_link': meeting_link, 'attendee_count': attendee_count, 'description': (event.get('description', '') or '')[:100], - 'date': start_dt.strftime('%Y-%m-%d'), - 'day_name': start_dt.strftime('%A'), + 'date': start_local.strftime('%Y-%m-%d'), + 'day_name': start_local.strftime('%A'), }) return events @@ -197,18 +203,23 @@ def delegate(model: str, system: str, prompt: str, max_tokens: int = 4096) -> di def cmd_today() -> dict: """Get today's events.""" - now = datetime.now(timezone.utc) - start = now.replace(hour=0, minute=0, second=0, microsecond=0) - end = start + timedelta(days=1) + # Use local time to determine "today" + now_local = datetime.now(LOCAL_TZ) + start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0) + end_local = start_local + timedelta(days=1) - events = fetch_events(start, end) + # Convert to UTC for API query + start_utc = start_local.astimezone(timezone.utc) + end_utc = end_local.astimezone(timezone.utc) + + events = fetch_events(start_utc, end_utc) return { "tier": "haiku", "operation": "today", - "date": start.strftime('%Y-%m-%d'), - "day_name": start.strftime('%A'), - "display_date": start.strftime('%A, %b %d'), + "date": start_local.strftime('%Y-%m-%d'), + "day_name": start_local.strftime('%A'), + "display_date": start_local.strftime('%A, %b %d'), "events": events, "count": len(events) } @@ -216,18 +227,23 @@ def cmd_today() -> dict: def cmd_tomorrow() -> dict: """Get tomorrow's events.""" - now = datetime.now(timezone.utc) - start = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0) - end = start + timedelta(days=1) + # Use local time to determine "tomorrow" + now_local = datetime.now(LOCAL_TZ) + start_local = (now_local + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0) + end_local = start_local + timedelta(days=1) - events = fetch_events(start, end) + # Convert to UTC for API query + start_utc = start_local.astimezone(timezone.utc) + end_utc = end_local.astimezone(timezone.utc) + + events = fetch_events(start_utc, end_utc) return { "tier": "haiku", "operation": "tomorrow", - "date": start.strftime('%Y-%m-%d'), - "day_name": start.strftime('%A'), - "display_date": start.strftime('%A, %b %d'), + "date": start_local.strftime('%Y-%m-%d'), + "day_name": start_local.strftime('%A'), + "display_date": start_local.strftime('%A, %b %d'), "events": events, "count": len(events) } @@ -235,16 +251,21 @@ def cmd_tomorrow() -> dict: def cmd_week() -> dict: """Get next 7 days of events, grouped by day.""" - now = datetime.now(timezone.utc) - start = now.replace(hour=0, minute=0, second=0, microsecond=0) - end = start + timedelta(days=7) + # Use local time to determine the week + now_local = datetime.now(LOCAL_TZ) + start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0) + end_local = start_local + timedelta(days=7) - events = fetch_events(start, end, max_results=100) + # Convert to UTC for API query + start_utc = start_local.astimezone(timezone.utc) + end_utc = end_local.astimezone(timezone.utc) - # Group by date + events = fetch_events(start_utc, end_utc, max_results=100) + + # Group by date (using local dates) by_day = {} for i in range(7): - day = start + timedelta(days=i) + day = start_local + timedelta(days=i) day_str = day.strftime('%Y-%m-%d') by_day[day_str] = { "date": day_str, @@ -261,9 +282,9 @@ def cmd_week() -> dict: return { "tier": "haiku", "operation": "week", - "start_date": start.strftime('%Y-%m-%d'), - "end_date": end.strftime('%Y-%m-%d'), - "display_range": f"{start.strftime('%b %d')} - {end.strftime('%b %d')}", + "start_date": start_local.strftime('%Y-%m-%d'), + "end_date": end_local.strftime('%Y-%m-%d'), + "display_range": f"{start_local.strftime('%b %d')} - {end_local.strftime('%b %d')}", "days": list(by_day.values()), "total_events": len(events) }