feat: Support typed RDATE/EXDATE recurrence - #116
Open
sirambd wants to merge 9 commits into
Open
Conversation
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds typed RDATE/EXDATE recurrence support across CalDAV parsing, persistence, querying, mapping, expansion, and tests.
Changes:
- Parses and stores typed recurrence dates with timezone metadata.
- Expands RRULE/RDATE sets while applying EXDATE exclusions.
- Adds recurrence-aware database bounds, queries, schema, and tests.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
CalendarKmpDav/.../RustCaldavBridge.kt |
Maps UniFFI recurrence dates. |
CalendarKmpDav/.../RemoteIcalDateValue.kt |
Defines remote typed date values. |
CalendarKmpDav/.../RemoteDavEvent.kt |
Adds RDATE/EXDATE fields. |
CalendarKmpDav/rust/.../models.rs |
Adds Rust recurrence date models. |
CalendarKmpDav/rust/.../events.rs |
Parses RDATE/EXDATE properties. |
CalendarCore/.../RecurringEventExpansionTest.kt |
Tests inclusion and exclusion. |
CalendarCore/.../RemoteDavEventToEntityRecurrenceTest.kt |
Tests recurrence-date parsing. |
CalendarCore/.../RecurrenceRuleEntitySerializationTest.kt |
Tests typed-value serialization. |
CalendarCore/.../EventDaoTest.kt |
Tests RDATE range queries. |
CalendarCore/.../RecurringEventExpansion.kt |
Implements recurrence-set expansion. |
CalendarCore/.../RecurrenceRuleFailureReason.kt |
Adds unsupported-period reason. |
CalendarCore/.../IcalDateValue.kt |
Defines domain recurrence values. |
CalendarCore/.../EventTiming.kt |
Carries RDATE/EXDATE values. |
CalendarCore/.../EventRepository.kt |
Passes recurrence dates to expansion. |
CalendarCore/.../CalendarRepository.kt |
Resolves recurrence during sync. |
CalendarCore/.../RemoteDavEventToEntity.kt |
Maps and validates recurrence values. |
CalendarCore/.../RecurrenceRuleToBoundsEntity.kt |
Computes combined recurrence bounds. |
CalendarCore/.../EventTimingEntityToDomain.kt |
Maps recurrence dates to domain. |
CalendarCore/.../EventEntityToDomain.kt |
Propagates stored recurrence dates. |
CalendarCore/.../EventCalendarColorInRange.kt |
Extends lightweight projection. |
CalendarCore/.../EventEntity.kt |
Persists recurrence fields and flag. |
CalendarCore/.../DatabaseProvider.kt |
Bumps database version. |
CalendarCore/.../EventDao.kt |
Queries using recurrence-set bounds. |
CalendarCore/.../CalendarTypeConverters.kt |
Serializes typed date lists. |
CalendarCore/schemas/.../13.json |
Defines schema version 13. |
Suppressed comments (2)
CalendarCore/src/commonMain/kotlin/com/infomaniak/multiplatform_calendar/core/data/mapper/RecurrenceRuleToBoundsEntity.kt:168
- For floating events, an
RDATEcan precedeDTSTART, but this branch stores only the upper local bound.RECURRING_FLOATINGstill usesevent.dtStartas its lower bound, so a query around that earlier RDATE filters out the master before expansion. Persist/query an earliest local occurrence bound (or conservatively omit the floating lower-bound check).
base.copy(
firstOccurrenceInstantMs = null,
lastOccurrenceEndLocalDateTime = maxOf(
base.lastOccurrenceEndLocalDateTime ?: timing.dtEndEffective,
localEnds.maxOrNull() ?: timing.dtEndEffective,
),
CalendarCore/src/commonMain/kotlin/com/infomaniak/multiplatform_calendar/core/data/mapper/RecurrenceRuleToBoundsEntity.kt:183
- For an all-day RDATE-only series,
lowBoundInstantMs()has already subtracted 14 hours whiledurationMshas added 14 hours, so adding them cancels the required upper padding for the includedDTSTART. IfDTSTARTis the latest member, range queries in negative-offset zones can drop its tail. Compute this end from the unpaddeddtStartInstantMs.
val dtStartMs = timing.lowBoundInstantMs()
val lastBound = maxNotNull(dtStartMs?.let { it + durationMs }, anchoredEnds.maxOrNull())
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| val rDates: List<IcalDateValue> = emptyList(), | ||
| val exDates: List<IcalDateValue> = emptyList(), | ||
| ) { | ||
| val hasRecurrence: Boolean get() = rule != null || rDates.isNotEmpty() |
Comment on lines
+131
to
+134
| dateValue.startInstantMs(timing)?.let { startMs -> | ||
| firstAnchoredStartInstantMs = minNotNull(firstAnchoredStartInstantMs, startMs) | ||
| anchoredEnds += startMs + durationMs | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



This pull request extends the event recurrence support in the calendar core by introducing explicit handling and storage for RDATE and EXDATE fields, which represent additional and excluded recurrence dates. It updates the data model, database schema, and related mapping logic to support these fields, and refactors how recurrence is detected and queried, moving from relying solely on
rruleto a more generalhasRecurrenceflag. The changes also improve how recurrence bounds are calculated, ensuring correct handling of events with only RDATEs or a mix of RRULE and RDATE.Recurrence Model and Database Changes:
rDatesandexDatesfields (lists ofIcalDateValue) and ahasRecurrenceboolean toEventEntity, and updated all related projections and database queries to use these fields instead of relying solely onrrule. [1] [2] [3] [4] [5] [6]CalendarTypeConvertersto serialize/deserialize lists ofIcalDateValuefor database storage.Recurrence Logic and Mapping Improvements:
toRecurrenceBoundsEntityand related helper methods. This ensures correct calculation of first/last occurrences and series bounds. [1] [2]rDatesandexDatesto the domain model, ensuring the recurrence logic in the application layer receives all relevant data. [1] [2]Other Notable Updates:
EventDaoto usehasRecurrenceinstead ofrrule IS NOT NULL, making recurrence detection more robust. [1] [2] [3]IcalDateValuethroughout the codebase to support the new fields and logic. [1] [2] [3] [4] [5]These changes make the recurrence system more flexible and standards-compliant, allowing for more accurate event representation and querying, especially for complex recurrence scenarios.