Skip to content

Commit 0c6602c

Browse files
authored
Minor refactoring of RecurrenceFieldsHandler (#158)
- Add test case for all-day recurring events - Simplify `alignUntil` method in `RecurrenceFieldsHandler` - Update `AndroidTimeUtils` for better handling of RDATE/EXDATE lists
1 parent fd08d5f commit 0c6602c

File tree

3 files changed

+48
-21
lines changed

3 files changed

+48
-21
lines changed

lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,7 @@ class RecurrenceFieldsHandler(
152152
* @see at.bitfire.synctools.mapping.calendar.builder.EndTimeBuilder.alignWithDtStart
153153
*/
154154
fun alignUntil(recur: Recur, startDate: Date): Recur {
155-
val until: Date? = recur.until
156-
if (until == null)
157-
return recur
155+
val until: Date = recur.until ?: return recur
158156

159157
if (until is DateTime) {
160158
// UNTIL is DATE-TIME

lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import net.fortuna.ical4j.model.TimeZoneRegistryFactory
2121
import net.fortuna.ical4j.model.parameter.Value
2222
import net.fortuna.ical4j.model.property.DateListProperty
2323
import net.fortuna.ical4j.model.property.DateProperty
24-
import net.fortuna.ical4j.model.property.ExDate
2524
import net.fortuna.ical4j.model.property.RDate
2625
import net.fortuna.ical4j.util.TimeZones
2726
import java.text.SimpleDateFormat
@@ -256,7 +255,7 @@ object AndroidTimeUtils {
256255

257256
val limiter = dbStr.indexOf(RECURRENCE_LIST_TZID_SEPARATOR)
258257
if (limiter != -1) { // TZID given
259-
val tzId = dbStr.substring(0, limiter)
258+
val tzId = dbStr.take(limiter)
260259
timeZone = tzRegistry.getTimeZone(tzId)
261260
if (TimeZones.isUtc(timeZone))
262261
timeZone = null
@@ -296,7 +295,7 @@ object AndroidTimeUtils {
296295
/**
297296
* Concatenates, if necessary, multiple RDATE/EXDATE lists and converts them to
298297
* a formatted string which OpenTasks can process.
299-
* OpenTasks expect a list of RFC 5545 DATE ("yyyymmdd") or DATE-TIME ("yyyymmdd[Z]") values,
298+
* OpenTasks expect a list of RFC 5545 DATE (`yyyymmdd`) or DATE-TIME (`yyyymmdd[Z]`) values,
300299
* where the time zone is stored in a separate field.
301300
*
302301
* @param dates one more more lists of RDATE or EXDATE
@@ -308,21 +307,20 @@ object AndroidTimeUtils {
308307
val allDay = tz == null
309308
val strDates = LinkedList<String>()
310309
for (dateListProp in dates) {
311-
if (dateListProp is RDate)
312-
if (dateListProp.periods.isNotEmpty())
313-
logger.warning("RDATE PERIOD not supported, ignoring")
314-
else if (dateListProp is ExDate)
315-
if (dateListProp.periods.isNotEmpty())
316-
logger.warning("EXDATE PERIOD not supported, ignoring")
310+
if (dateListProp is RDate && dateListProp.periods.isNotEmpty())
311+
logger.warning("RDATE PERIOD not supported, ignoring")
317312

318313
for (date in dateListProp.dates) {
319314
val dateToUse =
320-
if (date is DateTime && allDay) // VALUE=DATE-TIME, but allDay=1
315+
when (date) {
316+
is DateTime if allDay -> // VALUE=DATE-TIME, but allDay=1
321317
Date(date)
322-
else if (date !is DateTime && !allDay) // VALUE=DATE, but allDay=0
318+
319+
!is DateTime if !allDay -> // VALUE=DATE, but allDay=0
323320
DateTime(date.toString(), tz)
324-
else
325-
date
321+
322+
else -> date
323+
}
326324
if (dateToUse is DateTime && !dateToUse.isUtc)
327325
dateToUse.timeZone = tz!!
328326
strDates += dateToUse.toString()

lib/src/test/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldHandlerTest.kt

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import androidx.core.content.contentValuesOf
1313
import io.mockk.mockk
1414
import junit.framework.TestCase.assertEquals
1515
import net.fortuna.ical4j.model.Date
16+
import net.fortuna.ical4j.model.DateList
1617
import net.fortuna.ical4j.model.DateTime
1718
import net.fortuna.ical4j.model.ParameterList
1819
import net.fortuna.ical4j.model.Property
1920
import net.fortuna.ical4j.model.Recur
2021
import net.fortuna.ical4j.model.TimeZoneRegistryFactory
2122
import net.fortuna.ical4j.model.component.VEvent
23+
import net.fortuna.ical4j.model.parameter.Value
2224
import net.fortuna.ical4j.model.property.ExDate
2325
import net.fortuna.ical4j.model.property.ExRule
2426
import net.fortuna.ical4j.model.property.RDate
@@ -75,10 +77,10 @@ class RecurrenceFieldHandlerTest {
7577
fun `Recurring main event`() {
7678
val result = VEvent()
7779
val entity = Entity(contentValuesOf(
78-
Events.DTSTART to System.currentTimeMillis(),
79-
Events.RRULE to "FREQ=DAILY;COUNT=10",
80-
Events.RDATE to "20251010T010203Z",
81-
Events.EXRULE to "FREQ=WEEKLY;COUNT=1",
80+
Events.DTSTART to 1759403653000, // Thu Oct 02 2025 11:14:13 GMT+0000
81+
Events.RRULE to "FREQ=DAILY;COUNT=10", // Oct 02 ... Oct 12
82+
Events.RDATE to "20251002T111413Z,20251015T010203Z", // RDATE at event start as required by Android plus Oct 15
83+
Events.EXRULE to "FREQ=WEEKLY;COUNT=1", // meaningless EXRULE/EXDATE
8284
Events.EXDATE to "20260201T010203Z"
8385
))
8486
handler.process(entity, entity, result)
@@ -87,7 +89,7 @@ class RecurrenceFieldHandlerTest {
8789
result.getProperties<RRule>(Property.RRULE)
8890
)
8991
assertEquals(
90-
listOf(RDate(ParameterList(), "20251010T010203Z")),
92+
listOf(RDate(ParameterList(), "20251015T010203Z")),
9193
result.getProperties<RDate>(Property.RDATE)
9294
)
9395
assertEquals(
@@ -100,6 +102,35 @@ class RecurrenceFieldHandlerTest {
100102
)
101103
}
102104

105+
@Test
106+
fun `Recurring main event (all-day)`() {
107+
val result = VEvent()
108+
val entity = Entity(contentValuesOf(
109+
Events.ALL_DAY to 1,
110+
Events.DTSTART to 1759363200000, // Thu Oct 02 2025 00:00:00 GMT+0000
111+
Events.RRULE to "FREQ=DAILY;COUNT=10", // Oct 02 ... Oct 12
112+
Events.RDATE to "20251002,20251015", // RDATE at event start as required by Android plus Oct 15
113+
Events.EXRULE to "FREQ=WEEKLY;COUNT=1", // meaningless EXRULE/EXDATE
114+
Events.EXDATE to "20260201T010203Z"
115+
))
116+
handler.process(entity, entity, result)
117+
assertEquals(
118+
listOf(RRule("FREQ=DAILY;COUNT=10")),
119+
result.getProperties<RRule>(Property.RRULE)
120+
)
121+
assertEquals(
122+
listOf(RDate(DateList(Value.DATE).apply { add(Date("20251015")) })),
123+
result.getProperties<RDate>(Property.RDATE)
124+
)
125+
assertEquals(
126+
"FREQ=WEEKLY;COUNT=1",
127+
result.getProperties<ExRule>(Property.EXRULE).joinToString { it.value }
128+
)
129+
assertEquals(
130+
"20260201",
131+
result.getProperties<ExDate>(Property.EXDATE).joinToString { it.value }
132+
)
133+
}
103134

104135
@Test
105136
fun `RRULE with UNTIL before DTSTART`() {

0 commit comments

Comments
 (0)