ComparableTimeMark and specialized TimeSource KT-54082
- Introduce ComparableTimeMark interface extending TimeMark, and TimeSource.WithComparableMarks - a time source that returns such time marks. - Implement ComparableTimeMark in ValueTimeMark and AbstractLong/DoubleTimeMark classes.
This commit is contained in:
@@ -26,6 +26,15 @@ public interface TimeSource {
|
||||
*/
|
||||
public fun markNow(): TimeMark
|
||||
|
||||
/**
|
||||
* A [TimeSource] that returns [time marks][ComparableTimeMark] that can be compared for difference with each other.
|
||||
*/
|
||||
@SinceKotlin("1.8")
|
||||
@ExperimentalTime
|
||||
public interface WithComparableMarks : TimeSource {
|
||||
override fun markNow(): ComparableTimeMark
|
||||
}
|
||||
|
||||
/**
|
||||
* The most precise time source available in the platform.
|
||||
*
|
||||
@@ -35,28 +44,60 @@ public interface TimeSource {
|
||||
* The function [markNow] of this time source returns the specialized [ValueTimeMark] that is an inline value class
|
||||
* wrapping a platform-dependent time reading value.
|
||||
*/
|
||||
public object Monotonic : TimeSource {
|
||||
public object Monotonic : TimeSource.WithComparableMarks {
|
||||
override fun markNow(): ValueTimeMark = MonotonicTimeSource.markNow()
|
||||
override fun toString(): String = MonotonicTimeSource.toString()
|
||||
|
||||
/**
|
||||
* A specialized [kotlin.time.TimeMark] returned by [TimeSource.Monotonic].
|
||||
* A specialized [kotlin.time.TimeMark] returned by [TimeSource.Monotonic] time source.
|
||||
*
|
||||
* This time mark is implemented as an inline value class wrapping a platform-dependent
|
||||
* time reading value of the default monotonic time source, thus allowing to avoid additional boxing
|
||||
* of that value.
|
||||
*
|
||||
* The operations [plus] and [minus] are also specialized to return [ValueTimeMark] type.
|
||||
*
|
||||
* This time mark implements [ComparableTimeMark] and therefore is comparable with other time marks
|
||||
* obtained from the same [TimeSource.Monotonic] time source.
|
||||
*/
|
||||
@ExperimentalTime
|
||||
@SinceKotlin("1.7")
|
||||
@JvmInline
|
||||
public value class ValueTimeMark internal constructor(internal val reading: ValueTimeMarkReading) : TimeMark {
|
||||
public value class ValueTimeMark internal constructor(internal val reading: ValueTimeMarkReading) : ComparableTimeMark {
|
||||
override fun elapsedNow(): Duration = MonotonicTimeSource.elapsedFrom(this)
|
||||
override fun plus(duration: Duration): ValueTimeMark = MonotonicTimeSource.adjustReading(this, duration)
|
||||
override fun minus(duration: Duration): ValueTimeMark = MonotonicTimeSource.adjustReading(this, -duration)
|
||||
override fun hasPassedNow(): Boolean = !elapsedNow().isNegative()
|
||||
override fun hasNotPassedNow(): Boolean = elapsedNow().isNegative()
|
||||
|
||||
override fun minus(other: ComparableTimeMark): Duration {
|
||||
if (other !is ValueTimeMark)
|
||||
throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other")
|
||||
return this.minus(other)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration elapsed between the [other] time mark obtained from the same [TimeSource.Monotonic] time source and `this` time mark.
|
||||
*
|
||||
* The returned duration can be infinite if the time marks are far away from each other and
|
||||
* the result doesn't fit into [Duration] type,
|
||||
* or if one time mark is infinitely distant, or if both `this` and [other] time marks
|
||||
* lie infinitely distant on the opposite sides of the time scale.
|
||||
*
|
||||
* Two infinitely distant time marks on the same side of the time scale are considered equal and
|
||||
* the duration between them is [Duration.ZERO].
|
||||
*/
|
||||
public operator fun minus(other: ValueTimeMark): Duration = MonotonicTimeSource.differenceBetween(this, other)
|
||||
|
||||
/**
|
||||
* Compares this time mark with the [other] time mark for order.
|
||||
*
|
||||
* - Returns zero if this time mark represents *the same moment* of time as the [other] time mark.
|
||||
* - Returns a negative number if this time mark is *earlier* than the [other] time mark.
|
||||
* - Returns a positive number if this time mark is *later* than the [other] time mark.
|
||||
*/
|
||||
public operator fun compareTo(other: ValueTimeMark): Int =
|
||||
(this - other).compareTo(Duration.ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +139,7 @@ public interface TimeMark {
|
||||
* @throws IllegalArgumentException an implementation may throw if a positive infinite duration is added to an infinitely distant past time mark or
|
||||
* a negative infinite duration is added to an infinitely distant future time mark.
|
||||
*/
|
||||
public open operator fun plus(duration: Duration): TimeMark = AdjustedTimeMark(this, duration)
|
||||
public operator fun plus(duration: Duration): TimeMark = AdjustedTimeMark(this, duration)
|
||||
|
||||
/**
|
||||
* Returns a time mark on the same time source that is behind this time mark by the specified [duration].
|
||||
@@ -131,26 +172,53 @@ public interface TimeMark {
|
||||
public fun hasNotPassedNow(): Boolean = elapsedNow().isNegative()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A [TimeMark] that can be compared for difference with other time marks obtained from the same [TimeSource.WithComparableMarks] time source.
|
||||
*/
|
||||
@SinceKotlin("1.8")
|
||||
@ExperimentalTime
|
||||
@SinceKotlin("1.3")
|
||||
@kotlin.internal.InlineOnly
|
||||
@Deprecated(
|
||||
"Subtracting one TimeMark from another is not a well defined operation because these time marks could have been obtained from the different time sources.",
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
public inline operator fun TimeMark.minus(other: TimeMark): Duration = throw Error("Operation is disallowed.")
|
||||
public interface ComparableTimeMark : TimeMark, Comparable<ComparableTimeMark> {
|
||||
public abstract override operator fun plus(duration: Duration): ComparableTimeMark
|
||||
public open override operator fun minus(duration: Duration): ComparableTimeMark = plus(-duration)
|
||||
|
||||
@ExperimentalTime
|
||||
@SinceKotlin("1.3")
|
||||
@kotlin.internal.InlineOnly
|
||||
@Deprecated(
|
||||
"Comparing one TimeMark to another is not a well defined operation because these time marks could have been obtained from the different time sources.",
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
public inline operator fun TimeMark.compareTo(other: TimeMark): Int = throw Error("Operation is disallowed.")
|
||||
/**
|
||||
* Returns the duration elapsed between the [other] time mark and `this` time mark.
|
||||
*
|
||||
* The returned duration can be infinite if the time marks are far away from each other and
|
||||
* the result doesn't fit into [Duration] type,
|
||||
* or if one time mark is infinitely distant, or if both `this` and [other] time marks
|
||||
* lie infinitely distant on the opposite sides of the time scale.
|
||||
*
|
||||
* Two infinitely distant time marks on the same side of the time scale are considered equal and
|
||||
* the duration between them is [Duration.ZERO].
|
||||
*
|
||||
* Note that the other time mark must be obtained from the same time source as this one.
|
||||
*
|
||||
* @throws IllegalArgumentException if time marks were obtained from different time sources.
|
||||
*/
|
||||
public operator fun minus(other: ComparableTimeMark): Duration
|
||||
|
||||
/**
|
||||
* Compares this time mark with the [other] time mark for order.
|
||||
*
|
||||
* - Returns zero if this time mark represents *the same moment* of time as the [other] time mark.
|
||||
* - Returns a negative number if this time mark is *earlier* than the [other] time mark.
|
||||
* - Returns a positive number if this time mark is *later* than the [other] time mark.
|
||||
*
|
||||
* Note that the other time mark must be obtained from the same time source as this one.
|
||||
*
|
||||
* @throws IllegalArgumentException if time marks were obtained from different time sources.
|
||||
*/
|
||||
public override operator fun compareTo(other: ComparableTimeMark): Int =
|
||||
(this - other).compareTo(Duration.ZERO)
|
||||
|
||||
/**
|
||||
* Returns `true` if two time marks from the same time source represent the same moment of time, and `false` otherwise,
|
||||
* including the situation when the time marks were obtained from different time sources.
|
||||
*/
|
||||
override fun equals(other: Any?): Boolean
|
||||
override fun hashCode(): Int
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalTime
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
|
||||
package kotlin.time
|
||||
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.nanoseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalTime
|
||||
internal expect object MonotonicTimeSource : TimeSource {
|
||||
internal expect object MonotonicTimeSource : TimeSource.WithComparableMarks {
|
||||
override fun markNow(): TimeSource.Monotonic.ValueTimeMark
|
||||
fun elapsedFrom(timeMark: TimeSource.Monotonic.ValueTimeMark): Duration
|
||||
fun differenceBetween(one: TimeSource.Monotonic.ValueTimeMark, another: TimeSource.Monotonic.ValueTimeMark): Duration
|
||||
fun adjustReading(timeMark: TimeSource.Monotonic.ValueTimeMark, duration: Duration): TimeSource.Monotonic.ValueTimeMark
|
||||
}
|
||||
|
||||
@@ -20,19 +25,62 @@ internal expect object MonotonicTimeSource : TimeSource {
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalTime
|
||||
public abstract class AbstractLongTimeSource(protected val unit: DurationUnit) : TimeSource {
|
||||
public abstract class AbstractLongTimeSource(protected val unit: DurationUnit) : TimeSource.WithComparableMarks {
|
||||
/**
|
||||
* This protected method should be overridden to return the current reading of the time source expressed as a [Long] number
|
||||
* in the unit specified by the [unit] property.
|
||||
*/
|
||||
protected abstract fun read(): Long
|
||||
|
||||
private class LongTimeMark(private val startedAt: Long, private val timeSource: AbstractLongTimeSource, private val offset: Duration) : TimeMark {
|
||||
override fun elapsedNow(): Duration = (timeSource.read() - startedAt).toDuration(timeSource.unit) - offset
|
||||
override fun plus(duration: Duration): TimeMark = LongTimeMark(startedAt, timeSource, offset + duration)
|
||||
private class LongTimeMark(private val startedAt: Long, private val timeSource: AbstractLongTimeSource, private val offset: Duration) : ComparableTimeMark {
|
||||
override fun elapsedNow(): Duration = if (offset.isInfinite()) -offset else (timeSource.read() - startedAt).toDuration(timeSource.unit) - offset
|
||||
override fun plus(duration: Duration): ComparableTimeMark = LongTimeMark(startedAt, timeSource, offset + duration)
|
||||
override fun minus(other: ComparableTimeMark): Duration {
|
||||
if (other !is LongTimeMark || this.timeSource != other.timeSource)
|
||||
throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other")
|
||||
|
||||
// val thisValue = this.effectiveDuration()
|
||||
// val otherValue = other.effectiveDuration()
|
||||
// if (thisValue == otherValue && thisValue.isInfinite()) return Duration.ZERO
|
||||
// return thisValue - otherValue
|
||||
if (this.offset == other.offset && this.offset.isInfinite()) return Duration.ZERO
|
||||
val offsetDiff = this.offset - other.offset
|
||||
val startedAtDiff = (this.startedAt - other.startedAt).toDuration(timeSource.unit)
|
||||
// println("$startedAtDiff, $offsetDiff")
|
||||
return if (startedAtDiff == -offsetDiff) Duration.ZERO else startedAtDiff + offsetDiff
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is LongTimeMark && this.timeSource == other.timeSource && (this - other) == Duration.ZERO
|
||||
|
||||
internal fun effectiveDuration(): Duration {
|
||||
if (offset.isInfinite()) return offset
|
||||
val unit = timeSource.unit
|
||||
if (unit >= DurationUnit.MILLISECONDS) {
|
||||
return startedAt.toDuration(unit) + offset
|
||||
}
|
||||
val scale = convertDurationUnit(1L, DurationUnit.MILLISECONDS, unit)
|
||||
val startedAtMillis = startedAt / scale
|
||||
val startedAtRem = startedAt % scale
|
||||
|
||||
return offset.toComponents { offsetSeconds, offsetNanoseconds ->
|
||||
val offsetMillis = offsetNanoseconds / NANOS_IN_MILLIS
|
||||
val offsetRemNanos = offsetNanoseconds % NANOS_IN_MILLIS
|
||||
|
||||
// add component-wise
|
||||
(startedAtRem.toDuration(unit) + offsetRemNanos.nanoseconds) +
|
||||
(startedAtMillis + offsetMillis).milliseconds +
|
||||
offsetSeconds.seconds
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = effectiveDuration().hashCode()
|
||||
|
||||
override fun toString(): String = "LongTimeMark($startedAt${timeSource.unit.shortName()} + $offset (=${effectiveDuration()}), $timeSource)"
|
||||
}
|
||||
|
||||
override fun markNow(): TimeMark = LongTimeMark(read(), this, Duration.ZERO)
|
||||
override fun markNow(): ComparableTimeMark = LongTimeMark(read(), this, Duration.ZERO)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,19 +90,39 @@ public abstract class AbstractLongTimeSource(protected val unit: DurationUnit) :
|
||||
*/
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalTime
|
||||
public abstract class AbstractDoubleTimeSource(protected val unit: DurationUnit) : TimeSource {
|
||||
public abstract class AbstractDoubleTimeSource(protected val unit: DurationUnit) : TimeSource.WithComparableMarks {
|
||||
/**
|
||||
* This protected method should be overridden to return the current reading of the time source expressed as a [Double] number
|
||||
* in the unit specified by the [unit] property.
|
||||
*/
|
||||
protected abstract fun read(): Double
|
||||
|
||||
private class DoubleTimeMark(private val startedAt: Double, private val timeSource: AbstractDoubleTimeSource, private val offset: Duration) : TimeMark {
|
||||
private class DoubleTimeMark(private val startedAt: Double, private val timeSource: AbstractDoubleTimeSource, private val offset: Duration) : ComparableTimeMark {
|
||||
override fun elapsedNow(): Duration = (timeSource.read() - startedAt).toDuration(timeSource.unit) - offset
|
||||
override fun plus(duration: Duration): TimeMark = DoubleTimeMark(startedAt, timeSource, offset + duration)
|
||||
override fun plus(duration: Duration): ComparableTimeMark = DoubleTimeMark(startedAt, timeSource, offset + duration)
|
||||
|
||||
override fun minus(other: ComparableTimeMark): Duration {
|
||||
if (other !is DoubleTimeMark || this.timeSource != other.timeSource)
|
||||
throw IllegalArgumentException("Subtracting or comparing time marks from different time sources is not possible: $this and $other")
|
||||
|
||||
if (this.offset == other.offset && this.offset.isInfinite()) return Duration.ZERO
|
||||
val offsetDiff = this.offset - other.offset
|
||||
val startedAtDiff = (this.startedAt - other.startedAt).toDuration(timeSource.unit)
|
||||
return if (startedAtDiff == -offsetDiff) Duration.ZERO else startedAtDiff + offsetDiff
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is DoubleTimeMark && this.timeSource == other.timeSource && (this - other) == Duration.ZERO
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return (startedAt.toDuration(timeSource.unit) + offset).hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String = "DoubleTimeMark($startedAt${timeSource.unit.shortName()} + $offset, $timeSource)"
|
||||
}
|
||||
|
||||
override fun markNow(): TimeMark = DoubleTimeMark(read(), this, Duration.ZERO)
|
||||
override fun markNow(): ComparableTimeMark = DoubleTimeMark(read(), this, Duration.ZERO)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,6 +172,6 @@ public class TestTimeSource : AbstractLongTimeSource(unit = DurationUnit.NANOSEC
|
||||
}
|
||||
|
||||
private fun overflow(duration: Duration) {
|
||||
throw IllegalStateException("TestTimeSource will overflow if its reading ${reading}ns is advanced by $duration.")
|
||||
throw IllegalStateException("TestTimeSource will overflow if its reading ${reading}${unit.shortName()} is advanced by $duration.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,25 @@ internal fun saturatingDiff(valueNs: Long, originNs: Long): Duration {
|
||||
if (originNs.isSaturated()) { // MIN_VALUE or MAX_VALUE
|
||||
return -(originNs.toDuration(DurationUnit.DAYS)) // saturate to infinity
|
||||
}
|
||||
val result = valueNs - originNs
|
||||
if ((result xor valueNs) and (result xor originNs).inv() < 0) {
|
||||
val resultMs = valueNs / NANOS_IN_MILLIS - originNs / NANOS_IN_MILLIS
|
||||
val resultNs = valueNs % NANOS_IN_MILLIS - originNs % NANOS_IN_MILLIS
|
||||
return saturatingFiniteDiff(valueNs, originNs)
|
||||
}
|
||||
|
||||
internal fun saturatingOriginsDiff(origin1Ns: Long, origin2Ns: Long): Duration {
|
||||
if (origin2Ns.isSaturated()) { // MIN_VALUE or MAX_VALUE
|
||||
if (origin1Ns == origin2Ns) return Duration.ZERO // saturated values of the same sign are considered equal
|
||||
return -(origin2Ns.toDuration(DurationUnit.DAYS)) // saturate to infinity
|
||||
}
|
||||
if (origin1Ns.isSaturated()) {
|
||||
return origin1Ns.toDuration(DurationUnit.DAYS)
|
||||
}
|
||||
return saturatingFiniteDiff(origin1Ns, origin2Ns)
|
||||
}
|
||||
|
||||
private fun saturatingFiniteDiff(value1Ns: Long, value2Ns: Long): Duration {
|
||||
val result = value1Ns - value2Ns
|
||||
if ((result xor value1Ns) and (result xor value2Ns).inv() < 0) {
|
||||
val resultMs = value1Ns / NANOS_IN_MILLIS - value2Ns / NANOS_IN_MILLIS
|
||||
val resultNs = value1Ns % NANOS_IN_MILLIS - value2Ns % NANOS_IN_MILLIS
|
||||
return resultMs.milliseconds + resultNs.nanoseconds
|
||||
}
|
||||
return result.nanoseconds
|
||||
|
||||
Reference in New Issue
Block a user