diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt index fd7929b87..8af545504 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt @@ -36,10 +36,15 @@ abstract class DateUtils { private var startDayHourOffset: Int = 0 private var startDayMinuteOffset: Int = 0 + /** + * Number of milliseconds in one second. + */ + const val SECOND_LENGTH: Long = 1000 + /** * Number of milliseconds in one minute. */ - const val MINUTE_LENGTH: Long = 60 * 1000 + const val MINUTE_LENGTH: Long = 60 * SECOND_LENGTH /** * Number of milliseconds in one hour. diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt index e0661ba77..903099293 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt @@ -33,31 +33,39 @@ open class MidnightTimer @Inject constructor() { private val listeners: MutableList = LinkedList() private lateinit var executor: ScheduledExecutorService - @Synchronized fun addListener(listener: MidnightListener) { + @Synchronized + fun addListener(listener: MidnightListener) { this.listeners.add(listener) } - @Synchronized fun onPause(): MutableList? = executor.shutdownNow() + @Synchronized + fun onPause(): MutableList? = executor.shutdownNow() - @Synchronized fun onResume() { - executor = Executors.newSingleThreadScheduledExecutor() + @Synchronized + fun onResume( + delayOffsetInMillis: Long = DateUtils.SECOND_LENGTH, + testExecutor: ScheduledExecutorService? = null + ) { + executor = testExecutor ?: Executors.newSingleThreadScheduledExecutor() executor.scheduleAtFixedRate( { notifyListeners() }, - DateUtils.millisecondsUntilTomorrowWithOffset() + 1000, + DateUtils.millisecondsUntilTomorrowWithOffset() + delayOffsetInMillis, DateUtils.DAY_LENGTH, TimeUnit.MILLISECONDS ) } - @Synchronized fun removeListener(listener: MidnightListener) = this.listeners.remove(listener) + @Synchronized + fun removeListener(listener: MidnightListener) = this.listeners.remove(listener) - @Synchronized private fun notifyListeners() { + @Synchronized + private fun notifyListeners() { for (l in listeners) { l.atMidnight() } } - interface MidnightListener { + fun interface MidnightListener { fun atMidnight() } } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt index f97991b6d..4079600ab 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt @@ -95,15 +95,13 @@ open class BaseUnitTest { } fun unixTime(year: Int, month: Int, day: Int): Long { - val cal = getStartOfTodayCalendar() - cal.set(year, month, day, 0, 0, 0) - return cal.timeInMillis + return unixTime(year, month, day, 0, 0) } - open fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int): Long { + open fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, milliseconds: Long = 0): Long { val cal = getStartOfTodayCalendar() cal.set(year, month, day, hour, minute) - return cal.timeInMillis + return cal.timeInMillis + milliseconds } fun timestamp(year: Int, month: Int, day: Int): Timestamp { diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt index d354e81f0..cf81fbe9a 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt @@ -138,7 +138,7 @@ class ReminderSchedulerTest : BaseUnitTest() { reminderScheduler.schedule(habit) } - override fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int): Long { + override fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, milliseconds: Long): Long { val cal: Calendar = getStartOfTodayCalendar() cal[year, month, day, hour] = minute return cal.timeInMillis diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt new file mode 100644 index 000000000..b92507da0 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/MidnightTimerTest.kt @@ -0,0 +1,48 @@ +package org.isoron.uhabits.core.utils + +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.isoron.uhabits.core.BaseUnitTest +import org.junit.Test +import java.util.Calendar +import java.util.TimeZone +import java.util.concurrent.Executors +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlin.test.assertEquals + +class MidnightTimerTest : BaseUnitTest() { + + @Test + fun testMidnightTimer_notifyListener_atMidnight() = runBlocking { + // Given + val executor = Executors.newSingleThreadScheduledExecutor() + val dispatcher = executor.asCoroutineDispatcher() + + withContext(dispatcher) { + DateUtils.setFixedTimeZone(TimeZone.getTimeZone("GMT")) + DateUtils.setFixedLocalTime( + unixTime( + 2017, + Calendar.JANUARY, + 1, + 23, + 59, + DateUtils.MINUTE_LENGTH - 1 + ) + ) + + val suspendedListener = suspendCoroutine { continuation -> + MidnightTimer().apply { + addListener { continuation.resume(true) } + // When + onResume(1, executor) + } + } + + // Then + assertEquals(true, suspendedListener) + } + } +}