major-domo-v2/utils/timezone.py
Cal Corum 8a1a957c2a
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m16s
fix: use explicit America/Chicago timezone for freeze/thaw scheduling
The production container has ambiguous timezone config — /etc/localtime
points to Etc/UTC but date reports CST. The transaction freeze/thaw task
used datetime.now() (naive, relying on OS timezone), causing scheduling
to fire at unpredictable wall-clock times.

- Add utils/timezone.py with centralized Chicago timezone helpers
- Fix tasks/transaction_freeze.py to use now_chicago() for scheduling
- Fix utils/logging.py timestamp to use proper UTC-aware datetime
- Add 14 timezone utility tests
- Update freeze task tests to mock now_chicago instead of datetime

Closes #43

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:00:22 -06:00

55 lines
1.6 KiB
Python

"""
Timezone Utilities
Centralized timezone handling for the Discord bot. The SBA league operates
in America/Chicago time, but the production container may have ambiguous
timezone config. These helpers ensure scheduling logic uses explicit timezones
rather than relying on the OS default.
- Internal storage/logging: UTC
- Scheduling checks (freeze/thaw): America/Chicago
"""
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# League timezone — all scheduling decisions use this
CHICAGO_TZ = ZoneInfo("America/Chicago")
def now_utc() -> datetime:
"""Return the current time as a timezone-aware UTC datetime."""
return datetime.now(timezone.utc)
def now_chicago() -> datetime:
"""Return the current time as a timezone-aware America/Chicago datetime."""
return datetime.now(CHICAGO_TZ)
def to_chicago(dt: datetime) -> datetime:
"""Convert a datetime to America/Chicago.
If *dt* is naive (no tzinfo), it is assumed to be UTC.
"""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(CHICAGO_TZ)
def to_discord_timestamp(dt: datetime, style: str = "f") -> str:
"""Format a datetime as a Discord dynamic timestamp.
Args:
dt: A datetime (naive datetimes are assumed UTC).
style: Discord timestamp style letter.
R = relative, f = long date/short time, F = long date/time,
t = short time, T = long time, d = short date, D = long date.
Returns:
A string like ``<t:1234567890:f>``.
"""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return f"<t:{int(dt.timestamp())}:{style}>"