fix(tasks): render daily brief calendars in local time (#6926)

This commit is contained in:
red person
2026-10-31 13:54:14 +00:00
committed by GitHub
parent 72e8520fa3
commit ef3467e8d4
2 changed files with 140 additions and 5 deletions
+37 -5
View File
@@ -10,7 +10,7 @@ import os
import json
import re
import time
from datetime import datetime
from datetime import datetime, timezone
from typing import Tuple
from src.auth_helpers import owner_filter
@@ -1957,6 +1957,23 @@ async def action_learn_sender_signatures(owner: str, **kwargs) -> Tuple[str, boo
return str(e), False
def _daily_brief_utc_boundary(local_dt, tz=None):
"""Convert a local wall-clock boundary using the offset at that boundary."""
if local_dt.tzinfo is None and tz is not None:
local_dt = local_dt.replace(tzinfo=tz)
return local_dt.astimezone(timezone.utc).replace(tzinfo=None)
def _daily_brief_local_start(event, tz=None):
"""Keep date-only/floating rows local; convert only stored UTC instants."""
start = event.dtstart
if event.is_utc and not event.all_day:
if start.tzinfo is None:
start = start.replace(tzinfo=timezone.utc)
return start.astimezone(tz).replace(tzinfo=None)
return start
async def action_daily_brief(owner: str, **kwargs) -> Tuple[str, bool]:
"""Build a short morning digest: today's calendar events, unread email count
+ top-N senders/subjects, active todos."""
@@ -1966,10 +1983,13 @@ async def action_daily_brief(owner: str, **kwargs) -> Tuple[str, bool]:
from core.database import SessionLocal, CalendarEvent, CalendarCal, Note
from routes.email_helpers import _imap_connect, _decode_header
from sqlalchemy import and_, or_
# ----- Calendar: today's events -----
today = _dt.now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + _td(days=1)
utc_today = _daily_brief_utc_boundary(today)
utc_tomorrow = _daily_brief_utc_boundary(tomorrow)
# v2 review HIGH-12: gate the OR-null branch on single-user
# (unconfigured) deploys only. In a multi-user deploy, one
# user's daily brief must not include another user's notes or
@@ -1982,13 +2002,25 @@ async def action_daily_brief(owner: str, **kwargs) -> Tuple[str, bool]:
db = SessionLocal()
try:
ev_q = db.query(CalendarEvent).join(CalendarCal).filter(
CalendarEvent.dtstart < tomorrow,
CalendarEvent.dtend > today,
or_(
and_(
CalendarEvent.is_utc.is_(True),
CalendarEvent.all_day.is_not(True),
CalendarEvent.dtstart < utc_tomorrow,
CalendarEvent.dtend > utc_today,
),
and_(
or_(CalendarEvent.is_utc.is_not(True), CalendarEvent.all_day.is_(True)),
CalendarEvent.dtstart < tomorrow,
CalendarEvent.dtend > today,
),
),
CalendarEvent.status != "cancelled",
)
if owner:
ev_q = owner_filter(ev_q, CalendarCal, owner, include_shared=_allow_null)
events = ev_q.order_by(CalendarEvent.dtstart).all()
events = ev_q.all()
events.sort(key=_daily_brief_local_start)
# ----- Notes: pinned + non-archived todos with at least one undone item -----
n_q = db.query(Note).filter(Note.archived == False) # noqa: E712
if owner:
@@ -2058,7 +2090,7 @@ async def action_daily_brief(owner: str, **kwargs) -> Tuple[str, bool]:
if events:
plain.append("Calendar:")
for e in events:
t = e.dtstart.strftime("%H:%M") if not e.all_day else "all day"
t = _daily_brief_local_start(e).strftime("%H:%M") if not e.all_day else "all day"
loc = f" @ {e.location}" if e.location else ""
plain.append(f" {t} {e.summary}{loc}")
plain.append("")