
Dogwalk: The Timezone Shift the Client Would Have Noticed First
The three-hour shift
The date rendered on the interface came from a timezone-naive datetime field. When daily aggregates arrived from the database in UTC and the frontend interpreted them as local, the NextWalkHero countdown could jump three hours forward or backward depending on the query path. No test complained: types matched, formats matched, only the anchor was wrong.
The frustration was that no single place owned the flaw. Every backend module constructed time manually — datetime.now() here, a naive string there — and the source of truth differed across files.
The conflict: dependency hardening
The push came from dependencies. Starting at version 0.0.45, sqlmodel began enforcing explicit tz-aware datetime objects. Naive values that previously passed seed and rollback steps started crashing backend boots. With the engine stuck, it became clear the issue was never a single bad function—it was the lack of a unified time contract. To make things worse, integration tests suffered from a login lock leak: login_attempts survived transaction rollbacks and contaminated subsequent tests, making CI flake unpredictably.
The resolution: three commits, one contract
The 10/05 round landed in master across three fronts:
- Pinned sqlmodel (
aa484452): locked at<0.0.45while the migrationb1_timezone_aware_datetimes.pymatures—re-enabling reliable boots without broken updates. - Cleared login_attempts in conftest (
989f8216): state leaks between integration tests are explicitly purged, ensuring each test starts fresh. - Unified time helper (
ae7622ae):app/core/time.pycentralizes datetime creation backed bytest_time_contract.py. Following that (7a8f579d), the schema turns tz-aware andNextWalkHerosafely converts to local time on display (3c91e277) — the exact commit returning the correct walk schedule to the user.
The helper remains deliberately lean: UTC at the server boundary, conversion only where the user looks.
from datetime import datetime, timezone
def get_utc_now() -> datetime:
"""Returns the current UTC timestamp with explicit timezone."""
return datetime.now(timezone.utc)
Why three hours and not zero
Migration b1_timezone_aware_datetimes reflects the choice: instead of feigning neutrality, the database adopts timezone awareness. The client didn’t need to know about sqlmodel or the helper—but a three-hour offset in the next walk card would have sparked immediate notice. Now the test contract speaks up first.