Skip to content

Cache expiry math is timezone-dependent: zone-less UTC timestamps parsed as local time #208

Description

@josephkehan-prog

The bug

src/cache/store.ts persists timestamps via toIsoSeconds() as zone-less UTC — "YYYY-MM-DD HH:MM:SS", deliberately matching SQLite's datetime('now') format. But the three read sites parse them back with new Date(str):

  • isExpired() — store.ts:285
  • isCacheUsable() — store.ts:297
  • getCachedSearch() — store.ts:436

JavaScript's Date parser treats the space-separated, zone-less form as local time, so every expiry comparison silently shifts by the host's UTC offset.

Impact

  • West of UTC (e.g. UTC-4): every cached row appears to expire 4h later than it should — expired/stale content is served as fresh, and mode: fast's stale: true marker never fires inside the shifted window. Search cache TTL stretches the same way.
  • East of UTC: rows expire early — cache hit rate silently drops.

Not theoretical: on a UTC-4 machine, npm test on current main (185afb5) fails 14 unit tests (tests/unit/tools/fetch-mode.test.ts stale-window tests and friends); all pass under TZ=UTC. CI never sees it because runners are UTC.

Proposed fix

Small and local: a parseUtcTimestamp() helper next to toIsoSeconds() that re-attaches the UTC marker (value.replace(' ', 'T') + 'Z') when the string matches the zone-less format, falls through unchanged otherwise (so any Z-suffixed or ISO rows keep working). Swap the three new Date(...) call sites to it. No schema or stored-format change — existing rows parse correctly immediately.

Plus a regression test that sets process.env.TZ to a non-UTC zone (skipped on win32, where Node ignores runtime TZ changes) so UTC CI runners can still catch a reintroduction.

Happy to PR this right away — branch and tests ready to push. (Flagging per CONTRIBUTING.md: opening the PR means agreeing to the CLA.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions