Fix time range filtering with overwritten recurrences

This commit is contained in:
Unrud 2017-08-03 19:47:00 +02:00
parent 7678da7926
commit 748519e94d

View File

@ -246,12 +246,12 @@ def _time_range_match(vobject_item, filter_, child_name):
matched = False matched = False
def range_fn(range_start, range_end): def range_fn(range_start, range_end, recurrence):
nonlocal matched nonlocal matched
if start < range_end and range_start < end: if start < range_end and range_start < end:
matched = True matched = True
return True return True
if end < range_start: if end < range_start and not recurrence:
return True return True
return False return False
@ -267,8 +267,8 @@ def _visit_time_ranges(vobject_item, child_name, range_fn, infinity_fn):
`vobject_item`` with visitors ``range_fn`` and ``infinity_fn``. `vobject_item`` with visitors ``range_fn`` and ``infinity_fn``.
``range_fn`` gets called for every time_range with ``start`` and ``end`` ``range_fn`` gets called for every time_range with ``start`` and ``end``
datetimes as arguments. If the function returns True, the operation is datetimes and ``recurrence`` as arguments. If the function returns True,
cancelled. the operation is cancelled.
``infinity_fn`` gets called when an infiite recurrence rule is detected ``infinity_fn`` gets called when an infiite recurrence rule is detected
with ``start`` datetime as argument. If the function returns True, the with ``start`` datetime as argument. If the function returns True, the
@ -295,9 +295,23 @@ def _visit_time_ranges(vobject_item, child_name, range_fn, infinity_fn):
return (), True return (), True
return child.getrruleset(addRDate=True), False return child.getrruleset(addRDate=True), False
def get_children(components):
main = None
for comp in components:
if hasattr(comp, "recurrence_id") and comp.recurrence_id.value:
if comp.rruleset:
# Prevent possible infinite loop
raise ValueError("Overwritten recurrence with RRULESET")
yield comp, True
else:
if main is not None:
raise ValueError("Multiple main components")
main = comp
yield main, False
# Comments give the lines in the tables of the specification # Comments give the lines in the tables of the specification
if child_name == "VEVENT": if child_name == "VEVENT":
for child in vobject_item.vevent_list: for child, recurrence in get_children(vobject_item.vevent_list):
# TODO: check if there's a timezone # TODO: check if there's a timezone
dtstart = child.dtstart.value dtstart = child.dtstart.value
@ -325,30 +339,30 @@ def _visit_time_ranges(vobject_item, child_name, range_fn, infinity_fn):
if dtend is not None: if dtend is not None:
# Line 1 # Line 1
dtend = dtstart + timedelta(seconds=original_duration) dtend = dtstart + timedelta(seconds=original_duration)
if range_fn(dtstart, dtend): if range_fn(dtstart, dtend, recurrence):
return return
elif duration is not None: elif duration is not None:
if original_duration is None: if original_duration is None:
original_duration = duration.seconds original_duration = duration.seconds
if duration.seconds > 0: if duration.seconds > 0:
# Line 2 # Line 2
if range_fn(dtstart, dtstart + duration): if range_fn(dtstart, dtstart + duration, recurrence):
return return
else: else:
# Line 3 # Line 3
if range_fn(dtstart, dtstart + SECOND): if range_fn(dtstart, dtstart + SECOND, recurrence):
return return
elif dtstart_is_datetime: elif dtstart_is_datetime:
# Line 4 # Line 4
if range_fn(dtstart, dtstart + SECOND): if range_fn(dtstart, dtstart + SECOND, recurrence):
return return
else: else:
# Line 5 # Line 5
if range_fn(dtstart, dtstart + DAY): if range_fn(dtstart, dtstart + DAY, recurrence):
return return
elif child_name == "VTODO": elif child_name == "VTODO":
for child in vobject_item.vtodo_list: for child, recurrence in get_children(vobject_item.vtodo_list):
dtstart = getattr(child, "dtstart", None) dtstart = getattr(child, "dtstart", None)
duration = getattr(child, "duration", None) duration = getattr(child, "duration", None)
due = getattr(child, "due", None) due = getattr(child, "due", None)
@ -386,7 +400,7 @@ def _visit_time_ranges(vobject_item, child_name, range_fn, infinity_fn):
reference_dates = (created,) reference_dates = (created,)
else: else:
# Line 8 # Line 8
if range_fn(DATETIME_MIN, DATETIME_MAX): if range_fn(DATETIME_MIN, DATETIME_MAX, recurrence):
return return
reference_dates = () reference_dates = ()
@ -396,50 +410,58 @@ def _visit_time_ranges(vobject_item, child_name, range_fn, infinity_fn):
if dtstart is not None and duration is not None: if dtstart is not None and duration is not None:
# Line 1 # Line 1
if range_fn(reference_date, if range_fn(reference_date,
reference_date + duration + SECOND): reference_date + duration + SECOND,
recurrence):
return return
if range_fn(reference_date + duration - SECOND, if range_fn(reference_date + duration - SECOND,
reference_date + duration + SECOND): reference_date + duration + SECOND,
recurrence):
return return
elif dtstart is not None and due is not None: elif dtstart is not None and due is not None:
# Line 2 # Line 2
due = reference_date + timedelta(seconds=original_duration) due = reference_date + timedelta(seconds=original_duration)
if (range_fn(reference_date, due) or if (range_fn(reference_date, due, recurrence) or
range_fn(reference_date, range_fn(reference_date,
reference_date + SECOND) or reference_date + SECOND, recurrence) or
range_fn(due - SECOND, due) or range_fn(due - SECOND, due, recurrence) or
range_fn(due - SECOND, reference_date + SECOND)): range_fn(due - SECOND, reference_date + SECOND,
recurrence)):
return return
elif dtstart is not None: elif dtstart is not None:
if range_fn(reference_date, reference_date + SECOND): if range_fn(reference_date, reference_date + SECOND,
recurrence):
return return
elif due is not None: elif due is not None:
# Line 4 # Line 4
if range_fn(reference_date - SECOND, reference_date): if range_fn(reference_date - SECOND, reference_date,
recurrence):
return return
elif completed is not None and created is not None: elif completed is not None and created is not None:
# Line 5 # Line 5
completed = reference_date + timedelta( completed = reference_date + timedelta(
seconds=original_duration) seconds=original_duration)
if (range_fn(reference_date - SECOND, if (range_fn(reference_date - SECOND,
reference_date + SECOND) or reference_date + SECOND,
range_fn(completed - SECOND, completed + SECOND) or recurrence) or
range_fn(completed - SECOND, completed + SECOND,
recurrence) or
range_fn(reference_date - SECOND, range_fn(reference_date - SECOND,
reference_date + SECOND) or reference_date + SECOND, recurrence) or
range_fn(completed - SECOND, completed + SECOND)): range_fn(completed - SECOND, completed + SECOND,
recurrence)):
return return
elif completed is not None: elif completed is not None:
# Line 6 # Line 6
if range_fn(reference_date - SECOND, if range_fn(reference_date - SECOND,
reference_date + SECOND): reference_date + SECOND, recurrence):
return return
elif created is not None: elif created is not None:
# Line 7 # Line 7
if range_fn(reference_date, DATETIME_MAX): if range_fn(reference_date, DATETIME_MAX, recurrence):
return return
elif child_name == "VJOURNAL": elif child_name == "VJOURNAL":
for child in vobject_item.vjournal_list: for child, recurrence in get_children(vobject_item.vjournal_list):
dtstart = getattr(child, "dtstart", None) dtstart = getattr(child, "dtstart", None)
if dtstart is not None: if dtstart is not None:
@ -457,18 +479,18 @@ def _visit_time_ranges(vobject_item, child_name, range_fn, infinity_fn):
if dtstart_is_datetime: if dtstart_is_datetime:
# Line 1 # Line 1
if range_fn(dtstart, dtstart + SECOND): if range_fn(dtstart, dtstart + SECOND, recurrence):
return return
else: else:
# Line 2 # Line 2
if range_fn(dtstart, dtstart + DAY): if range_fn(dtstart, dtstart + DAY, recurrence):
return return
elif isinstance(child, date): elif isinstance(child, date):
if range_fn(child, child + DAY): if range_fn(child, child + DAY, False):
return return
elif isinstance(child, datetime): elif isinstance(child, datetime):
if range_fn(child, child + SECOND): if range_fn(child, child + SECOND, False):
return return
@ -587,7 +609,7 @@ def find_tag_and_time_range(vobject_item):
return (None, TIMESTAMP_MIN, TIMESTAMP_MAX) return (None, TIMESTAMP_MIN, TIMESTAMP_MAX)
start = end = None start = end = None
def range_fn(range_start, range_end): def range_fn(range_start, range_end, recurrence):
nonlocal start, end nonlocal start, end
if start is None or range_start < start: if start is None or range_start < start:
start = range_start start = range_start