All checks were successful
Build Docker Image / build (pull_request) Successful in 1m16s
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>
55 lines
1.6 KiB
Python
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}>"
|