From ee5b25e54f99a95b4ee5d739530d20a0397aeba1 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Fri, 16 Oct 2026 13:29:52 +0200 Subject: [PATCH] Tidy IcsParser day arithmetic, index scans and wire lookups --- .../calendula/domain/Recurrence.kt | 9 +-- .../calendula/domain/ics/IcsParser.kt | 55 ++++++------------- 2 files changed, 18 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/domain/Recurrence.kt b/app/src/main/java/de/jeanlucmakiola/calendula/domain/Recurrence.kt index 9da085d..3e55232 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/domain/Recurrence.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/domain/Recurrence.kt @@ -65,13 +65,8 @@ fun parseSimpleRecurrence( return null } - val freq = when (parts["FREQ"]?.uppercase()) { - "DAILY" -> RecurrenceFreq.Daily - "WEEKLY" -> RecurrenceFreq.Weekly - "MONTHLY" -> RecurrenceFreq.Monthly - "YEARLY" -> RecurrenceFreq.Yearly - else -> return null - } + val freq = RecurrenceFreq.entries.firstOrNull { it.name.equals(parts["FREQ"], ignoreCase = true) } + ?: return null val interval = parts["INTERVAL"]?.let { it.toIntOrNull()?.takeIf { n -> n >= 1 } ?: return null } ?: 1 // BYDAY is simple only as plain weekday picks on a weekly rule; ordinal diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/domain/ics/IcsParser.kt b/app/src/main/java/de/jeanlucmakiola/calendula/domain/ics/IcsParser.kt index 7adb142..75d4020 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/domain/ics/IcsParser.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/domain/ics/IcsParser.kt @@ -9,10 +9,10 @@ import kotlinx.datetime.atStartOfDayIn import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime import kotlin.math.ceil +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Instant -/** Milliseconds in a calendar day; all-day times are UTC midnights, so this is exact. */ -private const val DAY_MILLIS = 86_400_000L private const val MINUTES_PER_DAY = 1_440 /** @@ -316,13 +316,9 @@ class IcsParser(private val deviceZone: TimeZone = TimeZone.currentSystemDefault dtEnd: IcsDateTime?, duration: String?, ): Instant { - val explicit = dtEnd?.instant ?: duration?.let { - Instant.fromEpochMilliseconds( - start.instant.toEpochMilliseconds() + parseRfc2445DurationMillis(it), - ) - } + val explicit = dtEnd?.instant ?: duration?.let { start.instant + parseRfc2445DurationMillis(it).milliseconds } if (!start.isAllDay) return explicit ?: start.instant - return explicit?.takeIf { it > start.instant } ?: start.instant.plusDays(1) + return explicit?.takeIf { it > start.instant } ?: (start.instant + 1.days) } /** @@ -419,9 +415,6 @@ class IcsParser(private val deviceZone: TimeZone = TimeZone.currentSystemDefault fun IcsContentLine.isBegin(component: String) = name == "BEGIN" && value.trim().equals(component, true) - fun Instant.plusDays(days: Int): Instant = - Instant.fromEpochMilliseconds(toEpochMilliseconds() + days * DAY_MILLIS) - /** * A single-valued `CATEGORIES`, which is what the Fossify family writes * for the source calendar's name. A multi-item list is a tag list and @@ -440,42 +433,26 @@ class IcsParser(private val deviceZone: TimeZone = TimeZone.currentSystemDefault .takeIf { it.isNotEmpty() && !it.equals("null", true) } } - /** Index of the matching `END:` at/after [from], or list end. */ /** * Where an unterminated component's body has to stop: the next top-level * `VEVENT` / `VTODO` in `[from, end)`, or [end] when there is none. A * nested `VALARM` is part of the body and is not a boundary. */ - fun indexOfNextComponent(lines: List, from: Int, end: Int): Int { - var i = from - while (i < end) { + fun indexOfNextComponent(lines: List, from: Int, end: Int): Int = + (from until end).firstOrNull { i -> val line = parseContentLine(lines[i]) - if (line != null && (line.isBegin("VEVENT") || line.isBegin("VTODO"))) { - return i - } - i++ - } - return end - } + line != null && (line.isBegin("VEVENT") || line.isBegin("VTODO")) + } ?: end - fun indexOfEnd(lines: List, from: Int, component: String): Int { - var i = from - while (i < lines.size) { + /** Index of the matching `END:` at/after [from], or list end. */ + fun indexOfEnd(lines: List, from: Int, component: String): Int = + (from until lines.size).firstOrNull { i -> val line = parseContentLine(lines[i]) - if (line != null && line.name == "END" && - line.value.trim().equals(component, true) - ) { - return i - } - i++ - } - return lines.size - } + line != null && line.name == "END" && line.value.trim().equals(component, true) + } ?: lines.size - fun mapIcsStatus(value: String): EventStatus = when (value.trim().uppercase()) { - "TENTATIVE" -> EventStatus.Tentative - "CANCELLED" -> EventStatus.Cancelled - else -> EventStatus.Confirmed - } + fun mapIcsStatus(value: String): EventStatus = + EventStatus.entries.firstOrNull { it.name.equals(value.trim(), ignoreCase = true) } + ?: EventStatus.Confirmed } }