fix(utils): now() returns timezone-aware UTC - #11
Closed
jcfrei wants to merge 1 commit into
Closed
Conversation
… instant now() returned a naive isoformat() (no zone). A client parsing it (JS `new Date(...)`) reads a zone-less datetime as browser-local, so a timestamp stamped on a UTC server rendered shifted by the viewer's UTC offset — a note set server-side showed ~2h behind in Zürich, while a record whose timestamp the frontend set via toISOString() (with Z) showed correctly. The two producers disagreed on the encoding of the same instant. now() now returns datetime.now(timezone.utc).isoformat() (…+00:00), so every stored creation/modified/occurred_at is an unambiguous instant. nowdate() is unchanged (date-only fields stay zone-free). No core code parses these datetime strings back for comparison (only date-only fields via date.fromisoformat), and auth already uses aware UTC for JWT expiry — so nothing mixes naive/aware. Existing rows keep their naive value; only new writes carry the zone. tests/test_utils_now.py guards the format (aware UTC datetime, date-only date); wired into CI as a backend-agnostic step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jcfrei
added a commit
that referenced
this pull request
Jul 29, 2026
fix(utils): now() returns timezone-aware UTC — timestamps read as one instant (#11) now() returned a naive isoformat(); a client parsing it as browser-local rendered UTC-server timestamps shifted by the viewer's offset (e.g. an API-set CRM note ~2h behind in Zürich). now() now returns datetime.now(timezone.utc).isoformat(). nowdate() unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Squash-merged into master as 3e2dc31 and released as v0.6.7. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Surfaced by a CRM note set via the API showing ~2h behind the actual Zürich time.
now()returned a naiveisoformat()— no timezone. A client parsing that string (new Date(...)) reads a zone-less datetime as browser-local, so a timestamp stamped on a UTC server renders shifted by the viewer's UTC offset. Notes whose timestamp the frontend set viatoISOString()(withZ) rendered correctly, so the two producers of the same instant disagreed on encoding — API/chat-set timestamps drifted, UI-set ones didn't.Fix
now()→datetime.now(timezone.utc).isoformat()(…+00:00). Every storedcreation/modified/occurred_atis now an unambiguous instant that round-trips correctly.nowdate()is unchanged — date-only fields (posting_date,due_date) stay zone-free.Safety
fromisoformatcalls are on date-only fields (date.fromisoformat), which usenowdate().api/auth.pyalready uses aware UTC (datetime.now(timezone.utc)) for JWT expiry, so nothing mixes naive/aware datetimes.…+00:00) and old (naive) values share the fixed-widthYYYY-MM-DDTHH:MM:SS.ffffffprefix, so time ordering is preserved.Test
tests/test_utils_now.pyassertsnow()is aware UTC andnowdate()is date-only; wired into CI as a backend-agnostic step.🤖 Generated with Claude Code