Skip to content

Commit 5f5a9fe

Browse files
committed
Support case when the only RRULE is filtered, so that the EXDATE should be dropped too + test
1 parent 96ea620 commit 5f5a9fe

File tree

2 files changed

+36
-22
lines changed

2 files changed

+36
-22
lines changed

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/processor/RecurrenceFieldsProcessor.kt

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import net.fortuna.ical4j.model.property.ExDate
1616
import net.fortuna.ical4j.model.property.ExRule
1717
import net.fortuna.ical4j.model.property.RDate
1818
import net.fortuna.ical4j.model.property.RRule
19+
import java.util.LinkedList
1920
import java.util.logging.Level
2021
import java.util.logging.Logger
2122

@@ -28,19 +29,13 @@ class RecurrenceFieldsProcessor: AndroidEventFieldProcessor {
2829

2930
override fun process(from: Entity, main: Entity, to: Event) {
3031
val values = from.entityValues
31-
val rRuleField = values.getAsString(Events.RRULE)
32-
val rDateField = values.getAsString(Events.RDATE)
3332

34-
// generate recurrence properties only for recurring main events
35-
val recurring = rRuleField != null || rDateField != null
36-
if (from !== main || !recurring)
37-
return
38-
39-
val allDay = (values.getAsInteger(Events.ALL_DAY) ?: 0) != 0
4033
val tsStart = values.getAsLong(Events.DTSTART) ?: throw InvalidLocalResourceException("Found event without DTSTART")
34+
val allDay = (values.getAsInteger(Events.ALL_DAY) ?: 0) != 0
4135

42-
// RRULE
43-
if (rRuleField != null)
36+
// process RRULE field
37+
val rRules = LinkedList<RRule>()
38+
values.getAsString(Events.RRULE)?.let { rRuleField ->
4439
try {
4540
for (rule in rRuleField.split(AndroidTimeUtils.RECURRENCE_RULE_SEPARATOR)) {
4641
val rule = RRule(rule)
@@ -52,25 +47,31 @@ class RecurrenceFieldsProcessor: AndroidEventFieldProcessor {
5247
continue
5348
}
5449

55-
to.rRules += rule
50+
rRules += rule
5651
}
5752
} catch (e: Exception) {
5853
logger.log(Level.WARNING, "Couldn't parse RRULE field, ignoring", e)
5954
}
55+
}
6056

61-
// RDATE
62-
if (rDateField != null)
57+
// process RDATE field
58+
val rDates = LinkedList<RDate>()
59+
values.getAsString(Events.RDATE)?.let { rDateField ->
6360
try {
64-
val rDate = AndroidTimeUtils.androidStringToRecurrenceSet(rDateField, tzRegistry, allDay, tsStart) { RDate(it) }
65-
to.rDates += rDate
61+
val rDate = AndroidTimeUtils.androidStringToRecurrenceSet(rDateField, tzRegistry, allDay, tsStart) {
62+
RDate(it)
63+
}
64+
rDates += rDate
6665
} catch (e: Exception) {
6766
logger.log(Level.WARNING, "Couldn't parse RDATE field, ignoring", e)
6867
}
68+
}
6969

7070
// EXRULE
71-
values.getAsString(Events.EXRULE)?.let { rulesStr ->
71+
val exRules = LinkedList<ExRule>()
72+
values.getAsString(Events.EXRULE)?.let { exRuleField ->
7273
try {
73-
for (rule in rulesStr.split(AndroidTimeUtils.RECURRENCE_RULE_SEPARATOR)) {
74+
for (rule in exRuleField.split(AndroidTimeUtils.RECURRENCE_RULE_SEPARATOR)) {
7475
val rule = ExRule(null, rule)
7576

7677
// skip if UNTIL is before event's DTSTART
@@ -80,22 +81,32 @@ class RecurrenceFieldsProcessor: AndroidEventFieldProcessor {
8081
continue
8182
}
8283

83-
to.exRules += rule
84+
exRules += rule
8485
}
8586
} catch (e: Exception) {
8687
logger.log(Level.WARNING, "Couldn't parse recurrence rules, ignoring", e)
8788
}
8889
}
8990

9091
// EXDATE
91-
values.getAsString(Events.EXDATE)?.let { datesStr ->
92+
val exDates = LinkedList<ExDate>()
93+
values.getAsString(Events.EXDATE)?.let { exDateField ->
9294
try {
93-
val exDate = AndroidTimeUtils.androidStringToRecurrenceSet(datesStr, tzRegistry, allDay) { ExDate(it) }
94-
to.exDates += exDate
95+
val exDate = AndroidTimeUtils.androidStringToRecurrenceSet(exDateField, tzRegistry, allDay) { ExDate(it) }
96+
exDates += exDate
9597
} catch (e: Exception) {
9698
logger.log(Level.WARNING, "Couldn't parse recurrence rules, ignoring", e)
9799
}
98100
}
101+
102+
// generate recurrence properties only for recurring main events
103+
val recurring = rRules.isNotEmpty() || rDates.isNotEmpty()
104+
if (from === main && recurring) {
105+
to.rRules += rRules
106+
to.rDates += rDates
107+
to.exRules += exRules
108+
to.exDates += exDates
109+
}
99110
}
100111

101112
}

lib/src/test/kotlin/at/bitfire/synctools/mapping/calendar/processor/RecurrenceFieldProcessorTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,13 @@ class RecurrenceFieldProcessorTest {
8282
val result = Event()
8383
val entity = Entity(contentValuesOf(
8484
Events.DTSTART to 1759403653000, // Thu Oct 02 2025 11:14:13 GMT+0000
85-
Events.RRULE to "FREQ=DAILY;UNTIL=20251002T111300Z"
85+
Events.RRULE to "FREQ=DAILY;UNTIL=20251002T111300Z",
86+
Events.EXDATE to "1759403653000" // should be removed because the only RRULE is invalid and discarded,
87+
// so the whole event isn't recurring anymore
8688
))
8789
processor.process(entity, entity, result)
8890
assertTrue(result.rRules.isEmpty())
91+
assertTrue(result.exDates.isEmpty())
8992
}
9093

9194
@Test

0 commit comments

Comments
 (0)