From 45f4778fa8466de2c06a6d011b298bfb85cd2bb5 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 25 Mar 2026 16:01:16 +0100 Subject: [PATCH 1/4] Remove `Instant.toLocalDate()` from `DateUtils` Use the more generic `Temporal.toLocalDate()` from `TimeApiExtensions` instead. --- .../kotlin/at/bitfire/ical4android/util/DateUtils.kt | 10 ---------- .../synctools/mapping/tasks/DmfsTaskProcessor.kt | 2 +- .../at/bitfire/synctools/util/AndroidTimeUtils.kt | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt b/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt index 69d7f9cb..c29fbd10 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt @@ -16,7 +16,6 @@ import java.time.ZoneId import java.time.ZoneOffset import java.time.ZonedDateTime import java.time.temporal.ChronoField -import java.time.temporal.ChronoUnit import java.time.temporal.Temporal /** @@ -57,15 +56,6 @@ object DateUtils { fun isDateTime(date: Temporal?): Boolean = date != null && TemporalAdapter.isDateTimePrecision(date) - /** - * Converts the given [Instant] by truncating it to days, and converting into [LocalDate] by its - * epoch timestamp. - */ - fun Instant.toLocalDate(): LocalDate { - val epochSeconds = truncatedTo(ChronoUnit.DAYS).epochSecond - return LocalDate.ofEpochDay(epochSeconds / (24 * 60 * 60 /*seconds in a day*/)) - } - /** * Converts the given generic [Temporal] into milliseconds since epoch. * @param fallbackTimezone Any specific timezone to use as fallback if there's not enough diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt index 68af4eb8..68fef95b 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskProcessor.kt @@ -10,7 +10,7 @@ import android.content.ContentValues import at.bitfire.ical4android.DmfsTask.Companion.UNKNOWN_PROPERTY_DATA import at.bitfire.ical4android.Task import at.bitfire.ical4android.UnknownProperty -import at.bitfire.ical4android.util.DateUtils.toLocalDate +import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.icalendar.propertyListOf import at.bitfire.synctools.storage.tasks.DmfsTaskList import at.bitfire.synctools.util.AndroidTimeUtils diff --git a/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt index b6d34848..b2bfa10b 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt @@ -6,8 +6,8 @@ package at.bitfire.synctools.util -import at.bitfire.ical4android.util.DateUtils.toLocalDate import at.bitfire.ical4android.util.TimeApiExtensions +import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import net.fortuna.ical4j.model.CalendarDateFormat import net.fortuna.ical4j.model.DateList import net.fortuna.ical4j.model.TemporalAdapter From 1454be61e1f6cd90a21c0e28c22a35233eec8e47 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 25 Mar 2026 16:33:56 +0100 Subject: [PATCH 2/4] Remove `Temporal.toEpochMilli` from `DateUtils` Use `Temporal.toTimestamp()` from `AndroidTemporalMapper` instead. This function retains ical4j's old behavior when it comes to converting floating DATE and DATE-TIME values to a timestamp. --- .../mapping/tasks/DmfsTaskBuilderTest.kt | 6 ++-- .../at/bitfire/ical4android/util/DateUtils.kt | 34 ------------------- .../handler/RecurrenceFieldsHandler.kt | 6 ++-- .../mapping/tasks/DmfsTaskBuilder.kt | 6 ++-- 4 files changed, 9 insertions(+), 43 deletions(-) diff --git a/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt b/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt index c57e6d4c..c4a27e9c 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt @@ -18,7 +18,7 @@ import at.bitfire.ical4android.Task import at.bitfire.ical4android.TaskProvider import at.bitfire.ical4android.UnknownProperty import at.bitfire.ical4android.impl.TestTaskList -import at.bitfire.ical4android.util.DateUtils.toEpochMilli +import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.storage.tasks.DmfsTaskList import net.fortuna.ical4j.model.DateList import net.fortuna.ical4j.model.TimeZoneRegistryFactory @@ -440,7 +440,7 @@ class DmfsTaskBuilderTest ( LocalDateTime.of( LocalDate.of(2020, 7, 3), LocalTime.of(1, 2, 3) - ).toEpochMilli(), + ).toTimestamp(), result.getAsLong(TaskContract.Tasks.DTSTART) ) assertEquals( @@ -476,7 +476,7 @@ class DmfsTaskBuilderTest ( LocalDateTime.of( LocalDate.of(2020, 7, 3), LocalTime.of(1, 2, 3) - ).toEpochMilli(), + ).toTimestamp(), result.getAsLong(TaskContract.Tasks.DUE) ) assertEquals( diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt b/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt index c29fbd10..ec5a6d05 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/util/DateUtils.kt @@ -8,14 +8,6 @@ package at.bitfire.ical4android.util import net.fortuna.ical4j.model.TemporalAdapter import net.fortuna.ical4j.model.property.DateProperty -import java.time.Instant -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.OffsetDateTime -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.temporal.ChronoField import java.time.temporal.Temporal /** @@ -56,30 +48,4 @@ object DateUtils { fun isDateTime(date: Temporal?): Boolean = date != null && TemporalAdapter.isDateTimePrecision(date) - /** - * Converts the given generic [Temporal] into milliseconds since epoch. - * @param fallbackTimezone Any specific timezone to use as fallback if there's not enough - * information on the [Temporal] type (local types). Defaults to UTC. - * @throws IllegalArgumentException if the [Temporal] is from an unknown time, which also doesn't - * support [ChronoField.INSTANT_SECONDS] - */ - fun Temporal.toEpochMilli(fallbackTimezone: ZoneId? = null): Long { - // If the temporal supports instant seconds, we can compute epoch millis directly from them - if (isSupported(ChronoField.INSTANT_SECONDS)) { - val seconds = getLong(ChronoField.INSTANT_SECONDS) - val nanos = get(ChronoField.NANO_OF_SECOND) - // Convert seconds and nanos to millis - return (seconds * 1000) + (nanos / 1_000_000) - } - - return when (this) { - is Instant -> this.toEpochMilli() - is ZonedDateTime -> this.toInstant().toEpochMilli() - is OffsetDateTime -> this.toInstant().toEpochMilli() - is LocalDate -> this.atStartOfDay(fallbackTimezone ?: ZoneOffset.UTC).toInstant().toEpochMilli() - is LocalDateTime -> this.atZone(fallbackTimezone ?: ZoneOffset.UTC).toInstant().toEpochMilli() - else -> throw IllegalArgumentException("${this::class.java.simpleName} cannot be converted to epoch millis.") - } - } - } \ No newline at end of file diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt index 50f06a50..a8ce4406 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt @@ -9,9 +9,9 @@ package at.bitfire.synctools.mapping.calendar.handler import android.content.Entity import android.provider.CalendarContract.Events import at.bitfire.ical4android.util.DateUtils -import at.bitfire.ical4android.util.DateUtils.toEpochMilli import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.exception.InvalidLocalResourceException +import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.util.AndroidTimeUtils import net.fortuna.ical4j.model.Recur @@ -60,7 +60,7 @@ class RecurrenceFieldsHandler: AndroidEventFieldHandler { rule.recur = alignUntil(rule.recur, startDate) // skip if UNTIL is before event's DTSTART - val tsUntil = rule.recur.until?.toEpochMilli() + val tsUntil = rule.recur.until?.toTimestamp() if (tsUntil != null && tsUntil <= tsStart) { logger.warning("Ignoring $rule because UNTIL ($tsUntil) is not after DTSTART ($tsStart)") continue @@ -96,7 +96,7 @@ class RecurrenceFieldsHandler: AndroidEventFieldHandler { rule.recur = alignUntil(rule.recur, startDate) // skip if UNTIL is before event's DTSTART - val tsUntil = rule.recur.until?.toEpochMilli() + val tsUntil = rule.recur.until?.toTimestamp() if (tsUntil != null && tsUntil <= tsStart) { logger.warning("Ignoring $rule because UNTIL ($tsUntil) is not after DTSTART ($tsStart)") continue diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt index b680b4b2..25c39f64 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt @@ -12,8 +12,8 @@ import at.bitfire.ical4android.DmfsTask.Companion.UNKNOWN_PROPERTY_DATA import at.bitfire.ical4android.ICalendar import at.bitfire.ical4android.Task import at.bitfire.ical4android.UnknownProperty -import at.bitfire.ical4android.util.DateUtils.toEpochMilli import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate +import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.storage.BatchOperation.CpoBuilder import at.bitfire.synctools.storage.tasks.DmfsTaskList import at.bitfire.synctools.storage.tasks.TasksBatchOperation @@ -156,8 +156,8 @@ class DmfsTaskBuilder( .withValue(Tasks.CREATED, task.createdAt) .withValue(Tasks.LAST_MODIFIED, task.lastModified) - .withValue(Tasks.DTSTART, task.dtStart?.date?.toEpochMilli()) - .withValue(Tasks.DUE, task.due?.date?.toEpochMilli()) + .withValue(Tasks.DTSTART, task.dtStart?.date?.toTimestamp()) + .withValue(Tasks.DUE, task.due?.date?.toTimestamp()) .withValue(Tasks.DURATION, task.duration?.value) .withValue(Tasks.RDATE, From feb29d9ef5a280faef2168c3fe555ebe390e6495 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 25 Mar 2026 16:41:04 +0100 Subject: [PATCH 3/4] Move `AndroidTemporalMapper` to `util` package Move the class to a more generic package since it's not exclusively used by the calendar code. --- .../bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt | 2 +- lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt | 2 +- lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt | 2 +- .../synctools/mapping/calendar/AndroidEventHandler.kt | 2 +- .../mapping/calendar/builder/AndroidRecurrenceMapper.kt | 4 ++-- .../synctools/mapping/calendar/builder/DurationBuilder.kt | 2 +- .../synctools/mapping/calendar/builder/EndTimeBuilder.kt | 6 +++--- .../mapping/calendar/builder/OriginalInstanceTimeBuilder.kt | 4 ++-- .../synctools/mapping/calendar/builder/StartTimeBuilder.kt | 4 ++-- .../synctools/mapping/calendar/handler/DurationHandler.kt | 2 +- .../mapping/calendar/handler/RecurrenceFieldsHandler.kt | 4 ++-- .../at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt | 2 +- .../calendar/builder => util}/AndroidTemporalMapper.kt | 3 +-- .../calendar/builder => util}/AndroidTemporalMapperTest.kt | 6 +++--- 14 files changed, 22 insertions(+), 23 deletions(-) rename lib/src/main/kotlin/at/bitfire/synctools/{mapping/calendar/builder => util}/AndroidTemporalMapper.kt (96%) rename lib/src/test/kotlin/at/bitfire/synctools/{mapping/calendar/builder => util}/AndroidTemporalMapperTest.kt (95%) diff --git a/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt b/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt index c4a27e9c..a5e85ee7 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt @@ -18,7 +18,7 @@ import at.bitfire.ical4android.Task import at.bitfire.ical4android.TaskProvider import at.bitfire.ical4android.UnknownProperty import at.bitfire.ical4android.impl.TestTaskList -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.storage.tasks.DmfsTaskList import net.fortuna.ical4j.model.DateList import net.fortuna.ical4j.model.TimeZoneRegistryFactory diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt b/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt index ae69374a..62dc437c 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt @@ -11,7 +11,7 @@ import at.bitfire.synctools.exception.InvalidICalendarException import at.bitfire.synctools.icalendar.Css3Color import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.ICalendarParser -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import net.fortuna.ical4j.model.Component import net.fortuna.ical4j.model.TemporalAdapter import net.fortuna.ical4j.model.component.VToDo diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt b/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt index 2951de43..5f2215cc 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt @@ -11,7 +11,7 @@ import at.bitfire.ical4android.ICalendar.Companion.withUserAgents import at.bitfire.synctools.icalendar.Css3Color import at.bitfire.synctools.icalendar.VTimeZoneMinifier import at.bitfire.synctools.icalendar.plusAssign -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import net.fortuna.ical4j.data.CalendarOutputter import net.fortuna.ical4j.model.Calendar import net.fortuna.ical4j.model.Parameter diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt index b96e52aa..12d4bb48 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt @@ -13,7 +13,7 @@ import at.bitfire.synctools.icalendar.AssociatedEvents import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.plusAssign import at.bitfire.synctools.icalendar.recurrenceId -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.mapping.calendar.handler.AccessLevelHandler import at.bitfire.synctools.mapping.calendar.handler.AndroidEventFieldHandler import at.bitfire.synctools.mapping.calendar.handler.AttendeesHandler diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt index 8b8fa309..737aeaf9 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt @@ -7,8 +7,8 @@ package at.bitfire.synctools.mapping.calendar.builder import at.bitfire.ical4android.util.DateUtils -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import net.fortuna.ical4j.model.Property import java.time.Instant import java.time.LocalDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt index ffaabe7e..f1707920 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt @@ -16,7 +16,7 @@ import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.ical4android.util.TimeApiExtensions.toRfc5545Duration import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.RDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt index 4a9c8492..61c60515 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt @@ -13,9 +13,9 @@ import at.bitfire.ical4android.util.DateUtils import at.bitfire.ical4android.util.TimeApiExtensions.abs import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.androidTimezoneId -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTemporalMapper.androidTimezoneId +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.RDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt index 9743820b..f8b8be9b 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt @@ -12,8 +12,8 @@ import at.bitfire.ical4android.util.DateUtils import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.recurrenceId import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import net.fortuna.ical4j.model.component.VEvent import java.time.LocalDate import java.time.ZonedDateTime diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt index 15ea9822..de0eeab4 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt @@ -10,8 +10,8 @@ import android.content.Entity import android.provider.CalendarContract.Events import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.androidTimezoneId -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.androidTimezoneId +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import net.fortuna.ical4j.model.component.VEvent import java.time.temporal.Temporal diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt index 844f21ba..5c668e03 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt @@ -10,7 +10,7 @@ import android.content.Entity import android.provider.CalendarContract.Events import at.bitfire.ical4android.util.TimeApiExtensions.abs import at.bitfire.synctools.icalendar.plusAssign -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.util.AndroidTimeUtils import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.DtEnd diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt index a8ce4406..1ac0d098 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt @@ -11,8 +11,8 @@ import android.provider.CalendarContract.Events import at.bitfire.ical4android.util.DateUtils import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.exception.InvalidLocalResourceException -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.util.AndroidTimeUtils import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.component.VEvent diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt index 25c39f64..225ba5e4 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt @@ -13,7 +13,7 @@ import at.bitfire.ical4android.ICalendar import at.bitfire.ical4android.Task import at.bitfire.ical4android.UnknownProperty import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.storage.BatchOperation.CpoBuilder import at.bitfire.synctools.storage.tasks.DmfsTaskList import at.bitfire.synctools.storage.tasks.TasksBatchOperation diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidTemporalMapper.kt b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt similarity index 96% rename from lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidTemporalMapper.kt rename to lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt index 3ed70ba2..f8d8831c 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidTemporalMapper.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt @@ -4,9 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.synctools.mapping.calendar.builder +package at.bitfire.synctools.util -import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import net.fortuna.ical4j.model.TemporalAdapter import net.fortuna.ical4j.util.TimeZones import java.time.Instant diff --git a/lib/src/test/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidTemporalMapperTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt similarity index 95% rename from lib/src/test/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidTemporalMapperTest.kt rename to lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt index 29f1c488..2088a41f 100644 --- a/lib/src/test/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidTemporalMapperTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt @@ -4,12 +4,12 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -package at.bitfire.synctools.mapping.calendar.builder +package at.bitfire.synctools.util import at.bitfire.DefaultTimezoneRule import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.androidTimezoneId -import at.bitfire.synctools.mapping.calendar.builder.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTemporalMapper.androidTimezoneId +import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import net.fortuna.ical4j.data.CalendarBuilder import net.fortuna.ical4j.model.Component import net.fortuna.ical4j.model.component.VEvent From ec636cd58926466a9c1ef2c797d8aea97b81b25f Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 26 Mar 2026 11:52:40 +0100 Subject: [PATCH 4/4] Move functions from `AndroidTemporalMapper` to `AndroidTimeUtils` --- .../mapping/tasks/DmfsTaskBuilderTest.kt | 2 +- .../at/bitfire/ical4android/TaskReader.kt | 2 +- .../at/bitfire/ical4android/TaskWriter.kt | 2 +- .../mapping/calendar/AndroidEventHandler.kt | 2 +- .../builder/AndroidRecurrenceMapper.kt | 4 +- .../calendar/builder/DurationBuilder.kt | 2 +- .../calendar/builder/EndTimeBuilder.kt | 6 +- .../builder/OriginalInstanceTimeBuilder.kt | 4 +- .../calendar/builder/StartTimeBuilder.kt | 4 +- .../calendar/handler/DurationHandler.kt | 2 +- .../handler/RecurrenceFieldsHandler.kt | 4 +- .../mapping/tasks/DmfsTaskBuilder.kt | 2 +- .../synctools/util/AndroidTemporalMapper.kt | 82 -------- .../synctools/util/AndroidTimeUtils.kt | 63 ++++++ .../util/AndroidTemporalMapperTest.kt | 179 ------------------ .../synctools/util/AndroidTimeUtilsTest.kt | 162 ++++++++++++++++ 16 files changed, 243 insertions(+), 279 deletions(-) delete mode 100644 lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt delete mode 100644 lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt diff --git a/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt b/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt index a5e85ee7..c54a9db6 100644 --- a/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt +++ b/lib/src/androidTest/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilderTest.kt @@ -18,8 +18,8 @@ import at.bitfire.ical4android.Task import at.bitfire.ical4android.TaskProvider import at.bitfire.ical4android.UnknownProperty import at.bitfire.ical4android.impl.TestTaskList -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.storage.tasks.DmfsTaskList +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.model.DateList import net.fortuna.ical4j.model.TimeZoneRegistryFactory import net.fortuna.ical4j.model.parameter.Email diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt b/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt index 62dc437c..0fe55d62 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/TaskReader.kt @@ -11,7 +11,7 @@ import at.bitfire.synctools.exception.InvalidICalendarException import at.bitfire.synctools.icalendar.Css3Color import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.ICalendarParser -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.model.Component import net.fortuna.ical4j.model.TemporalAdapter import net.fortuna.ical4j.model.component.VToDo diff --git a/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt b/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt index 5f2215cc..acc8bdfe 100644 --- a/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt +++ b/lib/src/main/kotlin/at/bitfire/ical4android/TaskWriter.kt @@ -11,7 +11,7 @@ import at.bitfire.ical4android.ICalendar.Companion.withUserAgents import at.bitfire.synctools.icalendar.Css3Color import at.bitfire.synctools.icalendar.VTimeZoneMinifier import at.bitfire.synctools.icalendar.plusAssign -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.data.CalendarOutputter import net.fortuna.ical4j.model.Calendar import net.fortuna.ical4j.model.Parameter diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt index 12d4bb48..8a59e260 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/AndroidEventHandler.kt @@ -13,7 +13,6 @@ import at.bitfire.synctools.icalendar.AssociatedEvents import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.plusAssign import at.bitfire.synctools.icalendar.recurrenceId -import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.mapping.calendar.handler.AccessLevelHandler import at.bitfire.synctools.mapping.calendar.handler.AndroidEventFieldHandler import at.bitfire.synctools.mapping.calendar.handler.AttendeesHandler @@ -37,6 +36,7 @@ import at.bitfire.synctools.mapping.calendar.handler.UnknownPropertiesHandler import at.bitfire.synctools.mapping.calendar.handler.UrlHandler import at.bitfire.synctools.storage.calendar.EventAndExceptions import at.bitfire.synctools.storage.calendar.EventsContract +import at.bitfire.synctools.util.AndroidTimeUtils.toZonedDateTime import net.fortuna.ical4j.model.DateList import net.fortuna.ical4j.model.ParameterList import net.fortuna.ical4j.model.Property diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt index 737aeaf9..cb50aeca 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/AndroidRecurrenceMapper.kt @@ -7,8 +7,8 @@ package at.bitfire.synctools.mapping.calendar.builder import at.bitfire.ical4android.util.DateUtils -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toZonedDateTime import net.fortuna.ical4j.model.Property import java.time.Instant import java.time.LocalDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt index f1707920..4f224499 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/DurationBuilder.kt @@ -16,7 +16,7 @@ import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.ical4android.util.TimeApiExtensions.toRfc5545Duration import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.RDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt index 61c60515..eb5482ed 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/EndTimeBuilder.kt @@ -13,9 +13,9 @@ import at.bitfire.ical4android.util.DateUtils import at.bitfire.ical4android.util.TimeApiExtensions.abs import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.util.AndroidTemporalMapper.androidTimezoneId -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTimeUtils.androidTimezoneId +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toZonedDateTime import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.RDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt index f8b8be9b..0c3e6419 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/OriginalInstanceTimeBuilder.kt @@ -12,8 +12,8 @@ import at.bitfire.ical4android.util.DateUtils import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.recurrenceId import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toZonedDateTime import net.fortuna.ical4j.model.component.VEvent import java.time.LocalDate import java.time.ZonedDateTime diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt index de0eeab4..677f86fa 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/builder/StartTimeBuilder.kt @@ -10,8 +10,8 @@ import android.content.Entity import android.provider.CalendarContract.Events import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.util.AndroidTemporalMapper.androidTimezoneId -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.androidTimezoneId +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.model.component.VEvent import java.time.temporal.Temporal diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt index 5c668e03..d274aa91 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/DurationHandler.kt @@ -10,8 +10,8 @@ import android.content.Entity import android.provider.CalendarContract.Events import at.bitfire.ical4android.util.TimeApiExtensions.abs import at.bitfire.synctools.icalendar.plusAssign -import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.util.AndroidTimeUtils +import at.bitfire.synctools.util.AndroidTimeUtils.toZonedDateTime import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.DtEnd import java.time.Instant diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt index 1ac0d098..155615d9 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/calendar/handler/RecurrenceFieldsHandler.kt @@ -11,9 +11,9 @@ import android.provider.CalendarContract.Events import at.bitfire.ical4android.util.DateUtils import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate import at.bitfire.synctools.exception.InvalidLocalResourceException -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp -import at.bitfire.synctools.util.AndroidTemporalMapper.toZonedDateTime import at.bitfire.synctools.util.AndroidTimeUtils +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp +import at.bitfire.synctools.util.AndroidTimeUtils.toZonedDateTime import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.property.ExDate diff --git a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt index 225ba5e4..61010f14 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/mapping/tasks/DmfsTaskBuilder.kt @@ -13,11 +13,11 @@ import at.bitfire.ical4android.ICalendar import at.bitfire.ical4android.Task import at.bitfire.ical4android.UnknownProperty import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp import at.bitfire.synctools.storage.BatchOperation.CpoBuilder import at.bitfire.synctools.storage.tasks.DmfsTaskList import at.bitfire.synctools.storage.tasks.TasksBatchOperation import at.bitfire.synctools.util.AndroidTimeUtils +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.model.Parameter import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.TimeZone diff --git a/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt deleted file mode 100644 index f8d8831c..00000000 --- a/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTemporalMapper.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of bitfireAT/synctools which is released under GPLv3. - * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package at.bitfire.synctools.util - -import net.fortuna.ical4j.model.TemporalAdapter -import net.fortuna.ical4j.util.TimeZones -import java.time.Instant -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.OffsetDateTime -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.temporal.Temporal - -object AndroidTemporalMapper { - - private const val TZID_UTC = "UTC" - - /** - * Converts this [Temporal] to the timestamp that should be used when writing an event to the - * Android calendar provider. - */ - fun Temporal.toTimestamp(): Long { - val epochSeconds = when (this) { - is LocalDate -> atStartOfDay().atZone(TimeZones.getDateTimeZone().toZoneId()).toEpochSecond() - is LocalDateTime -> atZone(TimeZones.getDefault().toZoneId()).toEpochSecond() - is OffsetDateTime -> toEpochSecond() - is ZonedDateTime -> toEpochSecond() - is Instant -> epochSecond - else -> error("Unsupported Temporal type: ${this::class.qualifiedName}") - } - - return epochSeconds * 1000L - } - - /** - * Converts this [Temporal] to a [ZonedDateTime] that is created from the timestamp returned by - * [toTimestamp] and the time zone returned by [androidTimezoneId]. - */ - fun Temporal.toZonedDateTime(): ZonedDateTime { - return Instant.ofEpochMilli(toTimestamp()).atZone(ZoneId.of(androidTimezoneId())) - } - - /** - * Returns the timezone ID that should be used when writing an event to the Android calendar provider. - * - * Note: For date-times with a given time zone, it needs to be a system time zone. Call - * [at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate] on dates coming from - * ical4j before calling this function. - * - * @return - "UTC" for dates and UTC date-times - * - the specified time zone ID for date-times with given time zone - * - the currently set default time zone ID for floating date-times - */ - fun Temporal.androidTimezoneId(): String { - return if (TemporalAdapter.isDateTimePrecision(this)) { - if (TemporalAdapter.isUtc(this)) { - TZID_UTC - } else if (TemporalAdapter.isFloating(this)) { - ZoneId.systemDefault().id - } else { - require(this is ZonedDateTime) { "Non-floating date-time must be a ZonedDateTime" } - - val timezoneId = this.zone.id - require(!timezoneId.startsWith("ical4j")) { - "ical4j ZoneIds are not supported. Call DatePropertyTzMapper.normalizedDate() " + - "before passing a date to this function." - } - - timezoneId - } - } else { - // For all-day events EventsColumns.EVENT_TIMEZONE must be "UTC". - TZID_UTC - } - } - -} \ No newline at end of file diff --git a/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt index b2bfa10b..8e8069fa 100644 --- a/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt +++ b/lib/src/main/kotlin/at/bitfire/synctools/util/AndroidTimeUtils.kt @@ -17,6 +17,7 @@ import net.fortuna.ical4j.model.parameter.TzId import net.fortuna.ical4j.model.parameter.Value import net.fortuna.ical4j.model.property.DateListProperty import net.fortuna.ical4j.model.property.RDate +import net.fortuna.ical4j.util.TimeZones import java.time.Duration import java.time.Instant import java.time.LocalDate @@ -27,6 +28,7 @@ import java.time.ZoneId import java.time.ZoneOffset import java.time.ZonedDateTime import java.time.temporal.ChronoField +import java.time.temporal.Temporal import java.time.temporal.TemporalAmount import java.util.logging.Logger import kotlin.jvm.optionals.getOrDefault @@ -45,6 +47,67 @@ object AndroidTimeUtils { get() = Logger.getLogger(javaClass.name) + /** + * Converts this [Temporal] to the timestamp that should be used when writing an event to the + * Android calendar provider or task providers. + */ + fun Temporal.toTimestamp(): Long { + val epochSeconds = when (this) { + is LocalDate -> atStartOfDay().atZone(TimeZones.getDateTimeZone().toZoneId()).toEpochSecond() + is LocalDateTime -> atZone(TimeZones.getDefault().toZoneId()).toEpochSecond() + is OffsetDateTime -> toEpochSecond() + is ZonedDateTime -> toEpochSecond() + is Instant -> epochSecond + else -> error("Unsupported Temporal type: ${this::class.qualifiedName}") + } + + return epochSeconds * 1000L + } + + /** + * Converts this [Temporal] to a [ZonedDateTime] that is created from the timestamp returned by + * [toTimestamp] and the time zone returned by [androidTimezoneId]. + */ + fun Temporal.toZonedDateTime(): ZonedDateTime { + return Instant.ofEpochMilli(toTimestamp()).atZone(ZoneId.of(androidTimezoneId())) + } + + /** + * Returns the timezone ID that should be used when writing an event to the Android calendar + * provider or task providers. + * + * Note: For date-times with a given time zone, it needs to be a system time zone. Call + * [at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate] on dates coming from + * ical4j before calling this function. + * + * @return - "UTC" for dates and UTC date-times + * - the specified time zone ID for date-times with given time zone + * - the currently set default time zone ID for floating date-times + */ + fun Temporal.androidTimezoneId(): String { + return if (TemporalAdapter.isDateTimePrecision(this)) { + if (TemporalAdapter.isUtc(this)) { + TZID_UTC + } else if (TemporalAdapter.isFloating(this)) { + ZoneId.systemDefault().id + } else { + require(this is ZonedDateTime) { "Non-floating date-time must be a ZonedDateTime" } + + val timezoneId = this.zone.id + require(!timezoneId.startsWith("ical4j")) { + "ical4j ZoneIds are not supported. Call DatePropertyTzMapper.normalizedDate() " + + "before passing a date to this function." + } + + timezoneId + } + } else { + // For all-day events EventsColumns.EVENT_TIMEZONE must be "UTC". + TZID_UTC + } + } + + // recurrence sets /** diff --git a/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt deleted file mode 100644 index 2088a41f..00000000 --- a/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTemporalMapperTest.kt +++ /dev/null @@ -1,179 +0,0 @@ -/* - * This file is part of bitfireAT/synctools which is released under GPLv3. - * Copyright © All Contributors. See the LICENSE and AUTHOR files in the root directory for details. - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package at.bitfire.synctools.util - -import at.bitfire.DefaultTimezoneRule -import at.bitfire.synctools.icalendar.requireDtStart -import at.bitfire.synctools.util.AndroidTemporalMapper.androidTimezoneId -import at.bitfire.synctools.util.AndroidTemporalMapper.toTimestamp -import net.fortuna.ical4j.data.CalendarBuilder -import net.fortuna.ical4j.model.Component -import net.fortuna.ical4j.model.component.VEvent -import org.junit.Assert.assertEquals -import org.junit.Assert.fail -import org.junit.Rule -import org.junit.Test -import java.io.StringReader -import java.time.Instant -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.OffsetDateTime -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.chrono.JapaneseDate -import java.time.temporal.Temporal - -class AndroidTemporalMapperTest { - - @get:Rule - val tzRule = DefaultTimezoneRule("Europe/Vienna") - - @Test - fun `toTimestamp on LocalDate should use start of UTC day`() { - val date = LocalDate.of(2026, 3, 12) - - val timestamp = date.toTimestamp() - - assertEquals(1773273600000L, timestamp) - } - - @Test - fun `toTimestamp on LocalDateTime should use system default time zone`() { - val date = LocalDateTime.of(2026, 3, 12, 12, 34, 56) - - val timestamp = date.toTimestamp() - - assertEquals(1773315296000L, timestamp) - } - - @Test - fun `toTimestamp on OffsetDateTime`() { - val date = OffsetDateTime.of(2026, 3, 12, 12, 0, 0, 0, ZoneOffset.ofHours(3)) - - val timestamp = date.toTimestamp() - - assertEquals(1773306000000L, timestamp) - } - - @Test - fun `toTimestamp on ZonedDateTime`() { - val date = ZonedDateTime.of(2026, 3, 12, 12, 0, 0, 0, ZoneId.of("Europe/Helsinki")) - - val timestamp = date.toTimestamp() - - assertEquals(1773309600000L, timestamp) - } - - @Test - fun `toTimestamp on Instant`() { - val inputTimestamp = 1773273600000L - val date = Instant.ofEpochMilli(inputTimestamp) - - val timestamp = date.toTimestamp() - - assertEquals(inputTimestamp, timestamp) - } - - @Test - fun `toTimestamp on unsupported type`() { - try { - JapaneseDate.now().toTimestamp() - - fail("Expected exception") - } catch (e: IllegalStateException) { - assertEquals("Unsupported Temporal type: java.time.chrono.JapaneseDate", e.message) - } - } - - - @Test - fun `androidTimezoneId on LocalDate`() { - val date = LocalDate.now() - - val timezoneId = date.androidTimezoneId() - - assertEquals("UTC", timezoneId) - } - - @Test - fun `androidTimezoneId on LocalDateTime`() { - val date = LocalDateTime.now() - - val timezoneId = date.androidTimezoneId() - - assertEquals(tzRule.defaultZoneId.id, timezoneId) - } - - @Test - fun `androidTimezoneId on ZonedDateTime`() { - val date = LocalDateTime.now().atZone(ZoneId.of("Europe/Dublin")) - - val timezoneId = date.androidTimezoneId() - - assertEquals("Europe/Dublin", timezoneId) - } - - @Test - fun `androidTimezoneId on Instant`() { - val date = Instant.now() - - val timezoneId = date.androidTimezoneId() - - assertEquals("UTC", timezoneId) - } - - @Test - fun `androidTimezoneId on OffsetDateTime`() { - try { - OffsetDateTime.now().androidTimezoneId() - - fail("Expected exception") - } catch (e: IllegalArgumentException) { - assertEquals("Non-floating date-time must be a ZonedDateTime", e.message) - } - } - - @Test - fun `androidTimezoneId on ZonedDateTime from ical4j`() { - val cal = CalendarBuilder().build(StringReader( - """ - BEGIN:VCALENDAR - VERSION:2.0 - BEGIN:VTIMEZONE - TZID:Etc/ABC - BEGIN:STANDARD - TZNAME:-03 - TZOFFSETFROM:-0300 - TZOFFSETTO:-0300 - DTSTART:19700101T000000 - END:STANDARD - END:VTIMEZONE - BEGIN:VEVENT - SUMMARY:Test Timezones - DTSTART;TZID=Etc/ABC:20250828T130000 - END:VEVENT - END:VCALENDAR - """.trimIndent() - )) - val vEvent = cal.getComponent(Component.VEVENT).get() - val date = vEvent.requireDtStart().date - - try { - date.androidTimezoneId() - - fail("Expected exception") - } catch (e: IllegalArgumentException) { - assertEquals( - "ical4j ZoneIds are not supported. Call DatePropertyTzMapper.normalizedDate() " + - "before passing a date to this function.", - e.message - ) - } - } - -} \ No newline at end of file diff --git a/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTimeUtilsTest.kt b/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTimeUtilsTest.kt index 989d703c..7d28e3a2 100644 --- a/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTimeUtilsTest.kt +++ b/lib/src/test/kotlin/at/bitfire/synctools/util/AndroidTimeUtilsTest.kt @@ -6,28 +6,47 @@ package at.bitfire.synctools.util +import at.bitfire.DefaultTimezoneRule import at.bitfire.dateTimeValue import at.bitfire.dateValue +import at.bitfire.synctools.icalendar.requireDtStart +import at.bitfire.synctools.util.AndroidTimeUtils.androidTimezoneId +import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp import net.fortuna.ical4j.data.CalendarBuilder +import net.fortuna.ical4j.model.Component import net.fortuna.ical4j.model.DateList import net.fortuna.ical4j.model.Parameter import net.fortuna.ical4j.model.TimeZone import net.fortuna.ical4j.model.TimeZoneRegistryFactory +import net.fortuna.ical4j.model.component.VEvent import net.fortuna.ical4j.model.parameter.TzId import net.fortuna.ical4j.model.parameter.Value import net.fortuna.ical4j.model.property.ExDate import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Assert.fail +import org.junit.Rule import org.junit.Test import java.io.StringReader import java.time.DateTimeException import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.chrono.JapaneseDate import java.time.format.DateTimeParseException +import java.time.temporal.Temporal import java.time.zone.ZoneRulesException import java.util.Optional class AndroidTimeUtilsTest { + @get:Rule + val tzRule = DefaultTimezoneRule("Europe/Vienna") + val tzRegistry = TimeZoneRegistryFactory.getInstance().createRegistry()!! val tzBerlin: TimeZone = tzRegistry.getTimeZone("Europe/Berlin")!! val tzToronto: TimeZone = tzRegistry.getTimeZone("America/Toronto")!! @@ -412,4 +431,147 @@ class AndroidTimeUtilsTest { } */ + @Test + fun `toTimestamp on LocalDate should use start of UTC day`() { + val date = LocalDate.of(2026, 3, 12) + + val timestamp = date.toTimestamp() + + assertEquals(1773273600000L, timestamp) + } + + @Test + fun `toTimestamp on LocalDateTime should use system default time zone`() { + val date = LocalDateTime.of(2026, 3, 12, 12, 34, 56) + + val timestamp = date.toTimestamp() + + assertEquals(1773315296000L, timestamp) + } + + @Test + fun `toTimestamp on OffsetDateTime`() { + val date = OffsetDateTime.of(2026, 3, 12, 12, 0, 0, 0, ZoneOffset.ofHours(3)) + + val timestamp = date.toTimestamp() + + assertEquals(1773306000000L, timestamp) + } + + @Test + fun `toTimestamp on ZonedDateTime`() { + val date = ZonedDateTime.of(2026, 3, 12, 12, 0, 0, 0, ZoneId.of("Europe/Helsinki")) + + val timestamp = date.toTimestamp() + + assertEquals(1773309600000L, timestamp) + } + + @Test + fun `toTimestamp on Instant`() { + val inputTimestamp = 1773273600000L + val date = Instant.ofEpochMilli(inputTimestamp) + + val timestamp = date.toTimestamp() + + assertEquals(inputTimestamp, timestamp) + } + + @Test + fun `toTimestamp on unsupported type`() { + try { + JapaneseDate.now().toTimestamp() + + fail("Expected exception") + } catch (e: IllegalStateException) { + assertEquals("Unsupported Temporal type: java.time.chrono.JapaneseDate", e.message) + } + } + + + @Test + fun `androidTimezoneId on LocalDate`() { + val date = LocalDate.now() + + val timezoneId = date.androidTimezoneId() + + assertEquals("UTC", timezoneId) + } + + @Test + fun `androidTimezoneId on LocalDateTime`() { + val date = LocalDateTime.now() + + val timezoneId = date.androidTimezoneId() + + assertEquals(tzRule.defaultZoneId.id, timezoneId) + } + + @Test + fun `androidTimezoneId on ZonedDateTime`() { + val date = LocalDateTime.now().atZone(ZoneId.of("Europe/Dublin")) + + val timezoneId = date.androidTimezoneId() + + assertEquals("Europe/Dublin", timezoneId) + } + + @Test + fun `androidTimezoneId on Instant`() { + val date = Instant.now() + + val timezoneId = date.androidTimezoneId() + + assertEquals("UTC", timezoneId) + } + + @Test + fun `androidTimezoneId on OffsetDateTime`() { + try { + OffsetDateTime.now().androidTimezoneId() + + fail("Expected exception") + } catch (e: IllegalArgumentException) { + assertEquals("Non-floating date-time must be a ZonedDateTime", e.message) + } + } + + @Test + fun `androidTimezoneId on ZonedDateTime from ical4j`() { + val cal = CalendarBuilder().build(StringReader( + """ + BEGIN:VCALENDAR + VERSION:2.0 + BEGIN:VTIMEZONE + TZID:Etc/ABC + BEGIN:STANDARD + TZNAME:-03 + TZOFFSETFROM:-0300 + TZOFFSETTO:-0300 + DTSTART:19700101T000000 + END:STANDARD + END:VTIMEZONE + BEGIN:VEVENT + SUMMARY:Test Timezones + DTSTART;TZID=Etc/ABC:20250828T130000 + END:VEVENT + END:VCALENDAR + """.trimIndent() + )) + val vEvent = cal.getComponent(Component.VEVENT).get() + val date = vEvent.requireDtStart().date + + try { + date.androidTimezoneId() + + fail("Expected exception") + } catch (e: IllegalArgumentException) { + assertEquals( + "ical4j ZoneIds are not supported. Call DatePropertyTzMapper.normalizedDate() " + + "before passing a date to this function.", + e.message + ) + } + } + } \ No newline at end of file