fix(detail): clamp a backwards DTEND instead of dropping the event (#34)
toEventDetailCore returned null when a present DTEND preceded DTSTART, the only remaining false-drop that surfaces as the generic "Something went wrong." error screen — the same un-openable trap as the pre-1970 DTSTART bug, and worse because the user can't even open the malformed event to fix it. Clamp the end to DTSTART (a zero-length event) instead, matching how SearchMapper already coerces its end. After this the detail mapper drops a row only when DTSTART is genuinely absent (unrenderable). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user