Tidy IcsParser day arithmetic, index scans and wire lookups

This commit is contained in:
makiolaj committed 2026-10-16 13:35:34 +02:00
1 parent 9b04b2a419
commit ee5b25e54f
2 files changed
+18 -46

No files matched your search

@@ -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
@@ -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:<component>` 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<String>, from: Int, end: Int): Int {
var i = from
while (i < end) {
fun indexOfNextComponent(lines: List<String>, 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<String>, from: Int, component: String): Int {
var i = from
while (i < lines.size) {
/** Index of the matching `END:<component>` at/after [from], or list end. */
fun indexOfEnd(lines: List<String>, 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
}
}