diff --git a/synctools/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarGenerator.kt b/synctools/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarGenerator.kt index 4ab142ae33..49d43c844c 100644 --- a/synctools/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarGenerator.kt +++ b/synctools/src/main/kotlin/at/bitfire/synctools/icalendar/ICalendarGenerator.kt @@ -5,6 +5,7 @@ package at.bitfire.synctools.icalendar import androidx.annotation.VisibleForTesting +import at.bitfire.synctools.util.AndroidTimeUtils.toInstant import at.bitfire.synctools.util.Utils.trimToNull import net.fortuna.ical4j.data.CalendarOutputter import net.fortuna.ical4j.model.Calendar @@ -13,7 +14,6 @@ import net.fortuna.ical4j.model.ComponentList import net.fortuna.ical4j.model.Parameter import net.fortuna.ical4j.model.PropertyContainer import net.fortuna.ical4j.model.PropertyList -import net.fortuna.ical4j.model.TemporalAdapter import net.fortuna.ical4j.model.TimeZoneRegistryFactory import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.component.VTimeZone @@ -66,7 +66,10 @@ class ICalendarGenerator { ical += exception exception.dtStart()?.date?.let { start -> - if (earliestStart == null || TemporalAdapter.isBefore(start, earliestStart)) + // Convert both Temporals to Instants for comparison because they may be in different time zones. If conversion fails, UnsupportedTemporalTypeException is thrown. + val startInstant = start.toInstant() + val earliestInstant = start.toInstant() + if (startInstant < earliestInstant) earliestStart = start } usedTimezoneIds += timeZonesOf(exception) diff --git a/synctools/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarGeneratorTest.kt b/synctools/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarGeneratorTest.kt index 96d9ba294b..7673098a53 100644 --- a/synctools/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarGeneratorTest.kt +++ b/synctools/src/test/kotlin/at/bitfire/synctools/icalendar/ICalendarGeneratorTest.kt @@ -246,4 +246,27 @@ class ICalendarGeneratorTest { assertTrue(result.isEmpty()) } + @Test + fun `Write event with UTC Instant exception DTSTART does not throw`() { + val iCal = StringWriter() + writer.write(AssociatedEvents( + main = VEvent(propertyListOf( + Uid("UTCTEST"), + DtStart(ZonedDateTime.of(LocalDateTime.parse("2025-08-22T05:30:00"), tzBerlin)), + DtStamp("20250822T000000Z"), + RRule("FREQ=DAILY;COUNT=3") + )), + exceptions = listOf( + VEvent(propertyListOf( + Uid("UTCTEST"), + RecurrenceId(Instant.parse("2025-08-22T03:30:00Z")), + DtStart(Instant.parse("2025-08-22T03:30:00Z")), + DtStamp("20250822T000000Z") + )) + ), + prodId = userAgent + ), iCal) + assertTrue(iCal.toString().contains("BEGIN:VCALENDAR")) + } + } \ No newline at end of file