Skip to content

Commit 420a554

Browse files
authored
Split time builder into start and end time builders (#102)
* [WIP] Split TimeFieldsBuilder into StartTimeBuilder and EndTimeBuilder; make operation more clear * [WIP] Continue with EndTimeBuilder; drop TimeFieldsBuilder * [WIP] Naming changes * Finish EndTimeBuilder * Add DurationBuilder and update EndTimeBuilder for recurring events - Add DurationBuilder to handle duration for recurring events - Update EndTimeBuilder to skip DTEND for recurring events - Add unit tests for both builders - Fix Duration.toRfc5545Duration() for zero seconds * Refactor DurationBuilder to remove debug print and use Duration.ZERO - Remove the `System.err.println` statement that prints `startDate` and `endDate`. - Replace `java.time.Duration.ofSeconds(0)` with `java.time.Duration.ZERO` * Add / refactor tests
1 parent e49fdb6 commit 420a554

File tree

24 files changed

+886
-668
lines changed

24 files changed

+886
-668
lines changed

lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/calendar/LegacyAndroidEventBuilder2Test.kt

Lines changed: 0 additions & 487 deletions
This file was deleted.

lib/src/androidTest/kotlin/at/bitfire/synctools/storage/calendar/AndroidCalendarProviderBehaviorTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ class AndroidCalendarProviderBehaviorTest {
5656
}
5757

5858

59+
@Test
60+
fun testInsertEventWithDurationZeroSeconds() {
61+
// To make sure that it's not a problem to insert a recurring event with a duration of zero seconds.
62+
val values = contentValuesOf(
63+
Events.CALENDAR_ID to calendar.id,
64+
Events.DTSTART to 1759403653000, // Thu Oct 02 2025 11:14:13 GMT+0000
65+
Events.DURATION to "PT0S",
66+
Events.TITLE to "Event with useless RRULE",
67+
Events.RRULE to "FREQ=DAILY;UNTIL=20251002T000000Z"
68+
)
69+
val id = calendar.addEvent(Entity(values))
70+
71+
val event2 = calendar.getEventRow(id)
72+
assertContentValuesEqual(values, event2!!, onlyFieldsInExpected = true)
73+
}
74+
5975
@Test
6076
fun testInsertEventWithRRuleUntilBeforeDtStart() {
6177
// To make sure that's not a problem to insert an (invalid/useless) RRULE with UNTIL before the event's DTSTART.

lib/src/main/kotlin/at/bitfire/ical4android/Event.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package at.bitfire.ical4android
88

9+
import at.bitfire.synctools.exception.InvalidICalendarException
910
import at.bitfire.synctools.icalendar.Css3Color
1011
import net.fortuna.ical4j.model.Parameter
1112
import net.fortuna.ical4j.model.Property
@@ -91,4 +92,7 @@ data class Event(
9192
return email
9293
}
9394

95+
fun requireDtStart(): DtStart =
96+
dtStart ?: throw InvalidICalendarException("Missing DTSTART in VEVENT")
97+
9498
}

lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package at.bitfire.ical4android
88

99
import at.bitfire.ical4android.ICalendar.Companion.CALENDAR_NAME
1010
import at.bitfire.synctools.BuildConfig
11-
import at.bitfire.synctools.exception.InvalidRemoteResourceException
11+
import at.bitfire.synctools.exception.InvalidICalendarException
1212
import at.bitfire.synctools.icalendar.ICalendarParser
1313
import at.bitfire.synctools.icalendar.validation.ICalPreprocessor
1414
import net.fortuna.ical4j.data.CalendarBuilder
@@ -89,7 +89,7 @@ open class ICalendar {
8989
*
9090
* @return parsed iCalendar resource
9191
*
92-
* @throws InvalidRemoteResourceException when the iCalendar can't be parsed
92+
* @throws InvalidICalendarException when the iCalendar can't be parsed
9393
*/
9494
@Deprecated("Use ICalendarParser directly")
9595
fun fromReader(

lib/src/main/kotlin/at/bitfire/ical4android/JtxICalObject.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import android.os.ParcelFileDescriptor
1414
import android.util.Base64
1515
import androidx.core.content.contentValuesOf
1616
import at.bitfire.ical4android.ICalendar.Companion.withUserAgents
17-
import at.bitfire.synctools.exception.InvalidRemoteResourceException
17+
import at.bitfire.synctools.exception.InvalidICalendarException
1818
import at.bitfire.synctools.icalendar.Css3Color
1919
import at.bitfire.synctools.storage.BatchOperation
2020
import at.bitfire.synctools.storage.JtxBatchOperation
@@ -285,7 +285,7 @@ open class JtxICalObject(
285285
*
286286
* @return array of filled [JtxICalObject] data objects (may have size 0)
287287
*
288-
* @throws InvalidRemoteResourceException when the iCalendar can't be parsed
288+
* @throws InvalidICalendarException when the iCalendar can't be parsed
289289
* @throws IOException on I/O errors
290290
*/
291291
fun fromReader(

lib/src/main/kotlin/at/bitfire/ical4android/Task.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package at.bitfire.ical4android
88

99
import androidx.annotation.IntRange
1010
import at.bitfire.ical4android.util.DateUtils
11-
import at.bitfire.synctools.exception.InvalidRemoteResourceException
11+
import at.bitfire.synctools.exception.InvalidICalendarException
1212
import at.bitfire.synctools.icalendar.Css3Color
1313
import net.fortuna.ical4j.data.CalendarOutputter
1414
import net.fortuna.ical4j.model.Calendar
@@ -107,7 +107,7 @@ data class Task(
107107
*
108108
* @return array of filled [Task] data objects (may have size 0)
109109
*
110-
* @throws InvalidRemoteResourceException when the iCalendar can't be parsed
110+
* @throws InvalidICalendarException when the iCalendar can't be parsed
111111
* @throws IOException on I/O errors
112112
*/
113113
fun tasksFromReader(reader: Reader): List<Task> {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object AndroidTimeUtils {
4242
/**
4343
* Timezone ID to store for all-day events, according to CalendarContract.Events SDK documentation.
4444
*/
45-
val TZID_ALLDAY = "UTC"
45+
val TZID_UTC = "UTC"
4646

4747
private const val RECURRENCE_LIST_TZID_SEPARATOR = ';'
4848
private const val RECURRENCE_LIST_VALUE_SEPARATOR = ","
@@ -136,7 +136,7 @@ object AndroidTimeUtils {
136136
}
137137
} else
138138
// DATE
139-
TZID_ALLDAY
139+
TZID_UTC
140140

141141

142142
// recurrence sets

lib/src/main/kotlin/at/bitfire/ical4android/util/TimeApiExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ object TimeApiExtensions {
152152
var secs = seconds
153153

154154
if (secs == 0L)
155-
return "P0S"
155+
return "PT0S"
156156

157157
var weeks = secs / SECONDS_PER_WEEK
158158
secs -= weeks * SECONDS_PER_WEEK

lib/src/main/kotlin/at/bitfire/synctools/exception/InvalidRemoteResourceException.kt renamed to lib/src/main/kotlin/at/bitfire/synctools/exception/InvalidICalendarException.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
package at.bitfire.synctools.exception
88

99
/**
10-
* Represents an invalid remote resource (for instance, a calendar object resource).
10+
* Represents an invalid iCalendar resource.
1111
*/
12-
class InvalidRemoteResourceException: InvalidResourceException {
12+
class InvalidICalendarException: InvalidResourceException {
1313

1414
constructor(message: String): super(message)
1515
constructor(message: String, ex: Throwable): super(message, ex)

lib/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarParser.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
package at.bitfire.synctools.icalendar
88

9-
import at.bitfire.synctools.exception.InvalidRemoteResourceException
9+
import at.bitfire.synctools.exception.InvalidICalendarException
1010
import at.bitfire.synctools.icalendar.validation.ICalPreprocessor
1111
import net.fortuna.ical4j.data.CalendarBuilder
1212
import net.fortuna.ical4j.data.CalendarParserFactory
@@ -39,7 +39,7 @@ class ICalendarParser(
3939
* @param reader where the iCalendar is read from
4040
* @param tzRegistry time zone registry where VTIMEZONE definitions of the iCalendar will be put
4141
*
42-
* @throws InvalidRemoteResourceException when the resource is can't be parsed
42+
* @throws InvalidICalendarException when the resource is can't be parsed
4343
*/
4444
fun parse(reader: Reader): Calendar {
4545
// preprocess stream to work around problems that prevent parsing and thus can't be fixed later
@@ -54,9 +54,9 @@ class ICalendarParser(
5454
/* tzRegistry = */ TimeZoneRegistryFactory.getInstance().createRegistry()
5555
).build(preprocessed)
5656
} catch(e: ParserException) {
57-
throw InvalidRemoteResourceException("Couldn't parse iCalendar", e)
57+
throw InvalidICalendarException("Couldn't parse iCalendar", e)
5858
} catch(e: IllegalArgumentException) {
59-
throw InvalidRemoteResourceException("iCalendar contains invalid value", e)
59+
throw InvalidICalendarException("iCalendar contains invalid value", e)
6060
}
6161

6262
// Pre-process calendar for increased compatibility (fixes some common errors)

0 commit comments

Comments
 (0)