-
Notifications
You must be signed in to change notification settings - Fork 102
/
Clock.kt
143 lines (126 loc) · 6.12 KB
/
Clock.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
* Copyright 2019-2020 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/
package kotlinx.datetime
import kotlin.time.*
/**
* A source of [Instant] values.
*
* See [Clock.System][Clock.System] for the clock instance that queries the operating system.
*
* It is not recommended to use [Clock.System] directly in the implementation. Instead, you can pass a
* [Clock] explicitly to the necessary functions or classes.
* This way, tests can be written deterministically by providing custom [Clock] implementations
* to the system under test.
*/
public interface Clock {
/**
* Returns the [Instant] corresponding to the current time, according to this clock.
*
* Calling [now] later is not guaranteed to return a larger [Instant].
* In particular, for [Clock.System], the opposite is completely expected,
* and it must be taken into account.
* See the [System] documentation for details.
*
* Even though [Instant] is defined to be on the UTC-SLS time scale, which enforces a specific way of handling
* leap seconds, [now] is not guaranteed to handle leap seconds in any specific way.
*/
public fun now(): Instant
/**
* The [Clock] instance that queries the platform-specific system clock as its source of time knowledge.
*
* Successive calls to [now] will not necessarily return increasing [Instant] values, and when they do,
* these increases will not necessarily correspond to the elapsed time.
*
* For example, when using [Clock.System], the following could happen:
* - [now] returns `2023-01-02T22:35:01Z`.
* - The system queries the Internet and recognizes that its clock needs adjusting.
* - [now] returns `2023-01-02T22:32:05Z`.
*
* When you need predictable intervals between successive measurements, consider using [TimeSource.Monotonic].
*
* For improved testability, you should avoid using [Clock.System] directly in the implementation
* and pass a [Clock] explicitly instead. For example:
*
* @sample kotlinx.datetime.test.samples.ClockSamples.system
* @sample kotlinx.datetime.test.samples.ClockSamples.dependencyInjection
*/
public object System : Clock {
override fun now(): Instant = @Suppress("DEPRECATION_ERROR") Instant.now()
}
/** A companion object used purely for namespacing. */
public companion object {
}
}
/**
* Returns the current date at the given [time zone][timeZone], according to [this Clock][this].
*
* The time zone is important because the current date is not the same in all time zones at the same instant.
*
* @sample kotlinx.datetime.test.samples.ClockSamples.todayIn
*/
public fun Clock.todayIn(timeZone: TimeZone): LocalDate =
now().toLocalDateTime(timeZone).date
/**
* Returns a [TimeSource] that uses this [Clock] to mark a time instant and to find the amount of time elapsed since that mark.
*
* **Pitfall**: using this function with [Clock.System] is error-prone
* because [Clock.System] is not well suited for measuring time intervals.
* Please only use this conversion function on the [Clock] instances that are fully controlled programmatically.
*/
@ExperimentalTime
public fun Clock.asTimeSource(): TimeSource.WithComparableMarks = object : TimeSource.WithComparableMarks {
override fun markNow(): ComparableTimeMark = InstantTimeMark(now(), this@asTimeSource)
}
@ExperimentalTime
private class InstantTimeMark(private val instant: Instant, private val clock: Clock) : ComparableTimeMark {
override fun elapsedNow(): Duration = saturatingDiff(clock.now(), instant)
override fun plus(duration: Duration): ComparableTimeMark = InstantTimeMark(instant.saturatingAdd(duration), clock)
override fun minus(duration: Duration): ComparableTimeMark = InstantTimeMark(instant.saturatingAdd(-duration), clock)
override fun minus(other: ComparableTimeMark): Duration {
if (other !is InstantTimeMark || other.clock != this.clock) {
throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other")
}
return saturatingDiff(this.instant, other.instant)
}
override fun equals(other: Any?): Boolean {
return other is InstantTimeMark && this.clock == other.clock && this.instant == other.instant
}
override fun hashCode(): Int = instant.hashCode()
override fun toString(): String = "InstantTimeMark($instant, $clock)"
private fun Instant.isSaturated() = this == Instant.MAX || this == Instant.MIN
private fun Instant.saturatingAdd(duration: Duration): Instant {
if (isSaturated()) {
if (duration.isInfinite() && duration.isPositive() != this.isDistantFuture) {
throw IllegalArgumentException("Summing infinities of different signs")
}
return this
}
return this + duration
}
private fun saturatingDiff(instant1: Instant, instant2: Instant): Duration = when {
instant1 == instant2 ->
Duration.ZERO
instant1.isSaturated() || instant2.isSaturated() ->
(instant1 - instant2) * Double.POSITIVE_INFINITY
else ->
instant1 - instant2
}
}
/**
* 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)