From 175305a28f59d3caa0af1f48f8fedca12f48b443 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 5 May 2026 14:52:06 -0400 Subject: [PATCH] fix: calendar event UTC times and slot query range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CalDAV DTSTART/DTEND now use UTC (Z-suffix) instead of TZID local time. Without a VTIMEZONE component, some CalDAV servers strip the TZID on return, causing ical.js to read the times as UTC — shifting every event 4 hours early and letting taken slots appear free. Slot query range changed from ±6h around UTC midnight (36-hour window) to 3AM–6AM UTC the next day (~27h) which covers the full ET business day without pulling in afternoon events from the previous day. Co-Authored-By: Claude Sonnet 4.6 --- estore/src/app/api/slots/route.ts | 11 ++++++----- estore/src/lib/caldav.ts | 25 ++++--------------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/estore/src/app/api/slots/route.ts b/estore/src/app/api/slots/route.ts index 8f4f181..98a7c76 100644 --- a/estore/src/app/api/slots/route.ts +++ b/estore/src/app/api/slots/route.ts @@ -20,11 +20,12 @@ export async function GET(req: NextRequest) { return NextResponse.json({ error: 'Invalid tier' }, { status: 400 }) try { - // Widen the CalDAV query by ±6 h around the day to handle DST edge cases. - const dayStart = new Date(`${date}T00:00:00Z`) - dayStart.setUTCHours(dayStart.getUTCHours() - 6) - const dayEnd = new Date(`${date}T23:59:59Z`) - dayEnd.setUTCHours(dayEnd.getUTCHours() + 6) + // Cover the full ET day: EDT=UTC-4 (midnight=4AM UTC), EST=UTC-5 (midnight=5AM UTC). + // Start at 3AM UTC (before midnight ET in any DST state) to avoid pulling in + // afternoon events from the previous calendar day. + const dayStart = new Date(`${date}T03:00:00Z`) + const dayEnd = new Date(`${date}T06:00:00Z`) + dayEnd.setUTCDate(dayEnd.getUTCDate() + 1) // next day 6AM UTC (after last EDT slot) const CALDAV_TIMEOUT_MS = 6_000 diff --git a/estore/src/lib/caldav.ts b/estore/src/lib/caldav.ts index d2d777f..d86f403 100644 --- a/estore/src/lib/caldav.ts +++ b/estore/src/lib/caldav.ts @@ -60,23 +60,6 @@ function toIcalDate(d: Date): string { return d.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '') } -/** Local time in America/New_York for DTSTART/DTEND — used with TZID property */ -function toIcalDateET(d: Date): string { - const parts = new Intl.DateTimeFormat('en-US', { - timeZone: 'America/New_York', - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, - }).formatToParts(d) - const p: Record = {} - for (const { type, value } of parts) p[type] = value - const h = p.hour === '24' ? '00' : p.hour // Intl can emit '24' for midnight - return `${p.year}${p.month}${p.day}T${h}${p.minute}${p.second}` -} export interface CalendarLineItem { name: string @@ -152,8 +135,8 @@ export async function createDeliveryEvent(params: { 'BEGIN:VEVENT', `UID:${uid}`, `DTSTAMP:${toIcalDate(new Date())}`, - `DTSTART;TZID=America/New_York:${toIcalDateET(startTime)}`, - `DTEND;TZID=America/New_York:${toIcalDateET(endTime)}`, + `DTSTART:${toIcalDate(startTime)}`, + `DTEND:${toIcalDate(endTime)}`, foldLine(`SUMMARY:${icalEscape(customerName)}`), foldLine(`LOCATION:${icalEscape(address)}`), foldLine(`DESCRIPTION:${icalEscape(descParts)}`), @@ -213,8 +196,8 @@ export async function createPickupEvent(params: { 'BEGIN:VEVENT', `UID:${uid}`, `DTSTAMP:${toIcalDate(new Date())}`, - `DTSTART;TZID=America/New_York:${toIcalDateET(startTime)}`, - `DTEND;TZID=America/New_York:${toIcalDateET(endTime)}`, + `DTSTART:${toIcalDate(startTime)}`, + `DTEND:${toIcalDate(endTime)}`, foldLine(`SUMMARY:${icalEscape(customerName)}`), 'LOCATION:Beach Party Balloons\\, 554 Boston Post Rd\\, Milford CT', foldLine(`DESCRIPTION:${icalEscape(descParts)}`),