Skip to content

[Bug]: Moved occurrence (RECURRENCE-ID outside the master's range) is missing from time-range queries / Calendar app #65979

Open

Description

@torvalstrom

Bug description

CalDavBackend::getDenormalizedData() computes firstoccurence/lastoccurence from the master component only. An overridden instance (RECURRENCE-ID) that has been moved outside the master's range is ignored. Because calendarQuery() pre-filters on these columns, a time-range REPORT never returns the moved instance. The Calendar app does not show it. Clients that sync the whole collection (DAVx5, Thunderbird) do show it.

RFC 4791 section 9.9 requires overridden instances to be part of the time-range evaluation. Sabre's EventIterator handles this correctly.

This looks like a regression from #48131, which replaced the EventIterator over all VEVENTs. #64315 covers objects without a master, but not this case.

Steps to reproduce

  1. PUT this object. It is what Exchange and Thunderbird store when one occurrence of a meeting is rescheduled two weeks later:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//test//EN
BEGIN:VEVENT
UID:moved-1
DTSTAMP:20261025T000000Z
DTSTART:20261013T100000Z
RDATE:20261013T100000Z
END:VEVENT
BEGIN:VEVENT
UID:moved-1
DTSTAMP:20261025T000000Z
RECURRENCE-ID:20261013T100000Z
DTSTART:20261028T100000Z
DTEND:20261028T112000Z
SUMMARY:Moved meeting
END:VEVENT
END:VCALENDAR
  1. Open 28 October 2026 in the Calendar app, or run a calendar-query with a time-range on that day.

Expected behavior

The meeting shows on 28 October.

Actual behavior

Nothing is returned. The stored range is firstoccurence = lastoccurence = 2026-10-13 10:00Z. The same happens with an RRULE series whose last occurrence is moved past UNTIL or COUNT.

Possible fix

Widen the master's range with every override of the same component type. The columns are only a pre-filter, so widening is safe:

$data = $extract($baseComponent);
foreach ($vObject->getComponents() as $component) {
	if ($component === $baseComponent || $component->name !== $baseComponent->name || !isset($component->{'RECURRENCE-ID'})) {
		continue;
	}
	$exceptionData = $extract($component);
	if ($exceptionData['firstOccurence'] > 0) {
		$data['firstOccurence'] = min($data['firstOccurence'], $exceptionData['firstOccurence']);
	}
	$data['lastOccurence'] = max($data['lastOccurence'], $exceptionData['lastOccurence']);
}
return $derived + $data;

Existing objects need their columns recomputed after the fix.

Nextcloud Server version

34.0.5 (the code on master is the same)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions