From b9eeb2fe840b1ae32efc6e115f84cfc4f6440ea6 Mon Sep 17 00:00:00 2001 From: fethij <32542424+fethij@users.noreply.github.com> Date: Thu, 28 May 2026 21:54:32 +0000 Subject: [PATCH] Fix jump-to-date being off by one day in non-UTC timezones Closes signalapp/Signal-Android#14816 --- .../conversation/v2/ConversationFragment.kt | 3 ++- .../conversation/v2/JumpToDateValidator.kt | 3 ++- .../v2/JumpToDateValidatorTest.kt | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 026530ba20..09d4f6c815 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -392,6 +392,7 @@ import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId +import java.time.ZoneOffset import java.util.Locale import java.util.Optional import java.util.concurrent.ExecutionException @@ -5317,7 +5318,7 @@ class ConversationFragment : datePicker.addOnPositiveButtonClickListener { selectedDate -> if (selectedDate != null) { val localMidnightTimestamp = Instant.ofEpochMilli(selectedDate) - .atZone(ZoneId.systemDefault()) + .atZone(ZoneOffset.UTC) .toLocalDate() .atStartOfDay(ZoneId.systemDefault()) .toInstant() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidator.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidator.kt index 4187032070..ad988856d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidator.kt @@ -16,6 +16,7 @@ import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId +import java.time.ZoneOffset import java.time.temporal.TemporalAdjusters import java.util.concurrent.Executor import kotlin.time.Duration.Companion.days @@ -105,7 +106,7 @@ class JumpToDateValidator private constructor( private fun normalizeToLocalMidnight(timestamp: Long): Long { return Instant.ofEpochMilli(timestamp) - .atZone(zoneId) + .atZone(ZoneOffset.UTC) .toLocalDate() .atStartOfDay(zoneId) .toInstant() diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidatorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidatorTest.kt index c3e5d57b40..16a8f79c16 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidatorTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/JumpToDateValidatorTest.kt @@ -187,4 +187,27 @@ class JumpToDateValidatorTest { assertThat(validator.isValid(june15Utc)).isTrue() } + + @Test + fun `picker date maps to the same calendar day in a negative-offset zone`() { + val newYorkZone = ZoneId.of("America/New_York") + + // Days are keyed by local midnight in the database. + val june15NewYorkMidnight = LocalDate.of(2024, 6, 15) + .atStartOfDay(newYorkZone) + .toInstant() + .toEpochMilli() + + val lookup = { dates: Collection -> + dates.associateWith { it == june15NewYorkMidnight } + } + val validator = createValidator(lookup, zone = newYorkZone) + + // The picker reports June 15 as UTC midnight; it must map to June 15 locally, not June 14. + val june15PickerUtc = timestampForDate(2024, 6, 15) + + validator.isValid(june15PickerUtc) + + assertThat(validator.isValid(june15PickerUtc)).isTrue() + } }