diff --git a/events/__init__.py b/events/__init__.py index 2387df1..9e2e13c 100644 --- a/events/__init__.py +++ b/events/__init__.py @@ -388,12 +388,31 @@ def match(event) -> bool: if (exact and not search_term == title) or (not exact and search_term.lower() not in title.lower()): return False - if 'dtend' in event: - if datetime.timestamp(rationalize_time(event['dtend'].dt)) < after: + # Special case for repeating events + if 'rrule' in event: + if not 'dtstart' in event: + logging.warning(f'Event {event} has rrule but not dtstart') + return False # Invalid event, ignore + + rule = rrulestr(event['rrule'].to_ical().decode(), dtstart=rationalize_time(event['dtstart'].dt)) + + begin = None + if 'dtstart' in event and before != inf: + begin = rule.after(rationalize_time(datetime.fromtimestamp(before)), inc=True) + + end = None + if 'dtend' in event and after != 0: + end = rule.after(rationalize_time(datetime.fromtimestamp(after)), inc=True) + else: + begin = None if not 'dtstart' in event else event['dtstart'].dt + end = None if not 'dtend' in event else event['dtend'].dt + + if end is not None: + if datetime.timestamp(rationalize_time(end)) < after: return False - if 'dtstart' in event: - if datetime.timestamp(rationalize_time(event['dtstart'].dt)) > before: + if begin is not None: + if datetime.timestamp(rationalize_time(begin)) > before: return False return True diff --git a/events/test_events.py b/events/test_events.py index 2caa67b..56f0420 100644 --- a/events/test_events.py +++ b/events/test_events.py @@ -126,6 +126,14 @@ event_14.add('attendee', 'MAILTO:foo14@bar.com') event_14['sequence'] = 11 +yearly_repeating_event = Event() +yearly_repeating_event.add('rrule', {'FREQ': 'YEARLY'}) +yearly_repeating_event.add('dtstart', datetime(2013, 10, 10, 10, 0, 0)) +yearly_repeating_event.add('dtend', datetime(2013, 10, 10, 10, 0, 0)) +yearly_repeating_event['summary'] = 'yearly_repeating_event' +yearly_repeating_event.add('created', datetime(2013, 10, 10, 10, 0, 0)) +yearly_repeating_event['uid'] = 'yearly_repeating_event' + save_event_override = None @@ -146,6 +154,7 @@ def __init__(self, read_only): 'event_12': event_12, 'event_13': event_13, 'event_14': event_14, + 'yearly_repeating_event': yearly_repeating_event, } def get_event_impl(self, name: str): @@ -343,6 +352,24 @@ def test_search_api_strip(client): order = [e['title'] for e in content] assert order == ['event_1'] +def test_search_rrule_exact_match(client): + after = datetime.timestamp(yearly_repeating_event['dtend'].dt) + before = datetime.timestamp(yearly_repeating_event['dtstart'].dt) + content = search_api(client, "yearly_repeating_event", before=before, after=after) + assert [e['title'] for e in content] == ['yearly_repeating_event'] + +def test_search_rrule_negative(client): + after = datetime.timestamp(yearly_repeating_event['dtend'].dt) + before = datetime.timestamp(yearly_repeating_event['dtstart'].dt - timedelta(hours=1)) + content = search_api(client, "yearly_repeating_event", before=before, after=after) + assert content == [] + +def test_search_rrule_occurence(client): + after = datetime.timestamp(yearly_repeating_event['dtend'].dt + timedelta(days=365)) + before = datetime.timestamp(yearly_repeating_event['dtstart'].dt + timedelta(days=365)) + content = search_api(client, "yearly_repeating_event", before=before, after=after) + assert [e['title'] for e in content] == ['yearly_repeating_event'] + def test_view_event_admin(client): response = client.get('/1/event_1.ics', headers={'X-Admin': 'true'})