1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-08-10 18:40:53 +00:00

(#1812) Fixed an issue where non-recurring events were not included in

the response when requesting an expand report
This commit is contained in:
Georgiy 2025-07-21 20:58:58 +03:00
parent c553460365
commit 7cd918d036
4 changed files with 120 additions and 50 deletions

View file

@ -360,12 +360,12 @@ def _expand(
# Split the vevents included in the component into one that contains the
# recurrence information and others that contain a recurrence id to
# override instances.
vevent_recurrence, vevents_overridden = _split_overridden_vevents(vevent_component)
base_vevent, vevents_overridden = _split_overridden_vevents(vevent_component)
dt_format = '%Y%m%dT%H%M%SZ'
all_day_event = False
if type(vevent_recurrence.dtstart.value) is datetime.date:
if type(base_vevent.dtstart.value) is datetime.date:
# If an event comes to us with a dtstart specified as a date
# then in the response we return the date, not datetime
dt_format = '%Y%m%d'
@ -382,11 +382,11 @@ def _expand(
_strip_single_event(vevent, dt_format)
duration = None
if hasattr(vevent_recurrence, "dtend"):
duration = vevent_recurrence.dtend.value - vevent_recurrence.dtstart.value
elif hasattr(vevent_recurrence, "duration"):
if hasattr(base_vevent, "dtend"):
duration = base_vevent.dtend.value - base_vevent.dtstart.value
elif hasattr(base_vevent, "duration"):
try:
duration = vevent_recurrence.duration.value
duration = base_vevent.duration.value
if duration.total_seconds() <= 0:
logger.warning("Invalid DURATION: %s", duration)
duration = None
@ -396,8 +396,8 @@ def _expand(
# Generate EXDATE to remove from expansion range
exdates_set: set[datetime.datetime] = set()
if hasattr(vevent_recurrence, 'exdate'):
exdates = vevent_recurrence.exdate.value
if hasattr(base_vevent, 'exdate'):
exdates = base_vevent.exdate.value
if not isinstance(exdates, list):
exdates = [exdates]
@ -409,9 +409,14 @@ def _expand(
logger.debug("EXDATE values: %s", exdates_set)
events_for_filtering = vevents_overridden
rruleset = None
if hasattr(vevent_recurrence, 'rrule'):
rruleset = vevent_recurrence.getrruleset()
if hasattr(base_vevent, 'rrule'):
rruleset = base_vevent.getrruleset()
else:
# if event does not have rrule, only include base event
events_for_filtering = [base_vevent]
filtered_vevents = []
if rruleset:
@ -436,7 +441,7 @@ def _expand(
.format(max_occurrence))
_strip_component(vevent_component)
_strip_single_event(vevent_recurrence, dt_format)
_strip_single_event(base_vevent, dt_format)
i_overridden = 0
@ -463,7 +468,7 @@ def _expand(
if not vevent:
# Create new instance from recurrence
vevent = copy.deepcopy(vevent_recurrence)
vevent = copy.deepcopy(base_vevent)
# For all day events, the system timezone may influence the
# results, so use recurrence_dt
@ -488,45 +493,45 @@ def _expand(
filtered_vevents.append(vevent)
# Filter overridden and recurrence base events
if time_range_start is not None and time_range_end is not None:
for vevent in vevents_overridden:
dtstart = vevent.dtstart.value
# Filter overridden and non-recurring events
if time_range_start is not None and time_range_end is not None:
for vevent in events_for_filtering:
dtstart = vevent.dtstart.value
# Handle string values for DTSTART/DTEND
if isinstance(dtstart, str):
try:
dtstart = datetime.datetime.strptime(dtstart, dt_format)
if all_day_event:
dtstart = dtstart.date()
except ValueError as e:
logger.warning("Invalid DTSTART format: %s, error: %s", dtstart, e)
continue
dtend = dtstart + duration if duration else dtstart
logger.debug(
"Filtering VEVENT with DTSTART: %s (type: %s), DTEND: %s (type: %s)",
dtstart, type(dtstart), dtend, type(dtend))
# Convert to datetime for comparison
if all_day_event and isinstance(dtstart, datetime.date) and not isinstance(dtstart, datetime.datetime):
dtstart = datetime.datetime.fromordinal(dtstart.toordinal()).replace(tzinfo=None)
dtend = datetime.datetime.fromordinal(dtend.toordinal()).replace(tzinfo=None)
elif not all_day_event and isinstance(dtstart, datetime.datetime) \
and isinstance(dtend, datetime.datetime):
dtstart = dtstart.replace(tzinfo=datetime.timezone.utc)
dtend = dtend.replace(tzinfo=datetime.timezone.utc)
else:
logger.warning("Unexpected DTSTART/DTEND type: dtstart=%s, dtend=%s", type(dtstart), type(dtend))
# Handle string values for DTSTART/DTEND
if isinstance(dtstart, str):
try:
dtstart = datetime.datetime.strptime(dtstart, dt_format)
if all_day_event:
dtstart = dtstart.date()
except ValueError as e:
logger.warning("Invalid DTSTART format: %s, error: %s", dtstart, e)
continue
if dtstart < time_range_end and dtend > time_range_start:
if vevent not in filtered_vevents: # Avoid duplicates
logger.debug("VEVENT passed time-range filter: %s", dtstart)
filtered_vevents.append(vevent)
else:
logger.debug("VEVENT filtered out: %s", dtstart)
dtend = dtstart + duration if duration else dtstart
logger.debug(
"Filtering VEVENT with DTSTART: %s (type: %s), DTEND: %s (type: %s)",
dtstart, type(dtstart), dtend, type(dtend))
# Convert to datetime for comparison
if all_day_event and isinstance(dtstart, datetime.date) and not isinstance(dtstart, datetime.datetime):
dtstart = datetime.datetime.fromordinal(dtstart.toordinal()).replace(tzinfo=None)
dtend = datetime.datetime.fromordinal(dtend.toordinal()).replace(tzinfo=None)
elif not all_day_event and isinstance(dtstart, datetime.datetime) \
and isinstance(dtend, datetime.datetime):
dtstart = dtstart.replace(tzinfo=datetime.timezone.utc)
dtend = dtend.replace(tzinfo=datetime.timezone.utc)
else:
logger.warning("Unexpected DTSTART/DTEND type: dtstart=%s, dtend=%s", type(dtstart), type(dtend))
continue
if dtstart < time_range_end and dtend > time_range_start:
if vevent not in filtered_vevents: # Avoid duplicates
logger.debug("VEVENT passed time-range filter: %s", dtstart)
filtered_vevents.append(vevent)
else:
logger.debug("VEVENT filtered out: %s", dtstart)
# Rebuild component