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>
130 lines
4.7 KiB
Python
130 lines
4.7 KiB
Python
"""
|
|
Tests for timezone utility module.
|
|
|
|
Validates centralized timezone helpers that ensure scheduling logic uses
|
|
explicit America/Chicago timezone rather than relying on OS defaults.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime, timezone
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from utils.timezone import (
|
|
CHICAGO_TZ,
|
|
now_utc,
|
|
now_chicago,
|
|
to_chicago,
|
|
to_discord_timestamp,
|
|
)
|
|
|
|
|
|
class TestChicagoTZ:
|
|
"""Tests for the CHICAGO_TZ constant."""
|
|
|
|
def test_chicago_tz_is_zoneinfo(self):
|
|
"""CHICAGO_TZ should be a ZoneInfo instance for America/Chicago."""
|
|
assert isinstance(CHICAGO_TZ, ZoneInfo)
|
|
assert str(CHICAGO_TZ) == "America/Chicago"
|
|
|
|
|
|
class TestNowUtc:
|
|
"""Tests for now_utc()."""
|
|
|
|
def test_returns_aware_datetime(self):
|
|
"""now_utc() should return a timezone-aware datetime."""
|
|
result = now_utc()
|
|
assert result.tzinfo is not None
|
|
|
|
def test_returns_utc(self):
|
|
"""now_utc() should return a datetime in UTC."""
|
|
result = now_utc()
|
|
assert result.tzinfo == timezone.utc
|
|
|
|
|
|
class TestNowChicago:
|
|
"""Tests for now_chicago()."""
|
|
|
|
def test_returns_aware_datetime(self):
|
|
"""now_chicago() should return a timezone-aware datetime."""
|
|
result = now_chicago()
|
|
assert result.tzinfo is not None
|
|
|
|
def test_returns_chicago_tz(self):
|
|
"""now_chicago() should return a datetime in America/Chicago timezone."""
|
|
result = now_chicago()
|
|
assert result.tzinfo == CHICAGO_TZ
|
|
|
|
|
|
class TestToChicago:
|
|
"""Tests for to_chicago()."""
|
|
|
|
def test_converts_utc_datetime(self):
|
|
"""to_chicago() should correctly convert a UTC datetime to Chicago time."""
|
|
# 2024-07-15 18:00 UTC = 2024-07-15 13:00 CDT (UTC-5 during summer)
|
|
utc_dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
result = to_chicago(utc_dt)
|
|
assert result.hour == 13
|
|
assert result.tzinfo == CHICAGO_TZ
|
|
|
|
def test_handles_naive_datetime_assumes_utc(self):
|
|
"""to_chicago() should treat naive datetimes as UTC."""
|
|
naive_dt = datetime(2024, 7, 15, 18, 0, 0)
|
|
result = to_chicago(naive_dt)
|
|
# Same as converting from UTC
|
|
utc_dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
expected = to_chicago(utc_dt)
|
|
assert result.hour == expected.hour
|
|
assert result.tzinfo == CHICAGO_TZ
|
|
|
|
def test_preserves_already_chicago(self):
|
|
"""to_chicago() on an already-Chicago datetime should be a no-op."""
|
|
chicago_dt = datetime(2024, 7, 15, 13, 0, 0, tzinfo=CHICAGO_TZ)
|
|
result = to_chicago(chicago_dt)
|
|
assert result.hour == 13
|
|
assert result.tzinfo == CHICAGO_TZ
|
|
|
|
def test_winter_offset(self):
|
|
"""to_chicago() should use CST (UTC-6) during winter months."""
|
|
# 2024-01-15 18:00 UTC = 2024-01-15 12:00 CST (UTC-6 during winter)
|
|
utc_dt = datetime(2024, 1, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
result = to_chicago(utc_dt)
|
|
assert result.hour == 12
|
|
|
|
|
|
class TestToDiscordTimestamp:
|
|
"""Tests for to_discord_timestamp()."""
|
|
|
|
def test_default_style(self):
|
|
"""to_discord_timestamp() should use 'f' style by default."""
|
|
dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
result = to_discord_timestamp(dt)
|
|
unix_ts = int(dt.timestamp())
|
|
assert result == f"<t:{unix_ts}:f>"
|
|
|
|
def test_relative_style(self):
|
|
"""to_discord_timestamp() with style='R' should produce relative format."""
|
|
dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
result = to_discord_timestamp(dt, style="R")
|
|
unix_ts = int(dt.timestamp())
|
|
assert result == f"<t:{unix_ts}:R>"
|
|
|
|
def test_all_styles(self):
|
|
"""to_discord_timestamp() should support all Discord timestamp styles."""
|
|
dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
unix_ts = int(dt.timestamp())
|
|
for style in ("R", "f", "F", "t", "T", "d", "D"):
|
|
result = to_discord_timestamp(dt, style=style)
|
|
assert result == f"<t:{unix_ts}:{style}>"
|
|
|
|
def test_naive_datetime_assumes_utc(self):
|
|
"""to_discord_timestamp() should treat naive datetimes as UTC."""
|
|
naive_dt = datetime(2024, 7, 15, 18, 0, 0)
|
|
aware_dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
assert to_discord_timestamp(naive_dt) == to_discord_timestamp(aware_dt)
|
|
|
|
def test_chicago_datetime_same_instant(self):
|
|
"""to_discord_timestamp() should produce the same unix timestamp regardless of tz."""
|
|
utc_dt = datetime(2024, 7, 15, 18, 0, 0, tzinfo=timezone.utc)
|
|
chicago_dt = utc_dt.astimezone(CHICAGO_TZ)
|
|
assert to_discord_timestamp(utc_dt) == to_discord_timestamp(chicago_dt)
|