diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt index eebdfc3..c4fdc98 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.Card @@ -35,6 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import ch.parano.myice.models.Event +import ch.parano.myice.util.formatEventDateTime @Composable fun EventCard( @@ -51,9 +51,7 @@ fun EventCard( .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp), ) { - Row( - modifier = Modifier.height(120.dp), - ) { + Row { Box( modifier = Modifier .width(4.dp) @@ -62,8 +60,7 @@ fun EventCard( ) Column( modifier = Modifier - .padding(16.dp) - .fillMaxHeight(), + .padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( @@ -83,7 +80,7 @@ fun EventCard( style = MaterialTheme.typography.bodySmall, ) Text( - text = "${event.start} - ${event.end}", + text = formatEventDateTime(event.start, event.end), style = MaterialTheme.typography.bodySmall, ) } diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt new file mode 100644 index 0000000..13d28f3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// MyIce Kotlin Multiplatform — schedule/convocation viewer +// Copyright (C) 2026 parano.ch +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ch.parano.myice.util + +import kotlinx.datetime.LocalDateTime + +private fun parseOrNull(iso: String): LocalDateTime? = runCatching { + LocalDateTime.parse(iso) +}.getOrNull() + +private fun LocalDateTime.dateStr(): String = + "${day.toString().padStart(2, '0')}.${monthNumber.toString().padStart(2, '0')}.${year.toString().padStart(4, '0')}" + +private fun LocalDateTime.timeStr(): String = + "${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}" + +fun formatEventDateTime(start: String, end: String): String { + val startDt = parseOrNull(start) + val endDt = parseOrNull(end) + if (startDt == null || endDt == null) return "$start - $end" + return if (startDt.date == endDt.date) { + "${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()}" + } else { + "${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()} (${endDt.dateStr()})" + } +} diff --git a/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt b/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt new file mode 100644 index 0000000..2fe081a --- /dev/null +++ b/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// MyIce Kotlin Multiplatform — schedule/convocation viewer +// Copyright (C) 2026 parano.ch +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package ch.parano.myice.util + +import kotlin.test.Test +import kotlin.test.assertEquals + +class EventDateTimeFormatterTest { + + @Test + fun formatsSameDayEventAsDateFollowedByTimeRange() { + val formatted = formatEventDateTime( + start = "2024-11-10T14:00:00", + end = "2024-11-10T16:15:00", + ) + assertEquals("10.11.2024 14:00 - 16:15", formatted) + } + + @Test + fun formatsCrossingMidnightEventWithBothDates() { + val formatted = formatEventDateTime( + start = "2024-11-10T20:00:00", + end = "2024-11-11T01:30:00", + ) + assertEquals("10.11.2024 20:00 - 01:30 (11.11.2024)", formatted) + } + + @Test + fun returnsRawStartWhenStartUnparseable() { + val formatted = formatEventDateTime(start = "s", end = "2024-11-10T16:15:00") + assertEquals("s - 2024-11-10T16:15:00", formatted) + } + + @Test + fun returnsRawEndWhenEndUnparseable() { + val formatted = formatEventDateTime(start = "2024-11-10T14:00:00", end = "e") + assertEquals("2024-11-10T14:00:00 - e", formatted) + } + + @Test + fun returnsRawWhenBothUnparseable() { + val formatted = formatEventDateTime(start = "s", end = "e") + assertEquals("s - e", formatted) + } +}