---
paths:
  - "src/**/*.py"
  - "tests/**/*.py"
---

# Datetime handling rule (two-zone discipline)

**Never** construct or read "now" directly. All datetime flows through
`everos.component.utils.datetime`. This is a **hard CI gate**
(`make check-datetime`, wired into `make lint`).

## Banned (the checker fails the build on these)

- `datetime.now()`, `datetime.utcnow()`, `datetime.today()`
- `time.time()`, `time.time_ns()`
- `datetime(YYYY, ...)` without `tzinfo=`
- `.astimezone(...)` / `.replace(tzinfo=...)` outside the helper module

## Use instead

| Need | Helper |
|---|---|
| "now" for **storage** (UTC) | `get_utc_now()` |
| "now" for **display** (configured TZ) | `get_now_with_timezone()` |
| today's date, display TZ | `today_with_timezone()` |
| normalize a value to UTC | `ensure_utc(d)` |
| render to display TZ | `to_display_tz(d)` |
| parse ISO / epoch / str | `from_iso_format(v)`, `from_timestamp(ts)` |
| serialize | `to_iso_format(d)`, `to_date_str(d)`, `to_timestamp_ms(d)` |

**Two zones**: persist in UTC, present in the configured display TZ. Crossing them
goes through the helpers — never ad-hoc. See [docs/datetime.md](../../docs/datetime.md).
