fix: measure the SGA Spaces buffer in the timezone it is stored in (#87) - #89
Conversation
The advance-notice requirement rejected bookings that were far enough out. With it set to two hours it behaved like six in summer and seven in winter, which is why it is currently set to 0 in app_settings -- the only way to stop it blocking legitimate bookings. SGA Spaces stores times as Boston wall-clock digits with a Z on the end: a 6 PM booking is T18:00:00.000Z whatever the offset is that month. slotToIso sets UTC hours straight from the slot index, the modal reads them back with getUTCHours, and the confirmation email depends on it too -- there is a comment in space-booking-confirmed.ts saying UTC fields give the right local-time digits to pair with TZID=America/New_York. That is self-consistent until something compares one of those values against a real instant. The check did exactly that: start_time against Date.now(). Every booking therefore looked one UTC offset earlier than it really was, and the requirement rejected anything inside minHours + 4, or + 5 once the clocks go back. Which is why the calendar disagreed with the server. The shading uses wallClockNow, already in the stored domain, so it correctly showed the slot as available -- and then submitting it failed. "Now" is therefore taken as Boston wall clock, in the same domain as the value it is compared against. Pinned to America/New_York rather than the server's clock, because Vercel runs in UTC and reading local fields there is the same bug with an extra step. Intl resolves the offset for the instant given, so EDT and EST both work without a table. Also fixes the same root cause in remaining-hours, which derived its Sun-Sat window from real UTC. UTC is already Sunday by 8 PM on a Saturday in EDT, so for the last four hours of the week a user's hours were counted against the next one and the remaining figure jumped. Half a domain bug is worse than none. Verified by running the check both ways against fixed instants in each season. With a two-hour requirement in EDT, the old comparison rejected every booking up to five hours out and the new one rejects only those under two; in EST the old rejected up to six. Wall clock resolves to 11:46 from 15:46Z in September and to 11:00 from 16:00Z in January, so a hardcoded offset would have been wrong for half the year. min_hours_advance_spaces can go back to a real value once this ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The kiosk displays themselves were already right, and deliberately so: the page compares local minutes-from-midnight against the stored value's UTC hours and minutes, which are the wall-clock digits, so both sides are in the same domain. There is a comment saying as much, and the date-flip trap is handled in the page as well -- local date components rather than toISOString, which would roll over at 8 PM EDT. What was still armed is the server's fallback for a missing `date` parameter, which took the UTC date. After 8 PM EDT that is already tomorrow, so a caller without the parameter would be served the wrong day's bookings. The display page always sends it, so nothing reaches this today -- but it is the same trap as the booking buffer, and leaving it set for the next caller is how the buffer bug got written in the first place. Verified at 9:30 PM EDT: the kiosk still shows a 9-10 PM booking as in use, and the two fallbacks disagree by a day exactly as expected -- UTC says the 9th, Boston says the 8th. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added The displays were already correctNot by luck. The page compares like with like: Both sides are in the same wall-clock domain, so "in use now" is right. There is already a comment saying so, and the date-flip trap is handled in the page too — local date components rather than One latent gap, now closedThe server's fallback for a missing Verified at 9:30 PM EDT
So: nothing about the kiosk behaviour changes, and the one place that could have gone wrong no longer can. |
Closes #87. No migration.
What was wrong
With the requirement set to two hours it behaved like six in summer and seven in winter. That's why
min_hours_advance_spacesis currently0inapp_settings— the only way to stop it blocking legitimate bookings.SGA Spaces stores times as Boston wall-clock digits with a Z on the end: a 6 PM booking is
T18:00:00.000Zwhatever the offset is that month.slotToIsosets UTC hours straight from the slot index, the modal reads them back withgetUTCHours, and the confirmation email depends on it too — there's already a comment inspace-booking-confirmed.tssaying UTC fields give the right local-time digits to pair withTZID=America/New_York.Self-consistent, until something compares one of those values against a real instant. The check did exactly that —
start_timeagainstDate.now()— so every booking looked one UTC offset earlier than it was, and the requirement rejected anything insideminHours + 4(or+ 5after the clocks go back).This is also why the calendar disagreed with the server. The shading uses
wallClockNow, already in the stored domain, so it correctly showed the slot as available — and then submitting failed.The fix
"Now" is taken as Boston wall clock, in the same domain as the value it's compared against. Pinned to
America/New_Yorkrather than the server's clock, because Vercel runs in UTC and reading local fields there is the same bug with an extra step.Intlresolves the offset for the given instant, so EDT and EST both work without a table.Also fixed: the same root cause in
remaining-hoursIt derived its Sun–Sat window from real UTC. UTC is already Sunday by 8 PM Saturday in EDT, so for the last four hours of the week a user's hours were counted against the next week and the remaining figure jumped. Out of scope for the issue as written, but fixing half a domain bug is worse than fixing none.
Verification
Ran the check both ways against fixed instants in each season, with a 2-hour requirement:
Wall clock resolves to 11:46 from
15:46Zin September and 11:00 from16:00Zin January — a hardcoded −4 would have been wrong for half the year.After merging
min_hours_advance_spacescan go back to a real value; it's at0today as a workaround.🤖 Generated with Claude Code