Share the all-day form prefill and RRULE normalising

This commit is contained in:
makiolaj committed 2026-10-16 13:35:34 +02:00
1 parent 4a4daaaf76
commit 770d73b529
3 files changed
+22 -26

No files matched your search

@@ -162,10 +162,7 @@ fun EventDetail.toEditForm(beginMillis: Long, endMillis: Long, zone: TimeZone):
val begin = Instant.fromEpochMilliseconds(beginMillis)
val finish = Instant.fromEpochMilliseconds(endMillis)
val (start, end) = if (instance.isAllDay) {
val startDate = begin.toLocalDateTime(TimeZone.UTC).date
val endExclusive = finish.toLocalDateTime(TimeZone.UTC).date
val endDate = maxOf(startDate, LocalDate.fromEpochDays(endExclusive.toEpochDays() - 1))
FormTime(LocalDateTime(startDate, LocalTime(9, 0))) to FormTime(LocalDateTime(endDate, LocalTime(10, 0)))
allDayFormTimes(begin.toLocalDateTime(TimeZone.UTC).date, finish.toLocalDateTime(TimeZone.UTC).date)
} else {
begin.toFormTime(formZone) to finish.toFormTime(formZone)
}
@@ -181,7 +178,7 @@ fun EventDetail.toEditForm(beginMillis: Long, endMillis: Long, zone: TimeZone):
reminders = reminders.map { it.minutes }.distinct().sorted(),
availability = availability,
accessLevel = accessLevel,
rrule = rrule?.removePrefix("RRULE:")?.takeIf { it.isNotBlank() },
rrule = rrule.normalizedRrule(),
// The provider fills EVENT_COLOR from the key, so [color] is the
// swatch either way; a null colour means the event inherits its
// calendar's colour.
@@ -233,6 +230,19 @@ fun EventDetail.toEditSnapshot(beginMillis: Long, endMillis: Long, zone: TimeZon
rowEnd = instance.end,
)
/**
* Form times for an all-day span stored as UTC midnights with an exclusive
* end: the last covered day (never before [start]), with placeholder
* wall-clock times kept in case the user switches the event to timed.
*/
internal fun allDayFormTimes(start: LocalDate, endExclusive: LocalDate?): Pair<FormTime, FormTime> {
val end = endExclusive?.let { maxOf(start, LocalDate.fromEpochDays(it.toEpochDays() - 1)) } ?: start
return FormTime(LocalDateTime(start, LocalTime(9, 0))) to FormTime(LocalDateTime(end, LocalTime(10, 0)))
}
/** The bare RRULE value the form edits; blank means none. */
internal fun String?.normalizedRrule(): String? = this?.removePrefix("RRULE:")?.takeIf { it.isNotBlank() }
/**
* Whether this form's times mean something else in [target] — a timed event
* pinned to another zone. False when there's nothing to disambiguate: an
@@ -1,8 +1,5 @@
package de.jeanlucmakiola.calendula.domain
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Duration.Companion.hours
@@ -35,17 +32,11 @@ fun buildInsertEventForm(
now: Instant,
): EventForm {
val (start, end) = if (isAllDay) {
// All-day provider times are UTC midnights with an exclusive end; show
// the last covered day and keep placeholder wall-clock times in case the
// user switches the event to timed (mirrors EventDetail.toEditForm).
val startDate = beginMillis
?.let { Instant.fromEpochMilliseconds(it).toLocalDateTime(TimeZone.UTC).date }
?: now.toLocalDateTime(zone).date
val endDate = endMillis
?.let { Instant.fromEpochMilliseconds(it).toLocalDateTime(TimeZone.UTC).date }
?.let { exclusive -> maxOf(startDate, LocalDate.fromEpochDays(exclusive.toEpochDays() - 1)) }
?: startDate
FormTime(LocalDateTime(startDate, LocalTime(9, 0))) to FormTime(LocalDateTime(endDate, LocalTime(10, 0)))
val endExclusive = endMillis?.let { Instant.fromEpochMilliseconds(it).toLocalDateTime(TimeZone.UTC).date }
allDayFormTimes(startDate, endExclusive)
} else {
val startAt = beginMillis?.let(Instant::fromEpochMilliseconds) ?: nextFullHour(now)
val endAt = endMillis?.let(Instant::fromEpochMilliseconds)?.takeIf { it >= startAt }
@@ -62,7 +53,7 @@ fun buildInsertEventForm(
description = description.orEmpty(),
// Bare RRULE value (Events.RRULE convention); tolerate a leading "RRULE:"
// some callers include.
rrule = rrule?.removePrefix("RRULE:")?.takeIf { it.isNotBlank() },
rrule = rrule.normalizedRrule(),
)
}
@@ -1,11 +1,9 @@
package de.jeanlucmakiola.calendula.domain.ics
import de.jeanlucmakiola.calendula.domain.EventForm
import de.jeanlucmakiola.calendula.domain.FormTime
import de.jeanlucmakiola.calendula.domain.allDayFormTimes
import de.jeanlucmakiola.calendula.domain.normalizedRrule
import de.jeanlucmakiola.calendula.domain.toFormTime
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
@@ -23,10 +21,7 @@ import kotlinx.datetime.toLocalDateTime
*/
fun ParsedIcsEvent.toEventForm(zone: TimeZone): EventForm {
val (start, end) = if (isAllDay) {
val startDate = this.start.toLocalDateTime(TimeZone.UTC).date
val endExclusive = this.end.toLocalDateTime(TimeZone.UTC).date
val endDate = maxOf(startDate, LocalDate.fromEpochDays(endExclusive.toEpochDays() - 1))
FormTime(LocalDateTime(startDate, LocalTime(9, 0))) to FormTime(LocalDateTime(endDate, LocalTime(10, 0)))
allDayFormTimes(this.start.toLocalDateTime(TimeZone.UTC).date, this.end.toLocalDateTime(TimeZone.UTC).date)
} else {
this.start.toFormTime(zone) to this.end.toFormTime(zone)
}
@@ -40,6 +35,6 @@ fun ParsedIcsEvent.toEventForm(zone: TimeZone): EventForm {
description = description.orEmpty(),
reminders = semanticReminderMinutes(),
availability = availability,
rrule = recurrenceRule?.removePrefix("RRULE:")?.takeIf { it.isNotBlank() },
rrule = recurrenceRule.normalizedRrule(),
)
}