major-domo-v2/tests/test_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

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)