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

1"""Shared calendar date and keyword filtering.""" 

2 

3from datetime import date, datetime, time, timedelta 

4from typing import Optional 

5 

6 

7def get_event_date_range(event: dict) -> tuple[date, date]: 

8 """ 

9 Return the first and last day (both inclusive) an event covers. 

10 

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) 

24 

25 

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