70ab3f38c6
- Header wordmark Fabled -> Scribe; fable:calendar-changed event -> scribe:calendar-changed; SettingsView CSS comment. - Drop dead Project.auto_summary + summary_updated_at columns (migration 0063) -- the Ollama-era summarizer is gone; model + 2 frontend types + projects test updated. - Remove pivot vestiges: diagnostics _curator_busy()/curator_busy heartbeat field, tz BRIEFING_DAY_START_HOUR/user_briefing_date dead aliases, the ignored 'model' param on get_embedding (+ its test). ruff src/ clean; CI is the gate. Part of scribe plan #599. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
1.7 KiB
Python
46 lines
1.7 KiB
Python
"""User-timezone helpers.
|
||
|
||
All datetimes in the DB are stored as UTC. The helpers here bridge between
|
||
that UTC storage and the user's configured local timezone (IANA string in
|
||
the ``user_timezone`` setting). Use these anywhere the model or UI talks
|
||
in terms of "today", "tomorrow", or a bare calendar date — never
|
||
``date.today()`` or ``datetime.now(timezone.utc)`` inside a per-user flow.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, datetime, timedelta
|
||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||
|
||
from scribe.services.settings import get_setting
|
||
|
||
# Day-rollover boundary for day-anchored "today" logic. The "day" flips at
|
||
# this local hour (not midnight) so the 00:00–04:00 local window still
|
||
# shows yesterday's content until the user "starts" the new day.
|
||
DAY_ROLLOVER_HOUR = 4
|
||
|
||
|
||
async def get_user_tz(user_id: int) -> ZoneInfo:
|
||
"""Return the user's IANA ``ZoneInfo``, falling back to UTC."""
|
||
tz_str = await get_setting(user_id, "user_timezone") or "UTC"
|
||
try:
|
||
return ZoneInfo(tz_str)
|
||
except (ZoneInfoNotFoundError, KeyError):
|
||
return ZoneInfo("UTC")
|
||
|
||
|
||
async def user_today(user_id: int) -> date:
|
||
"""Return today's calendar date in the user's local timezone."""
|
||
tz = await get_user_tz(user_id)
|
||
return datetime.now(tz).date()
|
||
|
||
|
||
async def user_day_date(user_id: int) -> date:
|
||
"""Return the current "day" in the user's local timezone.
|
||
|
||
The day flips at ``DAY_ROLLOVER_HOUR`` (4am local) rather than midnight,
|
||
so the 00:00–04:00 local window still returns *yesterday* — a day-anchored
|
||
view stays on yesterday until the user crosses the rollover.
|
||
"""
|
||
tz = await get_user_tz(user_id)
|
||
return (datetime.now(tz) - timedelta(hours=DAY_ROLLOVER_HOUR)).date()
|