Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ package com.devil.phoenixproject.domain.model
expect fun currentTimeMillis(): Long

/**
* Get a monotonic elapsed time in milliseconds suitable for interval timing.
* Milliseconds since boot on a monotonic clock, including time spent in sleep.
*
* Matches Android `SystemClock.elapsedRealtime()`. Rest deadlines are armed on
* this clock, so both platforms must keep counting while the device is asleep.
*/
expect fun elapsedRealtimeMillis(): Long
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
package com.devil.phoenixproject.domain.model

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.convert
import platform.Foundation.NSDate
import platform.Foundation.NSProcessInfo
import platform.Foundation.timeIntervalSince1970
import platform.posix.CLOCK_MONOTONIC_RAW
import platform.posix.clock_gettime_nsec_np

actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong()

actual fun elapsedRealtimeMillis(): Long = (NSProcessInfo.processInfo.systemUptime * 1000).toLong()
/**
* Milliseconds from Darwin `CLOCK_MONOTONIC_RAW`, which follows `mach_continuous_time`
* and keeps counting through sleep. `NSProcessInfo.systemUptime` stops while the
* device is asleep, so rest armed on that clock under-counted on a locked phone.
*
* Darwin clock ids are not Linux ones. Apple Libc `gen/clock_gettime.c` reads
* `CLOCK_MONOTONIC_RAW` from `mach_continuous_time()` (counts while asleep) and
* `CLOCK_UPTIME_RAW` from `mach_absolute_time()` (stops while asleep).
*/
@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class)
actual fun elapsedRealtimeMillis(): Long =
(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW.convert()) / 1_000_000uL).toLong()
Comment thread
9thLevelSoftware marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.devil.phoenixproject.domain.model

import kotlin.test.Test
import kotlin.test.assertTrue

/**
* Smoke check only. The simulator cannot be put to sleep here, so nothing in this
* test tells the sleep-inclusive clock from the awake-only one; that rests on the
* clock id cited in `PlatformUtils.ios.kt`. These checks lock what is observable:
* a positive boot-relative reading that does not move backwards and is not
* wall-clock time.
*/
class ElapsedRealtimeIosTest {
@Test
fun elapsedRealtimeIsBootRelativeAndDoesNotGoBackwards() {
val first = elapsedRealtimeMillis()
val second = elapsedRealtimeMillis()

assertTrue(first > 0L, "elapsed realtime should be past boot, was $first")
assertTrue(second >= first, "elapsed realtime went backwards: $first then $second")
assertTrue(
second < WALL_CLOCK_MS,
"elapsed realtime looks like wall-clock time: $second",
)
}

private companion object {
/** Below any current Unix epoch in milliseconds (2001-09-09). */
private const val WALL_CLOCK_MS = 1_000_000_000_000L
}
}
Loading