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)
+ }
+}