fix(03): CR-03 convert exclusive all-day end to inclusive on edit pre-fill
This commit is contained in:
@@ -74,6 +74,27 @@ function writeLastCalendarUrl(url: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CR-03: convert an EXCLUSIVE all-day end date ('YYYY-MM-DD') to the INCLUSIVE
|
||||
* last day the form displays. The occurrence/expand contract keeps all-day ends
|
||||
* exclusive (matching the DTEND Fastmail stores, RFC-5545 §3.6.1), and
|
||||
* buildVeventString re-advances the inclusive form value by one day on write.
|
||||
* Without this subtraction, round-tripping an edit re-advances an already-exclusive
|
||||
* end, silently growing multi-day all-day events by one day per save. Parses by
|
||||
* UTC components so the roll-back is DST-safe (mirrors vevent.ts's roll-forward).
|
||||
*/
|
||||
function exclusiveEndToInclusiveDate(dateStr: string): string {
|
||||
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr)
|
||||
if (!m) return dateStr
|
||||
const [, y, mo, d] = m
|
||||
const date = new Date(Date.UTC(Number(y), Number(mo) - 1, Number(d)))
|
||||
date.setUTCDate(date.getUTCDate() - 1)
|
||||
const yy = String(date.getUTCFullYear())
|
||||
const mm = String(date.getUTCMonth() + 1).padStart(2, '0')
|
||||
const dd = String(date.getUTCDate()).padStart(2, '0')
|
||||
return `${yy}-${mm}-${dd}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an ISO date string (possibly with time + offset) into
|
||||
* { date: 'YYYY-MM-DD', time: 'HH:MM' }. Falls back to today/09:00 if malformed.
|
||||
@@ -143,12 +164,17 @@ export function EventForm() {
|
||||
|
||||
const initStart = occurrence ? parseDateTime(occurrence.start) : { date: todayIso(), time: '09:00' }
|
||||
const initEnd = occurrence ? parseDateTime(occurrence.end) : { date: todayIso(), time: '10:00' }
|
||||
// CR-03: occurrence.end for an all-day event is the EXCLUSIVE DTEND; the form's
|
||||
// end-date input is the INCLUSIVE last day. Convert when pre-filling so a re-edit
|
||||
// does not re-advance the span (buildVeventString rolls forward again on write).
|
||||
const initEndDate =
|
||||
occurrence?.allDay ? exclusiveEndToInclusiveDate(initEnd.date) : initEnd.date
|
||||
|
||||
const [title, setTitle] = useState(occurrence?.title ?? '')
|
||||
const [allDay, setAllDay] = useState(occurrence?.allDay ?? false)
|
||||
const [startDate, setStartDate] = useState(initStart.date)
|
||||
const [startTime, setStartTime] = useState(initStart.time)
|
||||
const [endDate, setEndDate] = useState(initEnd.date)
|
||||
const [endDate, setEndDate] = useState(initEndDate)
|
||||
const [endTime, setEndTime] = useState(initEnd.time)
|
||||
const [recurrence, setRecurrence] = useState<RecurrencePreset>('none')
|
||||
const [location, setLocation] = useState(occurrence?.location ?? '')
|
||||
@@ -179,11 +205,15 @@ export function EventForm() {
|
||||
if (eventFormOpen) {
|
||||
const startParsed = occurrence ? parseDateTime(occurrence.start) : { date: todayIso(), time: '09:00' }
|
||||
const endParsed = occurrence ? parseDateTime(occurrence.end) : { date: todayIso(), time: '10:00' }
|
||||
// CR-03: see exclusiveEndToInclusiveDate — pre-fill the inclusive last day for
|
||||
// all-day events so re-saving an edit does not grow the span by a day each time.
|
||||
const endDateValue =
|
||||
occurrence?.allDay ? exclusiveEndToInclusiveDate(endParsed.date) : endParsed.date
|
||||
setTitle(occurrence?.title ?? '')
|
||||
setAllDay(occurrence?.allDay ?? false)
|
||||
setStartDate(startParsed.date)
|
||||
setStartTime(startParsed.time)
|
||||
setEndDate(endParsed.date)
|
||||
setEndDate(endDateValue)
|
||||
setEndTime(endParsed.time)
|
||||
// WR-03 recurrence: derive from occurrence if present; default 'none' only when
|
||||
// genuinely absent. Note: occurrence.recurrence is not in CalendarOccurrence type
|
||||
|
||||
Reference in New Issue
Block a user