From 6cd420d9cbdeaac904cc714c04d8ff04ff6837d6 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 23 May 2026 15:21:40 +0200 Subject: [PATCH] fix(activities): stamp local tz offset on event datetimes before submit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../components/CreateEventDialog.vue | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/modules/activities/components/CreateEventDialog.vue b/src/modules/activities/components/CreateEventDialog.vue index 180184b..7f39fc0 100644 --- a/src/modules/activities/components/CreateEventDialog.vue +++ b/src/modules/activities/components/CreateEventDialog.vue @@ -87,6 +87,28 @@ function foldDateTime(date: string, time: string): string { return time ? `${date}T${time}` : date } +// Stamp the form's wall-clock datetime with the user's local UTC offset +// before sending it to the LNbits events backend. Without this, the +// backend's `_to_unix` (nostr_publisher.py) treats a naive ISO string +// as UTC, so e.g. "08:00" entered in CEST gets stored as 08:00 UTC and +// the NIP-52 `start` tag is off by the user's offset on the relay +// — the detail page then renders it +offset (08:00 → 10:00 in CEST). +// Preserving the user's intended wall-clock means stamping it here. +// Date-only values (no "T") pass through unchanged. +function withLocalTzOffset(value: string): string { + if (!value || !value.includes('T')) return value + // The form's "YYYY-MM-DDTHH:MM" is parsed by JS Date as local time; + // getTimezoneOffset() returns minutes west of UTC, so negate it. + const offMin = -new Date(value).getTimezoneOffset() + const sign = offMin >= 0 ? '+' : '-' + const abs = Math.abs(offMin) + const hh = String(Math.floor(abs / 60)).padStart(2, '0') + const mm = String(abs % 60).padStart(2, '0') + // Include `:00` seconds for compatibility with older Python + // `datetime.fromisoformat` (pre-3.11 won't accept "HH:MM+HH:MM"). + return `${value}:00${sign}${hh}:${mm}` +} + const formSchema = toTypedSchema( z .object({ @@ -138,8 +160,11 @@ interface BannerImage extends UploadedImage { } const bannerImages = ref([]) -// Inverse of foldDateTime: split a stored "YYYY-MM-DD[THH:MM]" back -// into separate date + time pieces for the form inputs. +// Inverse of foldDateTime: split a stored "YYYY-MM-DD[THH:MM[:SS][±HH:MM]]" +// back into separate date + time pieces for the form inputs. The +// time slice trims to "HH:MM" so any seconds + offset suffix added by +// withLocalTzOffset on submit drops cleanly — the user sees the same +// wall-clock they originally entered when re-editing. function splitDateTime(value: string | null | undefined): { date: string; time: string } { if (!value) return { date: '', time: '' } const [date, time = ''] = value.split('T') @@ -267,9 +292,8 @@ const onSubmit = form.handleSubmit(async (formValues) => { try { const eventData: CreateEventRequest = { name: formValues.name, - event_start_date: foldDateTime( - formValues.event_start_date, - formValues.event_start_time + event_start_date: withLocalTzOffset( + foldDateTime(formValues.event_start_date, formValues.event_start_time) ), } if (!isEditMode.value) { @@ -281,9 +305,8 @@ const onSubmit = form.handleSubmit(async (formValues) => { // Optional fields — only include if provided if (formValues.info) eventData.info = formValues.info if (formValues.event_end_date) { - eventData.event_end_date = foldDateTime( - formValues.event_end_date, - formValues.event_end_time + eventData.event_end_date = withLocalTzOffset( + foldDateTime(formValues.event_end_date, formValues.event_end_time) ) } if (formValues.location) eventData.location = formValues.location