Coverage for src/data_api/utils/calendars.py: 100%
26 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-13 19:28 +0000
1"""Shared calendar date and keyword filtering."""
3from datetime import date, datetime, time, timedelta
4from typing import Optional
7def get_event_date_range(event: dict) -> tuple[date, date]:
8 """
9 Return the first and last day (both inclusive) an event covers.
11 All-day events use an exclusive end date (iCal convention). Timed events
12 use the local dates in the published timestamps.
13 """
14 first_day = date.fromisoformat(event["start"][:10])
15 if event["all_day"]:
16 last_day = date.fromisoformat(event["end"][:10]) - timedelta(days=1)
17 else:
18 end = datetime.fromisoformat(event["end"])
19 last_day = end.date()
20 # An event ending exactly at midnight does not cover the next day.
21 if end.time() == time(0) and last_day > first_day:
22 last_day -= timedelta(days=1)
23 return first_day, max(first_day, last_day)
26def filter_calendar_events(
27 events: list[dict],
28 start: Optional[date] = None,
29 end: Optional[date] = None,
30 keyword: Optional[str] = None,
31) -> list[dict]:
32 """Keep events that overlap [start, end] and contain keyword in title or description."""
33 keyword = keyword.strip().lower() if keyword else None
34 results = []
35 for event in events:
36 first_day, last_day = get_event_date_range(event)
37 if start and last_day < start:
38 continue
39 if end and first_day > end:
40 continue
41 if keyword:
42 text = f"{event['title']}\n{event.get('description') or ''}".lower()
43 if keyword not in text:
44 continue
45 results.append(event)
46 return results