Found while reviewing #600 (day-aware iCal, part of #542). Pre-existing, not introduced there.
In functions/api/feeds/ical.js generateICal(), DTSTART and DTEND both use the same calendar date:
const dtstart = `${eventDate.replace(/-/g, "")}T${startTime.replace(/:/g, "")}00`;
const dtend = `${eventDate.replace(/-/g, "")}T${endTime.replace(/:/g, "")}00`;
A set that crosses midnight (e.g. 23:30–00:30) gets DTEND before DTSTART on the same date — an invalid VEVENT that calendar clients may reject or render with garbage duration. The frontend already handles this in prepareBands() (if (endMs < startMs) endMs += MS_PER_DAY); the iCal generator needs the same rule: when endTime < startTime (string compare works for zero-padded HH:MM), stamp DTEND with eventDate + 1 day.
Note this is distinct from after-midnight sets (start 00:00–05:59), which are fine — both stamps share the stored date. Only sets straddling midnight are affected.
Found while reviewing #600 (day-aware iCal, part of #542). Pre-existing, not introduced there.
In
functions/api/feeds/ical.jsgenerateICal(), DTSTART and DTEND both use the same calendar date:A set that crosses midnight (e.g.
23:30–00:30) getsDTENDbeforeDTSTARTon the same date — an invalid VEVENT that calendar clients may reject or render with garbage duration. The frontend already handles this inprepareBands()(if (endMs < startMs) endMs += MS_PER_DAY); the iCal generator needs the same rule: whenendTime < startTime(string compare works for zero-padded HH:MM), stamp DTEND witheventDate + 1 day.Note this is distinct from after-midnight sets (start 00:00–05:59), which are fine — both stamps share the stored date. Only sets straddling midnight are affected.