/** * Event date/time serialization for the write path (BUG A fix). * * The bug: EventForm previously sent a NAIVE local wall-clock string * (`${date}T${time}:00`, e.g. "2026-06-07T09:00:00") with NO timezone offset. * The API outbox worker then did `new Date(thatString)`, which Node parses in * the SERVER container's local timezone (UTC in Docker). So "09:00" Toronto * became 09:00 UTC, and buildVeventString serialized it as `090000Z`, which * displays back as 05:00 EDT — a 4-hour error. * * The fix: serialize timed events to an UNAMBIGUOUS instant here in the browser, * where the operator's timezone IS known. `new Date(date + 'T' + time)` is parsed * in the browser's local zone (the operator's wall clock), and `.toISOString()` * converts that instant to a correct UTC `...Z` string. The worker's `new Date()` * then parses an unambiguous UTC instant regardless of container timezone, and * the round-trip preserves the operator's wall-clock time. * * All-day events carry no time component and remain `YYYY-MM-DD` DATE strings * (D-13) — they are timezone-independent by contract and must NOT be converted. */ /** * Serialize the start/end of an event for the create/update payload. * * @param allDay when true, returns the date strings unchanged (DATE contract, D-13) * @param startDate 'YYYY-MM-DD' * @param startTime 'HH:MM' (ignored when allDay) * @param endDate 'YYYY-MM-DD' * @param endTime 'HH:MM' (ignored when allDay) * @returns { start, end } — for timed events, ISO-8601 UTC instants ('...Z'); * for all-day events, the raw 'YYYY-MM-DD' date strings. */ export function serializeEventDateTime( allDay: boolean, startDate: string, startTime: string, endDate: string, endTime: string, ): { start: string; end: string } { if (allDay) { // DATE contract (D-13): no time component, timezone-independent. return { start: startDate, end: endDate } } // Timed: build the instant from local wall-clock parts (browser is in the // operator's zone) and serialize to a UTC instant so the wire value is // unambiguous and container-timezone-independent. return { start: localWallClockToUtcIso(startDate, startTime), end: localWallClockToUtcIso(endDate, endTime), } } /** * Convert a local wall-clock date+time to a UTC ISO-8601 instant. * `new Date('YYYY-MM-DDTHH:MM:00')` (no offset) is parsed in the browser's * local timezone per ECMAScript, giving the correct instant for the operator. */ export function localWallClockToUtcIso(date: string, time: string): string { return new Date(`${date}T${time}:00`).toISOString() }