Display calendar events in local timezone (PST)
- Query using local "today/tomorrow/week" boundaries converted to UTC - Display event times converted to America/Los_Angeles timezone - Headers show local dates (Dec 31, 2025 instead of Jan 1, 2026) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,10 @@ import argparse
|
|||||||
import subprocess
|
import subprocess
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
# Local timezone for display
|
||||||
|
LOCAL_TZ = ZoneInfo('America/Los_Angeles')
|
||||||
|
|
||||||
# OAuth setup - reuse Gmail credentials location
|
# OAuth setup - reuse Gmail credentials location
|
||||||
CREDENTIALS_PATH = Path.home() / ".gmail-mcp" / "credentials.json"
|
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'))
|
start_raw = event['start'].get('dateTime', event['start'].get('date'))
|
||||||
if 'T' in start_raw:
|
if 'T' in start_raw:
|
||||||
start_dt = datetime.fromisoformat(start_raw.replace('Z', '+00:00'))
|
start_dt = datetime.fromisoformat(start_raw.replace('Z', '+00:00'))
|
||||||
|
start_local = start_dt.astimezone(LOCAL_TZ)
|
||||||
all_day = False
|
all_day = False
|
||||||
else:
|
else:
|
||||||
start_dt = datetime.strptime(start_raw, '%Y-%m-%d')
|
start_dt = datetime.strptime(start_raw, '%Y-%m-%d')
|
||||||
|
start_local = start_dt # All-day events don't need TZ conversion
|
||||||
all_day = True
|
all_day = True
|
||||||
|
|
||||||
# Parse end time
|
# Parse end time
|
||||||
@@ -121,7 +127,7 @@ def fetch_events(start: datetime, end: datetime, max_results: int = 50) -> list[
|
|||||||
'id': event['id'],
|
'id': event['id'],
|
||||||
'title': event.get('summary', '(no title)'),
|
'title': event.get('summary', '(no title)'),
|
||||||
'start': start_raw,
|
'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,
|
'end': end_raw,
|
||||||
'duration_mins': duration_mins,
|
'duration_mins': duration_mins,
|
||||||
'all_day': all_day,
|
'all_day': all_day,
|
||||||
@@ -129,8 +135,8 @@ def fetch_events(start: datetime, end: datetime, max_results: int = 50) -> list[
|
|||||||
'meeting_link': meeting_link,
|
'meeting_link': meeting_link,
|
||||||
'attendee_count': attendee_count,
|
'attendee_count': attendee_count,
|
||||||
'description': (event.get('description', '') or '')[:100],
|
'description': (event.get('description', '') or '')[:100],
|
||||||
'date': start_dt.strftime('%Y-%m-%d'),
|
'date': start_local.strftime('%Y-%m-%d'),
|
||||||
'day_name': start_dt.strftime('%A'),
|
'day_name': start_local.strftime('%A'),
|
||||||
})
|
})
|
||||||
|
|
||||||
return events
|
return events
|
||||||
@@ -197,18 +203,23 @@ def delegate(model: str, system: str, prompt: str, max_tokens: int = 4096) -> di
|
|||||||
|
|
||||||
def cmd_today() -> dict:
|
def cmd_today() -> dict:
|
||||||
"""Get today's events."""
|
"""Get today's events."""
|
||||||
now = datetime.now(timezone.utc)
|
# Use local time to determine "today"
|
||||||
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
now_local = datetime.now(LOCAL_TZ)
|
||||||
end = start + timedelta(days=1)
|
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 {
|
return {
|
||||||
"tier": "haiku",
|
"tier": "haiku",
|
||||||
"operation": "today",
|
"operation": "today",
|
||||||
"date": start.strftime('%Y-%m-%d'),
|
"date": start_local.strftime('%Y-%m-%d'),
|
||||||
"day_name": start.strftime('%A'),
|
"day_name": start_local.strftime('%A'),
|
||||||
"display_date": start.strftime('%A, %b %d'),
|
"display_date": start_local.strftime('%A, %b %d'),
|
||||||
"events": events,
|
"events": events,
|
||||||
"count": len(events)
|
"count": len(events)
|
||||||
}
|
}
|
||||||
@@ -216,18 +227,23 @@ def cmd_today() -> dict:
|
|||||||
|
|
||||||
def cmd_tomorrow() -> dict:
|
def cmd_tomorrow() -> dict:
|
||||||
"""Get tomorrow's events."""
|
"""Get tomorrow's events."""
|
||||||
now = datetime.now(timezone.utc)
|
# Use local time to determine "tomorrow"
|
||||||
start = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
now_local = datetime.now(LOCAL_TZ)
|
||||||
end = start + timedelta(days=1)
|
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 {
|
return {
|
||||||
"tier": "haiku",
|
"tier": "haiku",
|
||||||
"operation": "tomorrow",
|
"operation": "tomorrow",
|
||||||
"date": start.strftime('%Y-%m-%d'),
|
"date": start_local.strftime('%Y-%m-%d'),
|
||||||
"day_name": start.strftime('%A'),
|
"day_name": start_local.strftime('%A'),
|
||||||
"display_date": start.strftime('%A, %b %d'),
|
"display_date": start_local.strftime('%A, %b %d'),
|
||||||
"events": events,
|
"events": events,
|
||||||
"count": len(events)
|
"count": len(events)
|
||||||
}
|
}
|
||||||
@@ -235,16 +251,21 @@ def cmd_tomorrow() -> dict:
|
|||||||
|
|
||||||
def cmd_week() -> dict:
|
def cmd_week() -> dict:
|
||||||
"""Get next 7 days of events, grouped by day."""
|
"""Get next 7 days of events, grouped by day."""
|
||||||
now = datetime.now(timezone.utc)
|
# Use local time to determine the week
|
||||||
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
now_local = datetime.now(LOCAL_TZ)
|
||||||
end = start + timedelta(days=7)
|
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 = {}
|
by_day = {}
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
day = start + timedelta(days=i)
|
day = start_local + timedelta(days=i)
|
||||||
day_str = day.strftime('%Y-%m-%d')
|
day_str = day.strftime('%Y-%m-%d')
|
||||||
by_day[day_str] = {
|
by_day[day_str] = {
|
||||||
"date": day_str,
|
"date": day_str,
|
||||||
@@ -261,9 +282,9 @@ def cmd_week() -> dict:
|
|||||||
return {
|
return {
|
||||||
"tier": "haiku",
|
"tier": "haiku",
|
||||||
"operation": "week",
|
"operation": "week",
|
||||||
"start_date": start.strftime('%Y-%m-%d'),
|
"start_date": start_local.strftime('%Y-%m-%d'),
|
||||||
"end_date": end.strftime('%Y-%m-%d'),
|
"end_date": end_local.strftime('%Y-%m-%d'),
|
||||||
"display_range": f"{start.strftime('%b %d')} - {end.strftime('%b %d')}",
|
"display_range": f"{start_local.strftime('%b %d')} - {end_local.strftime('%b %d')}",
|
||||||
"days": list(by_day.values()),
|
"days": list(by_day.values()),
|
||||||
"total_events": len(events)
|
"total_events": len(events)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user