fix(activities): stamp local tz offset on event datetimes before submit #66

Merged
padreug merged 1 commit from fix/activities-event-time-tz-offset into dev 2026-05-23 13:29:48 +00:00
Owner

Summary

  • User reports: created an activity with 8:00 — 10:00, the detail page shows 10:00 — 12:00. A clean +offset shift in CEST (+02:00).
  • Root cause: the form sent a naive ISO string "YYYY-MM-DDTHH:MM" to the LNbits events backend. The backend's _to_unix in events/nostr_publisher.py:28-33 does datetime.fromisoformat(value) and, when tzinfo is None, assumes UTC. So 08:00 entered in CEST is stored as the unix for 08:00 UTC, and the NIP-52 start tag lands on the relays at the wrong instant. The detail page reads that unix and renders in local time → 08:00 UTC = 10:00 CEST.
  • (The Events list page wasn't showing the bug because it parses the raw LNbits string via new Date(...), which JS treats as local — so it displayed 8:00 and the offset bug was invisible there.)
  • Fix: a small withLocalTzOffset helper in CreateEventDialog.vue stamps the form's wall-clock string with the user's UTC offset ("2026-05-24T08:00""2026-05-24T08:00:00+02:00") before the create/update payload goes to the backend. Python's fromisoformat then honors the offset and the unix lands correctly. Date-only values pass through unchanged. Seconds (:00) are included for pre-3.11 fromisoformat compatibility.
  • Edit round-trip unchanged: splitDateTime already trims time to "HH:MM", so the suffix drops cleanly when re-populating the form.
  • Per CLAUDE.md "source of truth, don't re-derive": the wall-clock input is the canonical user intent, and the fix preserves it across the API boundary instead of letting it pass through an assume-UTC interpretation.

Test plan

  • Create a new time-based activity at e.g. 08:00 — 10:00 in your local tz.
  • Open the detail page (/activities/<id>) — verify it shows 08:00 — 10:00, not +offset.
  • Open the same activity in the Events list — verify the displayed time also matches (it already did pre-fix, but should not regress).
  • Edit that activity — verify the time inputs repopulate as 08:00 / 10:00.
  • Save the edit without changes — verify the detail page still shows 08:00 — 10:00.
  • Sanity: create a date-only activity (no start time) — verify it still renders as a date with no time component.
  • Existing pre-fix activities will keep showing the +offset on the detail page until re-edited and re-saved (the bad unix is on the relays). Worth noting in release notes but not a blocker.

🤖 Generated with Claude Code

## Summary - User reports: created an activity with `8:00 — 10:00`, the detail page shows `10:00 — 12:00`. A clean +offset shift in CEST (+02:00). - Root cause: the form sent a *naive* ISO string `"YYYY-MM-DDTHH:MM"` to the LNbits events backend. The backend's `_to_unix` in `events/nostr_publisher.py:28-33` does `datetime.fromisoformat(value)` and, when `tzinfo is None`, **assumes UTC**. So `08:00` entered in CEST is stored as the unix for `08:00 UTC`, and the NIP-52 `start` tag lands on the relays at the wrong instant. The detail page reads that unix and renders in local time → `08:00 UTC = 10:00 CEST`. - (The Events list page wasn't showing the bug because it parses the raw LNbits string via `new Date(...)`, which JS treats as local — so it displayed `8:00` and the offset bug was invisible there.) - Fix: a small `withLocalTzOffset` helper in `CreateEventDialog.vue` stamps the form's wall-clock string with the user's UTC offset (`"2026-05-24T08:00"` → `"2026-05-24T08:00:00+02:00"`) before the create/update payload goes to the backend. Python's `fromisoformat` then honors the offset and the unix lands correctly. Date-only values pass through unchanged. Seconds (`:00`) are included for pre-3.11 `fromisoformat` compatibility. - Edit round-trip unchanged: `splitDateTime` already trims time to `"HH:MM"`, so the suffix drops cleanly when re-populating the form. - Per CLAUDE.md "source of truth, don't re-derive": the wall-clock input is the canonical user intent, and the fix preserves it across the API boundary instead of letting it pass through an assume-UTC interpretation. ## Test plan - [x] Create a new time-based activity at e.g. `08:00 — 10:00` in your local tz. - [x] Open the detail page (`/activities/<id>`) — verify it shows `08:00 — 10:00`, not `+offset`. - [x] Open the same activity in the Events list — verify the displayed time also matches (it already did pre-fix, but should not regress). - [x] Edit that activity — verify the time inputs repopulate as `08:00` / `10:00`. - [x] Save the edit without changes — verify the detail page still shows `08:00 — 10:00`. - [x] Sanity: create a date-only activity (no start time) — verify it still renders as a date with no time component. - [x] Existing pre-fix activities will keep showing the +offset on the detail page until re-edited and re-saved (the bad unix is on the relays). Worth noting in release notes but not a blocker. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The form sent naive "YYYY-MM-DDTHH:MM" to the LNbits events backend,
where _to_unix (nostr_publisher.py) assumes UTC when tzinfo is None.
So 08:00 entered in CEST got stored as 08:00 UTC, and the NIP-52 start
tag landed on the relays at the wrong instant — the detail page then
re-localized it to 10:00 (offset doubly applied).

Stamp the wall-clock value with the user's UTC offset before sending so
the backend builds the correct unix and the detail page renders the
intended wall-clock. Seconds (`:00`) included for pre-3.11 Python
fromisoformat compatibility. Round-trips through edit mode unchanged:
splitDateTime trims to "HH:MM" so the suffix drops cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug deleted branch fix/activities-event-time-tz-offset 2026-05-23 13:29:48 +00:00
Sign in to join this conversation.
No description provided.