diff --git a/radicale/item/filter.py b/radicale/item/filter.py index 5a430c22..bb3a0c41 100644 --- a/radicale/item/filter.py +++ b/radicale/item/filter.py @@ -121,6 +121,7 @@ def comp_match(item: "item.Item", filter_: ET.Element, level: int = 0) -> bool: logger.warning("Filtering %s is not supported", name) return True # Point #3 and #4 of rfc4791-9.7.1 + trigger = None if level == 0: components = [item.vobject_item] elif level == 1: @@ -128,15 +129,18 @@ def comp_match(item: "item.Item", filter_: ET.Element, level: int = 0) -> bool: elif level == 2: components = list(getattr(item.vobject_item, "%s_list" % tag.lower())) for comp in components: - if not hasattr(comp, name.lower()): + subcomp = getattr(comp, name.lower(), None) + if not subcomp: return False + if hasattr(subcomp, "trigger"): + trigger = subcomp.trigger.value for child in filter_: if child.tag == xmlutils.make_clark("C:prop-filter"): if not any(prop_match(comp, child, "C") for comp in components): return False elif child.tag == xmlutils.make_clark("C:time-range"): - if not time_range_match(item.vobject_item, filter_[0], tag): + if not time_range_match(item.vobject_item, filter_[0], tag, trigger): return False elif child.tag == xmlutils.make_clark("C:comp-filter"): if not comp_match(item, child, level=level + 1): @@ -166,7 +170,7 @@ def prop_match(vobject_item: vobject.base.Component, # Point #3 and #4 of rfc4791-9.7.2 for child in filter_: if ns == "C" and child.tag == xmlutils.make_clark("C:time-range"): - if not time_range_match(vobject_item, child, name): + if not time_range_match(vobject_item, child, name, None): return False elif child.tag == xmlutils.make_clark("%s:text-match" % ns): if not text_match(vobject_item, child, name, ns): @@ -180,9 +184,10 @@ def prop_match(vobject_item: vobject.base.Component, def time_range_match(vobject_item: vobject.base.Component, - filter_: ET.Element, child_name: str) -> bool: + filter_: ET.Element, child_name: str, trigger: datetime | None) -> bool: """Check whether the component/property ``child_name`` of ``vobject_item`` matches the time-range ``filter_``.""" + # supporting since 3.5.4 now optional trigger (either absolute or relative offset) if not filter_.get("start") and not filter_.get("end"): return False @@ -193,6 +198,25 @@ def time_range_match(vobject_item: vobject.base.Component, def range_fn(range_start: datetime, range_end: datetime, is_recurrence: bool) -> bool: nonlocal matched + if trigger: + # if trigger is given, only check range_start + if isinstance(trigger, timedelta): + # trigger is a offset, apply to range_start + if start < range_start + trigger and range_start + trigger < end: + matched = True + return True + else: + return False + elif isinstance(trigger, datetime): + # trigger is absolute, use instead of range_start + if start < trigger and trigger < end: + matched = True + return True + else: + return False + else: + logger.warning("item/filter/time_range_match/range_fn: unsupported data format of provided trigger=%r", trigger) + return True if start < range_end and range_start < end: matched = True return True