All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
Recurrence sets with master in non-first position were rejected: RFC 5545 does not mandate that the master VEVENT (carrying
RRULE) appears first in a VCALENDAR. Some CalDAV servers (notablycalendar.mail.ru) emit override VEVENTs (withRECURRENCE-ID) before the master. The previous validation assumedcomponents[0]was always the master and raisedValueErrorfor any other ordering, which causedcaldav._filter_search_resultsto silently drop all expanded recurring instances. The master is now located by content (hasRRULE, noRECURRENCE-ID) regardless of position, and the list is reordered so it sits at index 0 for downstream expansion. Also reduced duplicated error-check code in the same function. -
check_component()mutatedselfon every call: The method resolvedNonefields (include_completed,todo,event,journal) and normalisedstart/end/alarm_start/alarm_enddate objects to datetimes by writing back toself. This made aSearcherinstance stateful: callingcheck_component()twice could produce different results, and reusing aSearcheracross multiple search operations (e.g. in the python-caldav library) could silently change behaviour after the first call. All these values are now computed as local variables insidecheck_component()and threaded to the internal filter methods via new keyword parameters (_start,_end,_alarm_start,_alarm_end,_include_completed), keepingselfimmutable throughout. (Triggered by python-caldav/caldav#650) -
VTODO with only
COMPLETED(noDTSTART/DUE/CREATED) crashed on time-range search: A time-range filter on aVTODOthat hasCOMPLETEDbut noDTSTART,DUE, orCREATEDwould crash. The RFC 4791 §9.9 matrix was also re-read and the implementation now correctly handles all VTODO timestamp combinations: a task with onlyCREATEDis treated as open-ended (matches any search interval ending afterCREATED); a task with onlyCOMPLETEDis treated as a point event; and a task with bothCREATEDandCOMPLETEDuses them as the time bounds when noDTSTART/DUEis present.
Replace Poetry with Hatch. Version 1.0.4 never got as far as pypi due to a silly lock file problem.
-
undeffilter for CATEGORIES was broken with icalendar >= 6.x:icalendar6.x provides a default emptyvCategoryobject for theCATEGORIESproperty even when it is not explicitly present in the iCalendar data, making"categories" in componentalways returnTrue. This caused searches likeno_category=True(oradd_property_filter("categories", ..., operator="undef")) to incorrectly filter out all events instead of only those that actually have categories. Fixed by checking the computed category set (empty → not defined) rather than the unreliableintest. -
undeffilter for DTEND was broken for recurring all-day events:recurring_ical_eventsadds a computedDTEND = DTSTART + 1 dayto individual occurrences of all-day recurring events, even when the master event does not have an explicitDTEND. This caused searches for events withoutDTEND(e.g.no_dtend=True) to incorrectly filter out recurring all-day events. Fixed by skippingundefchecks when filtering expanded recurrence occurrences whose master element has already passed theundefcheck.
Support for RFC7986-style CATEGORIES split over multiple lines. #6
- The sort method did not handle lists very well. In particular, RFC 7986 section 5.6 defines that the categories field may be split over multiple lines in the icalendar object, this could cause an exception to be raised. Now lists are sorted and converted to a comma-separated string prior to sorting.
- The filter method would also run into problems, it has also been fixed
-
filter()method: Implemented filtering for lists of Calendar/Component objects- Accepts
list[Calendar | Component]as input - Optional
split_expandedparameter for splitting expanded recurrences into separate Calendar objects - Returns new list without modifying input (immutability guarantee)
- Preserves Calendar properties and VTIMEZONE components
- Applies all configured property filters and component type filters
- Handles recurrence expansion when
self.expandis True - Deep copies all returned objects for safety
- Accepts
-
sort()method: Implemented sorting for lists of components/calendars- Accepts
list[Component | CalendarObjectResource]as input - Returns new sorted list without modifying input
- Uses configured sort keys (via
add_sort_key()) - Supports multiple sort keys with proper precedence
- Supports case-sensitive/case-insensitive sorting
- Supports reversed sorting
- Handles Calendar, Component, and CalendarObjectResource objects
- Accepts
-
filter_calendar()method: Implemented filtering of subcomponents within a Calendar object- Filters all subcomponents (events/todos/journals) within a single Calendar
- Returns new Calendar with only matching subcomponents, or None if no matches
- Preserves VTIMEZONE components
- Preserves all Calendar-level properties
- Handles recurrence expansion when
self.expandis True - Deep copies to ensure immutability
-
sort_calendar()method: Implemented sorting of subcomponents within a Calendar object- Sorts all subcomponents (events/todos/journals) within a single Calendar
- Preserves VTIMEZONE components in their original position
- Returns new Calendar without modifying input
- Preserves all Calendar-level properties
- Deep copies to ensure immutability
-
Comprehensive test suites:
- 15 tests for
filter()method covering filtering, expansion, splitting, and edge cases - 5 tests for
filter_calendar()method covering basic filtering, no matches, timezones, expansion, and mixed types - 12 tests for
sort()andsort_calendar()methods covering various sorting scenarios
- 15 tests for
- Core functionality now complete: The library's main methods (
filter(),filter_calendar(),sort(),sort_calendar()) are now fully implemented and no longer stubs - README updated: Added usage examples for
filter()andsort()methods, updated status section to reflect completion of core functionality
- This is the first stable 1.0 release
- The API is considered stable and will follow semantic versioning going forward
- Breaking changes in future versions will require a major version bump (2.0, 3.0, etc.)
- BREAKING: Collation system redesigned: Simplified collation architecture to use three collation strategies (SIMPLE, UNICODE, LOCALE), each supporting a
case_sensitiveparameter- Removed
Collation.BINARY- useCollation.SIMPLEwithcase_sensitive=Trueinstead - Removed
Collation.CASE_INSENSITIVE- useCollation.SIMPLEwithcase_sensitive=Falseinstead - All collation strategies (SIMPLE, UNICODE, LOCALE) now accept
case_sensitiveboolean parameter - UNICODE and LOCALE collations can now be case-sensitive or case-insensitive
- PyICU collator strength is configured based on
case_sensitiveparameter (TERTIARY for case-sensitive, SECONDARY for case-insensitive)
- Removed
- Replace
collation=Collation.BINARYwithcollation=Collation.SIMPLE, case_sensitive=True - Replace
collation=Collation.CASE_INSENSITIVEwithcollation=Collation.SIMPLE, case_sensitive=False - UNICODE/LOCALE collations now require explicit
case_sensitiveparameter if you want case-sensitive behavior
- UNICODE and LOCALE collation support for "==" operator: The == operator now properly supports UNICODE and LOCALE collations using ICU sort key comparison. Previously only BINARY and CASE_INSENSITIVE collations were supported for exact match operations.
- PyICU-based tests now pass in CI environments where PyICU is installed
Some wheel files etc that shouldn't be in the repository has accidentally been added. Either I've done some silly mistake while sleeping, or perhaps it was a mistake giving the AI access to my computer.
Categories are a mess. I've tried to make a predictable behaviour by making a distinction between categories (handling the property filter value as a list that should match or be a subset of the event category set) and category (handling the property filter value as a string that should match towards at least one of the categories in the event).
- "category" (singular) property filter: New virtual property for substring matching within category names
"category"with "contains" operator does substring matching (e.g., "out" matches "outdoor")"category"with "==" operator matches if value exactly equals at least one category name- Commas in filter values are treated as literal characters, not split into multiple categories
- Complements existing
"categories"(plural) which does exact category name matching
- Category filtering now properly respects collation settings for case-insensitive matching
- Category "contains" operator now correctly checks for subset (exact category names) rather than substring matching
- Category "==" operator now properly supports case-insensitive exact matching
- Text properties with "==" operator now support case-insensitive comparison when
case_sensitive=False - Category preprocessing now correctly handles tuples with comma-separated strings
Text filtering can be done both case-sensitive and case-insensitive - we need to support both of them. And we also may need to support various collations. DISCLAIMER: While I could easily add case insensitive/sensitive filtering myself, as soon as collations was tossed into the equation I considered the AI could do it better and faster than me. So 0.2.0 is AI-generated code, but curated by a human.
- Collation support for text searches and sorting: Added comprehensive support for both case-sensitive and case-insensitive text comparisons
- New
case_sensitiveparameter inadd_property_filter()andadd_sort_key()methods for simple API - New
Collationenum for power users with support forBINARY,CASE_INSENSITIVE,UNICODE, andLOCALEcollation strategies - Optional PyICU integration for advanced Unicode and locale-aware collation (install with
pip install 'icalendar-searcher[collation]') - Collation support for all text properties (SUMMARY, LOCATION, DESCRIPTION, etc.)
- Collation support for CATEGORIES property with special handling
- Graceful fallback when PyICU is not installed
- New
- BREAKING: Default text search behavior is now case-sensitive (was case-insensitive)
- Category searches are now case-sensitive by default (was implicitly case-sensitive, now explicit)
- Improved type handling in property filters to correctly distinguish between text properties and other types