diff --git a/core/common/src/Clock.kt b/core/common/src/Clock.kt index e9cd9137..3403af53 100644 --- a/core/common/src/Clock.kt +++ b/core/common/src/Clock.kt @@ -124,5 +124,20 @@ private class InstantTimeMark(private val instant: Instant, private val clock: C } } +/** + * Creates a [Clock] that uses the [time mark at the moment of creation][TimeMark.markNow] to determine how [far][TimeMark.elapsedNow] + * the [current moment][Clock.now] is from the [origin]. + * + * This clock stores the [TimeMark] at the moment of creation, so repeatedly creating [Clock]s from the same [TimeSource] results + * in different [Instant]s iff the time of the [TimeSource] was increased. To sync different [Clock]s, use the [origin] + * parameter. + * + * @sample kotlinx.datetime.test.samples.ClockSamples.timeSourceAsClock + */ +public fun TimeSource.asClock(origin: Instant): Clock = object : Clock { + private val startMark: TimeMark = markNow() + override fun now() = origin + startMark.elapsedNow() +} + @Deprecated("Use Clock.todayIn instead", ReplaceWith("this.todayIn(timeZone)"), DeprecationLevel.WARNING) public fun Clock.todayAt(timeZone: TimeZone): LocalDate = todayIn(timeZone) diff --git a/core/common/test/ClockTimeSourceTest.kt b/core/common/test/ClockTimeSourceTest.kt index 561cd222..571f6580 100644 --- a/core/common/test/ClockTimeSourceTest.kt +++ b/core/common/test/ClockTimeSourceTest.kt @@ -10,6 +10,7 @@ import kotlin.test.* import kotlin.time.* import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds @OptIn(ExperimentalTime::class) class ClockTimeSourceTest { @@ -83,4 +84,41 @@ class ClockTimeSourceTest { assertFailsWith { markFuture - Duration.INFINITE } assertFailsWith { markPast + Duration.INFINITE } } + + @Test + fun timeSourceAsClock() { + val timeSource = TestTimeSource() + val clock = timeSource.asClock(origin = Instant.fromEpochSeconds(0)) + + assertEquals(Instant.fromEpochSeconds(0), clock.now()) + assertEquals(Instant.fromEpochSeconds(0), clock.now()) + + timeSource += 1.seconds + assertEquals(Instant.fromEpochSeconds(1), clock.now()) + assertEquals(Instant.fromEpochSeconds(1), clock.now()) + } + + @Test + fun syncMultipleClocksFromTimeSource() { + val timeSource = TestTimeSource() + val clock1 = timeSource.asClock(origin = Instant.fromEpochSeconds(0)) + + assertEquals(0, clock1.now().epochSeconds) + + timeSource += 1.seconds + assertEquals(1, clock1.now().epochSeconds) + + val clock2 = timeSource.asClock(origin = Instant.fromEpochSeconds(1)) + assertEquals(clock1.now(), clock2.now()) + + timeSource += 1.seconds + assertEquals(2, clock1.now().epochSeconds) + assertEquals(clock1.now(), clock2.now()) + + val clock3 = timeSource.asClock(origin = clock2.now()) + timeSource += 1.seconds + assertEquals(3, clock3.now().epochSeconds) + assertEquals(clock1.now(), clock2.now()) + assertEquals(clock1.now(), clock3.now()) + } } diff --git a/core/common/test/samples/ClockSamples.kt b/core/common/test/samples/ClockSamples.kt index 83a3a156..70683efa 100644 --- a/core/common/test/samples/ClockSamples.kt +++ b/core/common/test/samples/ClockSamples.kt @@ -7,6 +7,8 @@ package kotlinx.datetime.test.samples import kotlinx.datetime.* import kotlin.test.* +import kotlin.time.Duration.Companion.seconds +import kotlin.time.TestTimeSource class ClockSamples { @Test @@ -45,4 +47,19 @@ class ClockSamples { check(clock.todayIn(TimeZone.UTC) == LocalDate(2020, 1, 1)) check(clock.todayIn(TimeZone.of("America/New_York")) == LocalDate(2019, 12, 31)) } + + @Test + fun timeSourceAsClock() { + // Creating a TimeSource + // When testing a Clock in combination of kotlinx-coroutines-test, use the testTimeSource of the TestDispatcher. + val timeSource = TestTimeSource() + // Creating a clock by setting the origin, here the epoch start + val clock = timeSource.asClock(origin = Instant.fromEpochSeconds(0)) + + check(Instant.fromEpochSeconds(0) == clock.now()) + + // Increasing the timeSource + timeSource += 1.seconds + check(Instant.fromEpochSeconds(1) == clock.now()) + } }