diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapper.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapper.kt index d441fd8..e6de991 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapper.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapper.kt @@ -37,16 +37,15 @@ internal fun ColumnReader.toEventDetailCore( // Recurring events store DURATION instead of DTEND, so the series row's // DTEND is null. Keep the event (end == begin); callers that opened a // specific occurrence supply the real per-occurrence times from - // CalendarContract.Instances. Only a present-but-backwards DTEND is malformed. + // CalendarContract.Instances. A present-but-backwards DTEND is malformed, + // but dropping the row would make the event un-openable — the same trap as + // the pre-1970 DTSTART bug above (issue #34): it would surface as the + // generic error screen with no way to open the event and fix it. Clamp to a + // zero-length event instead (matching SearchMapper's coerceAtLeast). val end = if (isNull(EventDetailProjection.IDX_DTEND)) { begin } else { - val rawEnd = getLong(EventDetailProjection.IDX_DTEND) - if (rawEnd < begin) { - Log.w(TAG, "Dropping event with dtend=$rawEnd < dtstart=$begin") - return null - } - rawEnd + getLong(EventDetailProjection.IDX_DTEND).coerceAtLeast(begin) } // Kept raw (no untitled fallback): the detail screen substitutes its own diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapperTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapperTest.kt index 9870cde..299d2fe 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapperTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventDetailMapperTest.kt @@ -120,9 +120,14 @@ class EventDetailMapperTest { } @Test - fun `dtend before dtstart drops detail`() { + fun `dtend before dtstart is clamped to a zero-length event, not dropped`() { + // A backwards DTEND is malformed, but dropping it would make the event + // un-openable (the "Something went wrong" trap, same as issue #34); keep + // it as a zero-length event so the user can still open and fix it. val detail = detailReader(dtstart = 2000L, dtend = 1000L).toDetail() - assertThat(detail).isNull() + assertThat(detail).isNotNull() + assertThat(detail!!.instance.start.toEpochMilliseconds()).isEqualTo(2000L) + assertThat(detail.instance.end.toEpochMilliseconds()).isEqualTo(2000L) } @Test