[fix] resolves #53 account for all day busy events on caldav availability calculations #57
No files matched your search
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@when/worker': patch
|
||||
---
|
||||
|
||||
better support for caldav all day busy events when calculating availability
|
||||
@@ -168,3 +168,58 @@ END:VEVENT
|
||||
END:VCALENDAR`;
|
||||
expect(parseBusyEvents(cancelled)).toEqual([]);
|
||||
});
|
||||
|
||||
test('parses all-day event with explicit DTEND', () => {
|
||||
const alldayWithEnd = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//test//EN
|
||||
BEGIN:VEVENT
|
||||
UID:allday-end@test
|
||||
DTSTAMP:20260101T000000Z
|
||||
DTSTART;VALUE=DATE:20260415
|
||||
DTEND;VALUE=DATE:20260416
|
||||
SUMMARY:All day with DTEND
|
||||
END:VEVENT
|
||||
END:VCALENDAR`;
|
||||
const busy = parseBusyEvents(alldayWithEnd);
|
||||
expect(busy).toHaveLength(1);
|
||||
expect(busy[0].uid).toBe('allday-end@test');
|
||||
expect(busy[0].start.toString()).toBe('2026-04-15T00:00:00Z');
|
||||
expect(busy[0].end.toString()).toBe('2026-04-16T00:00:00Z');
|
||||
});
|
||||
|
||||
test('parses single-day all-day event without DTEND (RFC 5545 default 1 day duration)', () => {
|
||||
const alldayNoEnd = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//test//EN
|
||||
BEGIN:VEVENT
|
||||
UID:allday-noend@test
|
||||
DTSTAMP:20260101T000000Z
|
||||
DTSTART;VALUE=DATE:20260415
|
||||
SUMMARY:All day single day
|
||||
END:VEVENT
|
||||
END:VCALENDAR`;
|
||||
const busy = parseBusyEvents(alldayNoEnd);
|
||||
expect(busy).toHaveLength(1);
|
||||
expect(busy[0].uid).toBe('allday-noend@test');
|
||||
expect(busy[0].start.toString()).toBe('2026-04-15T00:00:00Z');
|
||||
expect(busy[0].end.toString()).toBe('2026-04-16T00:00:00Z');
|
||||
});
|
||||
|
||||
test('parses timed event without DTEND or DURATION (0 duration)', () => {
|
||||
const timedNoEnd = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//test//EN
|
||||
BEGIN:VEVENT
|
||||
UID:timed-noend@test
|
||||
DTSTAMP:20260101T000000Z
|
||||
DTSTART:20260415T140000Z
|
||||
SUMMARY:Zero duration
|
||||
END:VEVENT
|
||||
END:VCALENDAR`;
|
||||
const busy = parseBusyEvents(timedNoEnd);
|
||||
expect(busy).toHaveLength(1);
|
||||
expect(busy[0].uid).toBe('timed-noend@test');
|
||||
expect(busy[0].start.toString()).toBe('2026-04-15T14:00:00Z');
|
||||
expect(busy[0].end.toString()).toBe('2026-04-15T14:00:00Z');
|
||||
});
|
||||
@@ -30,7 +30,7 @@ function toBusy(event: IcsEvent): BusyEvent | null {
|
||||
|
||||
try {
|
||||
const startMs = event.start.date.getTime();
|
||||
const endMs = getEventEnd(event).getTime();
|
||||
const endMs = eventEnd(event).getTime();
|
||||
return {
|
||||
uid: event.uid,
|
||||
start: Temporal.Instant.fromEpochMilliseconds(startMs),
|
||||
@@ -48,3 +48,20 @@ function toBusy(event: IcsEvent): BusyEvent | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function eventEnd(event: IcsEvent): Date {
|
||||
if (event.end?.date) {
|
||||
return event.end.date;
|
||||
}
|
||||
if (event.duration) {
|
||||
return getEventEnd(event);
|
||||
}
|
||||
if (event.start.type === 'DATE') {
|
||||
// All-day event without DTEND/DURATION lasts 1 full day per RFC 5545 §3.6.1
|
||||
const d = new Date(event.start.date.getTime());
|
||||
d.setUTCDate(d.getUTCDate() + 1);
|
||||
return d;
|
||||
}
|
||||
// DATE-TIME event without DTEND/DURATION has 0 duration per RFC 5545 §3.6.1
|
||||
return event.start.date;
|
||||
}
|
||||
Reference in new issue
Block a user