mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 15:28:47 +02:00
The CR-hardcoded today broke two scenarios: the owner traveling, and any future non-CR user. Now the provider sends the browser's IANA timezone (X-Client-Timezone) with every CopilotKit request — the runtime forwards x-* headers to the agent on its own — and the backend binds it to a per-request ContextVar next to the DB session. get_current_date and the future-cuota bounds use it; the tool also reports the timezone so the model can echo it. Unknown or absent zones (n8n posts, tests) fall back to Costa Rica; comma-joined duplicate header values are tolerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
import contextvars
|
|
from datetime import date, datetime, timedelta, timezone, tzinfo
|
|
from zoneinfo import ZoneInfo
|
|
|
|
# Costa Rica has no DST; a fixed offset is exact year-round. This is the
|
|
# FALLBACK when the request doesn't declare a timezone (n8n posts, tests).
|
|
CR_TZ = timezone(timedelta(hours=-6))
|
|
|
|
# Per-request client timezone, set by the agent middleware from the
|
|
# X-Client-Timezone header (IANA name sent by the browser). Falls back to
|
|
# Costa Rica so headless callers keep the owner's local semantics.
|
|
_client_tz: contextvars.ContextVar[tzinfo] = contextvars.ContextVar(
|
|
"client_tz", default=CR_TZ
|
|
)
|
|
|
|
|
|
def set_client_timezone(name: str | None) -> contextvars.Token | None:
|
|
"""Bind the request's IANA timezone; ignores unknown/absent names.
|
|
|
|
Proxies that see the header twice merge the values into a comma list
|
|
("Zone, Zone") — take the first entry.
|
|
"""
|
|
if not name:
|
|
return None
|
|
try:
|
|
return _client_tz.set(ZoneInfo(name.split(",")[0].strip()))
|
|
except (KeyError, ValueError):
|
|
return None
|
|
|
|
|
|
def reset_client_timezone(token: contextvars.Token | None) -> None:
|
|
if token is not None:
|
|
_client_tz.reset(token)
|
|
|
|
|
|
def client_tz() -> tzinfo:
|
|
return _client_tz.get()
|
|
|
|
|
|
def today_client() -> date:
|
|
"""Current date where the USER is (request timezone, CR fallback).
|
|
|
|
The server clock is UTC — at 6 pm in Costa Rica it is already "tomorrow"
|
|
in UTC. Anything user-facing that means "today" (agent answers, date
|
|
defaults) must come from here, never date.today().
|
|
"""
|
|
return datetime.now(client_tz()).date()
|
|
|
|
|
|
def today_cr() -> date:
|
|
"""Current date in Costa Rica (UTC-6) — timezone-independent fallback."""
|
|
return datetime.now(CR_TZ).date()
|
|
|
|
|
|
def utcnow() -> datetime:
|
|
"""Naive UTC now, via the non-deprecated API.
|
|
|
|
Every datetime column in the schema is TIMESTAMP WITHOUT TIME ZONE, so the
|
|
whole app speaks naive-UTC. Replacing datetime.utcnow() call sites with
|
|
this helper removes the Python 3.12 deprecation without changing any
|
|
stored value or comparison. When the columns move to TIMESTAMPTZ
|
|
(Phase 2), this becomes `datetime.now(timezone.utc)` and the type change
|
|
happens in exactly one place.
|
|
"""
|
|
return datetime.now(timezone.utc).replace(tzinfo=None)
|