package org.terst.doot.widget.ui import org.terst.doot.widget.data.* import java.time.Instant import java.time.LocalDate import java.time.ZoneId enum class MultiDayVariant { NONE, STARTS, ENDS, SPANS } /** * The last calendar day an item's occurrence actually covers. Google * Calendar's all-day end date is EXCLUSIVE -- a single-day all-day event on * July 13 has end=July14 00:00 (one day past its own day; mirrors the same * convention already handled server-side, see effectiveEndDay in * internal/handlers/timeline.go) -- so for an all-day item this subtracts * one day to get the real last day. Timed events' end is already the real * end instant, so no adjustment is needed there. */ private fun effectiveEndDay(end: Instant, zone: ZoneId, isAllDay: Boolean): LocalDate { val day = end.atZone(zone).toLocalDate() return if (isAllDay) day.minusDays(1) else day } /** True for a calendar event whose start and (adjusted) end fall on different calendar days. */ internal fun isMultiDayEvent(item: WidgetItem, zone: ZoneId): Boolean { if (item.type != "event") return false val start = item.start?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return false val end = item.end?.let { runCatching { Instant.parse(it) }.getOrNull() } ?: return false val startDay = start.atZone(zone).toLocalDate() val endDay = effectiveEndDay(end, zone, item.isAllDay) return endDay.isAfter(startDay) } /** * How a multi-day item should render on renderDay. Caller must have already * confirmed isMultiDayEvent(item) so item.start/item.end are non-null. */ internal fun multiDayVariant(item: WidgetItem, renderDay: LocalDate, zone: ZoneId): MultiDayVariant { val start = Instant.parse(item.start!!).atZone(zone).toLocalDate() val end = effectiveEndDay(Instant.parse(item.end!!), zone, item.isAllDay) return when { renderDay.isEqual(start) -> MultiDayVariant.STARTS renderDay.isEqual(end) -> MultiDayVariant.ENDS renderDay.isAfter(start) && renderDay.isBefore(end) -> MultiDayVariant.SPANS else -> MultiDayVariant.NONE } } /** "" for a plain all-day row or a "spans"-through day; " (starts/ends HH:MM)" otherwise. */ internal fun multiDayLabel(event: WidgetItem, variant: MultiDayVariant): String = when (variant) { MultiDayVariant.STARTS -> event.start?.let { timeSuffix("starts", it) } ?: "" MultiDayVariant.ENDS -> event.end?.let { timeSuffix("ends", it) } ?: "" else -> "" } private fun timeSuffix(verb: String, iso: String): String { val t = Instant.parse(iso).atZone(ZoneId.systemDefault()) if (t.hour == 0 && t.minute == 0) return "" val minutePart = if (t.minute > 0) t.minute.toString().padStart(2, '0') else "" return " ($verb ${hourLabel(t.hour)}$minutePart)" }