diff --git a/shared/core/src/androidMain/kotlin/com/paligot/confily/core/AlarmScheduler.android.kt b/shared/core/src/androidMain/kotlin/com/paligot/confily/core/AlarmScheduler.android.kt index 799bae10..ce745735 100644 --- a/shared/core/src/androidMain/kotlin/com/paligot/confily/core/AlarmScheduler.android.kt +++ b/shared/core/src/androidMain/kotlin/com/paligot/confily/core/AlarmScheduler.android.kt @@ -8,11 +8,7 @@ import com.paligot.confily.resources.Resource import com.paligot.confily.resources.title_notif_reminder_talk import com.paligot.confily.schedules.ui.models.TalkItemUi import kotlinx.coroutines.coroutineScope -import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.TimeZone -import kotlinx.datetime.minus -import kotlinx.datetime.toInstant -import kotlinx.datetime.toLocalDateTime import org.jetbrains.compose.resources.getString import java.util.Locale @@ -34,11 +30,11 @@ actual class AlarmScheduler( val pendingIntent = PendingIntent.getBroadcast(context, talkItem.id.hashCode(), intent, flags) if (isFavorite) { - val triggerAtMillis = talkItem.startTime - .toLocalDateTime() - .toInstant(TimeZone.UTC) - .minus(ReminderInMinutes, DateTimeUnit.MINUTE) - .toEpochMilliseconds() + val triggerAtMillis = reminderTriggerAtMillis( + startTime = talkItem.startTime, + timeZone = TimeZone.currentSystemDefault(), + reminderInMinutes = ReminderInMinutes + ) alarmManager.setAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerAtMillis, diff --git a/shared/core/src/commonMain/kotlin/com/paligot/confily/core/ReminderTime.kt b/shared/core/src/commonMain/kotlin/com/paligot/confily/core/ReminderTime.kt new file mode 100644 index 00000000..80e23122 --- /dev/null +++ b/shared/core/src/commonMain/kotlin/com/paligot/confily/core/ReminderTime.kt @@ -0,0 +1,25 @@ +package com.paligot.confily.core + +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.TimeZone +import kotlinx.datetime.minus +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime + +/** + * Computes the absolute trigger instant (epoch millis) of a session reminder alarm. + * + * [startTime] is the session's wall-clock start time at the event location (e.g. "2026-06-11T08:30") + * and carries no timezone. It is interpreted in [timeZone] before being turned into an absolute + * instant: pass the device timezone so the alarm fires at the time displayed in the app rather than + * being offset by the difference from UTC. The reminder is scheduled [reminderInMinutes] earlier. + */ +internal fun reminderTriggerAtMillis( + startTime: String, + timeZone: TimeZone, + reminderInMinutes: Int +): Long = startTime + .toLocalDateTime() + .toInstant(timeZone) + .minus(reminderInMinutes, DateTimeUnit.MINUTE) + .toEpochMilliseconds() diff --git a/shared/core/src/commonTest/kotlin/com/paligot/confily/core/ReminderTimeTest.kt b/shared/core/src/commonTest/kotlin/com/paligot/confily/core/ReminderTimeTest.kt new file mode 100644 index 00000000..c39d07f9 --- /dev/null +++ b/shared/core/src/commonTest/kotlin/com/paligot/confily/core/ReminderTimeTest.kt @@ -0,0 +1,31 @@ +package com.paligot.confily.core + +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class ReminderTimeTest { + @Test + fun reminder_trigger_uses_event_local_wall_clock_not_utc() { + // "2026-06-11T08:30" is a wall-clock time at the event (Lille / Europe/Paris, CEST = UTC+2 + // in June) and carries no timezone. A 10-minute reminder must fire at 08:20 local = 06:20 UTC. + val triggerAtMillis = reminderTriggerAtMillis( + startTime = "2026-06-11T08:30", + timeZone = TimeZone.of("Europe/Paris"), + reminderInMinutes = 10 + ) + + assertEquals( + Instant.parse("2026-06-11T06:20:00Z").toEpochMilliseconds(), + triggerAtMillis + ) + // Regression guard: the previous code interpreted the wall-clock time as UTC, which would + // have fired the alarm at 08:20 UTC (10:20 local) — two hours late. + assertNotEquals( + Instant.parse("2026-06-11T08:20:00Z").toEpochMilliseconds(), + triggerAtMillis + ) + } +}