From 383a4430f186368121fd7e525880fb5f012373eb Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 26 Mar 2026 06:34:22 -0400 Subject: [PATCH] fix: convert calendar event times to user timezone in briefing The briefing was formatting event start_dt directly in UTC instead of converting to the user's local timezone. Also, the day_start/day_end query window was naive (UTC), so events at the edges of the user's day could be missed or included incorrectly. Now reads user_timezone setting, uses it for today's date boundary and for converting each event's start_dt before formatting. Co-Authored-By: Claude Sonnet 4.6 --- .../services/briefing_pipeline.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/fabledassistant/services/briefing_pipeline.py b/src/fabledassistant/services/briefing_pipeline.py index bbe68a4..0fa741a 100644 --- a/src/fabledassistant/services/briefing_pipeline.py +++ b/src/fabledassistant/services/briefing_pipeline.py @@ -8,6 +8,7 @@ import asyncio import hashlib import logging from datetime import date, datetime, timezone +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError import httpx @@ -129,7 +130,13 @@ async def _gather_internal(user_id: int) -> dict: from fabledassistant.services.projects import list_projects from fabledassistant.services.caldav import is_caldav_configured, list_events - today = date.today().isoformat() + tz_name = await get_setting(user_id, "user_timezone") or "UTC" + try: + user_tz = ZoneInfo(tz_name) + except ZoneInfoNotFoundError: + user_tz = ZoneInfo("UTC") + + today = datetime.now(user_tz).date().isoformat() # Tasks: overdue, due today, high priority in-progress all_tasks: list[dict] = [] @@ -166,9 +173,9 @@ async def _gather_internal(user_id: int) -> dict: calendar_events: list[str] = [] try: from fabledassistant.services.events import list_events as list_internal_events - today_date = date.today() - day_start = datetime(today_date.year, today_date.month, today_date.day, 0, 0, 0) - day_end = datetime(today_date.year, today_date.month, today_date.day, 23, 59, 59) + today_date = datetime.now(user_tz).date() + day_start = datetime(today_date.year, today_date.month, today_date.day, 0, 0, 0, tzinfo=user_tz) + day_end = datetime(today_date.year, today_date.month, today_date.day, 23, 59, 59, tzinfo=user_tz) internal_events = await list_internal_events( user_id=user_id, date_from=day_start, date_to=day_end ) @@ -176,7 +183,8 @@ async def _gather_internal(user_id: int) -> dict: if e.all_day: time_str = "all day" elif e.start_dt: - time_str = e.start_dt.strftime("%-I:%M %p") + local_dt = e.start_dt.astimezone(user_tz) if e.start_dt.tzinfo else e.start_dt.replace(tzinfo=timezone.utc).astimezone(user_tz) + time_str = local_dt.strftime("%-I:%M %p") else: time_str = "unknown time" calendar_events.append(f"{e.title} at {time_str}")