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.
0.2.1 - 2026-05-25
Added
- Comprehensive
@moduledocand@docdocumentation across the entire public API following idiomatic ExDoc conventions (## Parameters,## Returns,## Examples). groups_for_modulesconfiguration inmix.exsto organize the HexDocs sidebar (Public API, Client, Operations, Internals, Types, Errors).
Fixed
- Default
User-Agentheader inCalDAVEx.Configis now derived from the application version via:application.get_key(:caldav_ex, :vsn)at runtime instead of a hard-coded"caldav_ex/0.1.0"string, so it tracks the released version. Consumers who set a custom User-Agent viaCalDAVEx.with_user_agent/2are unaffected.
0.2.0 - 2026-05-25
Added
:expand_recurrencesoption onCalDAVEx.list_events/3(andCalDAVEx.Event.list/3). Whentrue, the calendar-query REPORT emits a<C:expand start="..." end="..."/>element inside<C:calendar-data>(RFC 4791 §9.6.5), instructing the server to expand recurring events into individual occurrences within the:from/:towindow. Both bounds are required and must be%DateTime{}structs; otherwise an{:error, %CalDAVEx.Error{type: :invalid_argument}}is returned. Server-side support varies; verified against iCloud.CalDAVEx.Error.invalid_argument/1constructor and:invalid_argumentcase inCalDAVEx.Error.to_string/1.
Changed
- Behavior change:
CalDAVEx.list_events/3now returns one%CalDAVEx.Types.Event{}perVEVENTcomponent within a CalDAV resource, even when:expand_recurrencesisfalse. Previously, resources containing multipleVEVENTs (recurring masters withRECURRENCE-IDoverrides, or pre-expanded data) silently dropped every component after the first. Returned events from the same resource share the samehref,etag, andcalendar_data; the resource — not the occurrence — remains the unit of mutation. CalDAVEx.Error.to_string/1refactored to multi-clause function heads (no behavior change).
Fixed
- VEVENT block splitting now anchors
BEGIN:VEVENT/END:VEVENTto line boundaries, so property values legally containing the literal substringEND:VEVENT(e.g. inside aDESCRIPTION) cannot terminate a block prematurely. - TZID DTSTART/DTEND parsing is now scoped per
VEVENTso a TZID value from one event cannot leak into another in a multi-event resource. CalDAVEx.list_events/3now validates:fromand:totypes unconditionally and returns{:error, %CalDAVEx.Error{type: :invalid_argument}}for non-DateTimevalues, instead of crashing withFunctionClauseErrordeep in the query-formatting path.
0.1.4 - 2026-05-21
Fixed
- Fixed iCalendar DTSTART/DTEND parsing to handle TZID parameters (e.g.,
DTSTART;TZID=America/Los_Angeles:20260120T160000) - Events with timezone-aware datetime properties now correctly parse and convert to UTC
- Added proper handling for DST transitions:
- Fall-back (ambiguous times): chooses first occurrence
- Spring-forward (gap times): chooses time after the gap
- Events from Apple Calendar and other iCalendar clients with TZID parameters now parse correctly
- CRITICAL: Fixed timezone database dependency - library now works without consumer config
- All datetime operations use explicit
Tz.TimeZoneDatabaseparameter - Applies to TZID parsing, UTC conversion, and CalDAV time-range formatting
- Previously required consumers to configure
:elixir, :time_zone_databasein their app - Library now works out-of-the-box when added as a dependency
- All datetime operations use explicit
- Fixed RFC5545 compliance: TZID parameter now correctly parsed regardless of position or case
- Handles multiple parameters in any order (e.g.,
DTSTART;VALUE=DATE-TIME;TZID=...) - Case-insensitive property name matching per RFC5545 specification
- Supports quoted TZID parameter values (e.g.,
TZID="America/New_York")
- Handles multiple parameters in any order (e.g.,
Changed
- Improved test coverage with comprehensive test cases for TZID parsing, including:
- Timezone conversion for various timezones
- DST transition handling (fall-back ambiguous and spring-forward gap times)
- Error handling for invalid timezones and malformed datetimes
- Backward compatibility with UTC and DATE formats
- RFC5545 compliance (multiple parameters, quoted values, case-insensitivity)
- Performance optimization: precompiled TZID extraction regexes at module compile-time
- Eliminates repeated regex compilation overhead when processing multiple events
- Zero runtime cost for regex compilation
- Stricter datetime validation: regex now enforces exact iCalendar DATE-TIME format (YYYYMMDDTHHmmss)
- Rejects malformed datetime values early (e.g., extra digits, missing separators)
- Prevents partial matches on invalid input
- RFC5545 line unfolding: properly handles continuation lines in iCalendar data
- Unfolds lines that begin with space or tab per RFC5545 section 3.1
- Ensures TZID extraction works with folded properties and XML-indented content
- Improved error handling: replaced bang functions with explicit error handling
- Uses
DateTime.shift_zone/3instead ofshift_zone!/2with blanket rescue - Explicit handling of
{:ok, dt}and{:error, reason}for better debugging - Separated timezone resolution logic for clarity
- Uses
0.1.0 - 2026-05-19
Added
- Initial release
- CalDAV discovery (current-user-principal and calendar-home-set)
- Calendar listing with display name, description, and ctag
- Event listing with time-range filtering
- Single event retrieval by URL
- Robust XML parsing using Saxy
- iCalendar parsing via ical library
- Support for both timed and all-day events
- Basic and no-auth authentication methods
- Comprehensive test suite with Bypass HTTP mocking
Features
CalDAVEx.discover/1- Discover principal and calendar home setCalDAVEx.list_calendars/2- List all calendarsCalDAVEx.list_events/3- List events with optional time filteringCalDAVEx.get_event/2- Retrieve a single event by URLCalDAVEx.new_config/2- Create client configurationCalDAVEx.new_client/1- Create CalDAV clientCalDAVEx.basic_auth/2- Basic authenticationCalDAVEx.no_auth/0- No authentication