Skip to content

fix(display): respect TZ env on every CLI/MCP timestamp render#205

Merged
pszymkowiak merged 1 commit into
developfrom
fix/respect-tz-on-display
May 10, 2026
Merged

fix(display): respect TZ env on every CLI/MCP timestamp render#205
pszymkowiak merged 1 commit into
developfrom
fix/respect-tz-on-display

Conversation

@pszymkowiak
Copy link
Copy Markdown
Contributor

Summary

Closes #119.

Storage stays in UTC (`DateTime`) — that's the right canonical form for ordering and serialization. But every user-facing timestamp was also printed in UTC, even with `TZ=Asia/Bangkok` set, because the display sites called `dt.format(...)` directly on the UTC value.

Fix

  • New helper `icm_core::format_local(dt, fmt)` — wraps `dt.with_timezone(&Local).format(fmt).to_string()`. `chrono::Local` honours the `TZ` env var on Unix (via libc) and the system locale on Windows.
  • Threaded through every display-side formatter:
    • CLI (`icm-cli`): `stats`, `show`, `recall` hit ts, `memoir show`, `concept show`, `transcript list/show/stats` (~10 sites).
    • MCP (`icm_stats` tool): oldest / newest.
  • Unchanged (machine-readable, intentional): `cloud.rs` and `web.rs` keep `to_rfc3339` (UTC). API consumers parse offsets correctly; humans read the CLI.

Smoke test on the release binary

```
$ TZ=UTC icm stats | grep Oldest # 2026-05-10 08:12
$ TZ=Asia/Bangkok icm stats | grep Oldest # 2026-05-10 15:12 (+7)
$ TZ=America/Los_Angeles icm stats | grep Oldest # 2026-05-10 01:12 (-7)
```

Test plan

  • 2 unit tests on `format_local` (non-empty, agrees with explicit `with_timezone(&Local)`)
  • Binary smoke test across 3 timezones (above)
  • `cargo test --workspace` — 409 passed, 4 ignored
  • `cargo clippy --workspace --all-targets -- -D warnings` — clean
  • `cargo fmt --check` — clean

The TZ-shift correctness is asserted via the binary smoke test rather than a unit test because `chrono::Local` reads the env var once and Rust tests run in parallel — mutating `TZ` from inside one test would be racy with siblings.

🤖 Generated with Claude Code

Closes #119.

Storage stays in UTC (`DateTime<Utc>`) — that's the right canonical form
for ordering and serialization. But every user-facing timestamp was
also printed in UTC, even with `TZ=Asia/Bangkok` set, because the
display sites called `dt.format(...)` directly on the UTC value.

Add `icm_core::format_local(&dt, fmt)`. `chrono::Local` honours the
`TZ` env var on Unix (libc) and the system locale on Windows, so a
single conversion at each display boundary is enough. Replace every
display-side formatter in `icm-cli` (stats, show, memoir/concept,
transcript list/show/stats, recall hit timestamp) and in the
`icm_stats` MCP tool. Cloud and web JSON outputs keep `to_rfc3339`
(UTC, machine-readable) — that contract is unchanged.

Smoke test on the patched binary:

    $ TZ=UTC                  icm stats | grep Oldest  # 08:12
    $ TZ=Asia/Bangkok         icm stats | grep Oldest  # 15:12  (+7)
    $ TZ=America/Los_Angeles  icm stats | grep Oldest  # 01:12  (-7)

Two unit tests on the helper. The TZ-shift verification has to be
done via the binary smoke test because `chrono::Local` reads the env
once and Rust unit tests run in parallel — mutating TZ from inside a
test is racy with sibling tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pszymkowiak pszymkowiak merged commit 6deca6f into develop May 10, 2026
7 checks passed
@pszymkowiak pszymkowiak deleted the fix/respect-tz-on-display branch May 10, 2026 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant