diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/time/MonotonicTimeSource.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/time/MonotonicTimeSource.kt index 2d70af79971..4ddaa8c1d00 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/time/MonotonicTimeSource.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/time/MonotonicTimeSource.kt @@ -10,7 +10,7 @@ import kotlin.time.TimeSource.Monotonic.ValueTimeMark @SinceKotlin("1.3") @ExperimentalTime -internal actual object MonotonicTimeSource : TimeSource { +internal actual object MonotonicTimeSource : TimeSource.WithComparableMarks { private val zero: Long = getTimeNanos() private fun read(): Long = getTimeNanos() - zero override fun toString(): String = "TimeSource(System.nanoTime())" @@ -19,6 +19,9 @@ internal actual object MonotonicTimeSource : TimeSource { actual fun elapsedFrom(timeMark: ValueTimeMark): Duration = saturatingDiff(read(), timeMark.reading) + actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration = + saturatingOriginsDiff(one.reading, another.reading) + actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = ValueTimeMark(saturatingAdd(timeMark.reading, duration)) } diff --git a/libraries/stdlib/api/js-v1/kotlin.time.kt b/libraries/stdlib/api/js-v1/kotlin.time.kt index f123054b38a..aa339585534 100644 --- a/libraries/stdlib/api/js-v1/kotlin.time.kt +++ b/libraries/stdlib/api/js-v1/kotlin.time.kt @@ -132,12 +132,6 @@ public inline fun measureTime(block: () -> kotlin.Unit): kotlin.time.Duration @kotlin.time.ExperimentalTime public inline fun measureTimedValue(block: () -> T): kotlin.time.TimedValue -@kotlin.time.ExperimentalTime -@kotlin.SinceKotlin(version = "1.3") -@kotlin.internal.InlineOnly -@kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Comparing one TimeMark to another is not a well defined operation because these time marks could have been obtained from the different time sources.") -public inline operator fun kotlin.time.TimeMark.compareTo(other: kotlin.time.TimeMark): kotlin.Int - @kotlin.SinceKotlin(version = "1.3") @kotlin.time.ExperimentalTime public inline fun kotlin.time.TimeSource.measureTime(block: () -> kotlin.Unit): kotlin.time.Duration @@ -154,12 +148,6 @@ public inline fun kotlin.time.TimeSource.measureTimedValue(block: () -> T): @kotlin.time.ExperimentalTime public inline fun kotlin.time.TimeSource.Monotonic.measureTimedValue(block: () -> T): kotlin.time.TimedValue -@kotlin.time.ExperimentalTime -@kotlin.SinceKotlin(version = "1.3") -@kotlin.internal.InlineOnly -@kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Subtracting one TimeMark from another is not a well defined operation because these time marks could have been obtained from the different time sources.") -public inline operator fun kotlin.time.TimeMark.minus(other: kotlin.time.TimeMark): kotlin.time.Duration - @kotlin.SinceKotlin(version = "1.6") @kotlin.WasExperimental(markerClass = {kotlin.time.ExperimentalTime::class}) @kotlin.internal.InlineOnly @@ -184,28 +172,44 @@ public fun kotlin.Long.toDuration(unit: kotlin.time.DurationUnit): kotlin.time.D @kotlin.SinceKotlin(version = "1.3") @kotlin.time.ExperimentalTime -public abstract class AbstractDoubleTimeSource : kotlin.time.TimeSource { +public abstract class AbstractDoubleTimeSource : kotlin.time.TimeSource.WithComparableMarks { public constructor AbstractDoubleTimeSource(unit: kotlin.time.DurationUnit) protected final val unit: kotlin.time.DurationUnit { get; } - public open override fun markNow(): kotlin.time.TimeMark + public open override fun markNow(): kotlin.time.ComparableTimeMark protected abstract fun read(): kotlin.Double } @kotlin.SinceKotlin(version = "1.3") @kotlin.time.ExperimentalTime -public abstract class AbstractLongTimeSource : kotlin.time.TimeSource { +public abstract class AbstractLongTimeSource : kotlin.time.TimeSource.WithComparableMarks { public constructor AbstractLongTimeSource(unit: kotlin.time.DurationUnit) protected final val unit: kotlin.time.DurationUnit { get; } - public open override fun markNow(): kotlin.time.TimeMark + public open override fun markNow(): kotlin.time.ComparableTimeMark protected abstract fun read(): kotlin.Long } +@kotlin.SinceKotlin(version = "1.8") +@kotlin.time.ExperimentalTime +public interface ComparableTimeMark : kotlin.time.TimeMark, kotlin.Comparable { + public open override operator fun compareTo(other: kotlin.time.ComparableTimeMark): kotlin.Int + + public abstract override operator fun equals(other: kotlin.Any?): kotlin.Boolean + + public abstract override fun hashCode(): kotlin.Int + + public abstract operator fun minus(other: kotlin.time.ComparableTimeMark): kotlin.time.Duration + + public open override operator fun minus(duration: kotlin.time.Duration): kotlin.time.ComparableTimeMark + + public abstract override operator fun plus(duration: kotlin.time.Duration): kotlin.time.ComparableTimeMark +} + @kotlin.SinceKotlin(version = "1.6") @kotlin.WasExperimental(markerClass = {kotlin.time.ExperimentalTime::class}) @kotlin.jvm.JvmInline @@ -587,7 +591,7 @@ public interface TimeSource { public companion object of TimeSource { } - public object Monotonic : kotlin.time.TimeSource { + public object Monotonic : kotlin.time.TimeSource.WithComparableMarks { public open override fun markNow(): kotlin.time.TimeSource.Monotonic.ValueTimeMark public open override fun toString(): kotlin.String @@ -595,7 +599,9 @@ public interface TimeSource { @kotlin.time.ExperimentalTime @kotlin.SinceKotlin(version = "1.7") @kotlin.jvm.JvmInline - public final inline class ValueTimeMark : kotlin.time.TimeMark { + public final inline class ValueTimeMark : kotlin.time.ComparableTimeMark { + public final operator fun compareTo(other: kotlin.time.TimeSource.Monotonic.ValueTimeMark): kotlin.Int + public open override fun elapsedNow(): kotlin.time.Duration public open override operator fun equals(other: kotlin.Any?): kotlin.Boolean @@ -606,13 +612,23 @@ public interface TimeSource { public open override fun hashCode(): kotlin.Int + public open override operator fun minus(other: kotlin.time.ComparableTimeMark): kotlin.time.Duration + public open override operator fun minus(duration: kotlin.time.Duration): kotlin.time.TimeSource.Monotonic.ValueTimeMark + public final operator fun minus(other: kotlin.time.TimeSource.Monotonic.ValueTimeMark): kotlin.time.Duration + public open override operator fun plus(duration: kotlin.time.Duration): kotlin.time.TimeSource.Monotonic.ValueTimeMark public open override fun toString(): kotlin.String } } + + @kotlin.SinceKotlin(version = "1.8") + @kotlin.time.ExperimentalTime + public interface WithComparableMarks : kotlin.time.TimeSource { + public abstract override fun markNow(): kotlin.time.ComparableTimeMark + } } @kotlin.SinceKotlin(version = "1.3") diff --git a/libraries/stdlib/api/js/kotlin.time.kt b/libraries/stdlib/api/js/kotlin.time.kt index f123054b38a..aa339585534 100644 --- a/libraries/stdlib/api/js/kotlin.time.kt +++ b/libraries/stdlib/api/js/kotlin.time.kt @@ -132,12 +132,6 @@ public inline fun measureTime(block: () -> kotlin.Unit): kotlin.time.Duration @kotlin.time.ExperimentalTime public inline fun measureTimedValue(block: () -> T): kotlin.time.TimedValue -@kotlin.time.ExperimentalTime -@kotlin.SinceKotlin(version = "1.3") -@kotlin.internal.InlineOnly -@kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Comparing one TimeMark to another is not a well defined operation because these time marks could have been obtained from the different time sources.") -public inline operator fun kotlin.time.TimeMark.compareTo(other: kotlin.time.TimeMark): kotlin.Int - @kotlin.SinceKotlin(version = "1.3") @kotlin.time.ExperimentalTime public inline fun kotlin.time.TimeSource.measureTime(block: () -> kotlin.Unit): kotlin.time.Duration @@ -154,12 +148,6 @@ public inline fun kotlin.time.TimeSource.measureTimedValue(block: () -> T): @kotlin.time.ExperimentalTime public inline fun kotlin.time.TimeSource.Monotonic.measureTimedValue(block: () -> T): kotlin.time.TimedValue -@kotlin.time.ExperimentalTime -@kotlin.SinceKotlin(version = "1.3") -@kotlin.internal.InlineOnly -@kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "Subtracting one TimeMark from another is not a well defined operation because these time marks could have been obtained from the different time sources.") -public inline operator fun kotlin.time.TimeMark.minus(other: kotlin.time.TimeMark): kotlin.time.Duration - @kotlin.SinceKotlin(version = "1.6") @kotlin.WasExperimental(markerClass = {kotlin.time.ExperimentalTime::class}) @kotlin.internal.InlineOnly @@ -184,28 +172,44 @@ public fun kotlin.Long.toDuration(unit: kotlin.time.DurationUnit): kotlin.time.D @kotlin.SinceKotlin(version = "1.3") @kotlin.time.ExperimentalTime -public abstract class AbstractDoubleTimeSource : kotlin.time.TimeSource { +public abstract class AbstractDoubleTimeSource : kotlin.time.TimeSource.WithComparableMarks { public constructor AbstractDoubleTimeSource(unit: kotlin.time.DurationUnit) protected final val unit: kotlin.time.DurationUnit { get; } - public open override fun markNow(): kotlin.time.TimeMark + public open override fun markNow(): kotlin.time.ComparableTimeMark protected abstract fun read(): kotlin.Double } @kotlin.SinceKotlin(version = "1.3") @kotlin.time.ExperimentalTime -public abstract class AbstractLongTimeSource : kotlin.time.TimeSource { +public abstract class AbstractLongTimeSource : kotlin.time.TimeSource.WithComparableMarks { public constructor AbstractLongTimeSource(unit: kotlin.time.DurationUnit) protected final val unit: kotlin.time.DurationUnit { get; } - public open override fun markNow(): kotlin.time.TimeMark + public open override fun markNow(): kotlin.time.ComparableTimeMark protected abstract fun read(): kotlin.Long } +@kotlin.SinceKotlin(version = "1.8") +@kotlin.time.ExperimentalTime +public interface ComparableTimeMark : kotlin.time.TimeMark, kotlin.Comparable { + public open override operator fun compareTo(other: kotlin.time.ComparableTimeMark): kotlin.Int + + public abstract override operator fun equals(other: kotlin.Any?): kotlin.Boolean + + public abstract override fun hashCode(): kotlin.Int + + public abstract operator fun minus(other: kotlin.time.ComparableTimeMark): kotlin.time.Duration + + public open override operator fun minus(duration: kotlin.time.Duration): kotlin.time.ComparableTimeMark + + public abstract override operator fun plus(duration: kotlin.time.Duration): kotlin.time.ComparableTimeMark +} + @kotlin.SinceKotlin(version = "1.6") @kotlin.WasExperimental(markerClass = {kotlin.time.ExperimentalTime::class}) @kotlin.jvm.JvmInline @@ -587,7 +591,7 @@ public interface TimeSource { public companion object of TimeSource { } - public object Monotonic : kotlin.time.TimeSource { + public object Monotonic : kotlin.time.TimeSource.WithComparableMarks { public open override fun markNow(): kotlin.time.TimeSource.Monotonic.ValueTimeMark public open override fun toString(): kotlin.String @@ -595,7 +599,9 @@ public interface TimeSource { @kotlin.time.ExperimentalTime @kotlin.SinceKotlin(version = "1.7") @kotlin.jvm.JvmInline - public final inline class ValueTimeMark : kotlin.time.TimeMark { + public final inline class ValueTimeMark : kotlin.time.ComparableTimeMark { + public final operator fun compareTo(other: kotlin.time.TimeSource.Monotonic.ValueTimeMark): kotlin.Int + public open override fun elapsedNow(): kotlin.time.Duration public open override operator fun equals(other: kotlin.Any?): kotlin.Boolean @@ -606,13 +612,23 @@ public interface TimeSource { public open override fun hashCode(): kotlin.Int + public open override operator fun minus(other: kotlin.time.ComparableTimeMark): kotlin.time.Duration + public open override operator fun minus(duration: kotlin.time.Duration): kotlin.time.TimeSource.Monotonic.ValueTimeMark + public final operator fun minus(other: kotlin.time.TimeSource.Monotonic.ValueTimeMark): kotlin.time.Duration + public open override operator fun plus(duration: kotlin.time.Duration): kotlin.time.TimeSource.Monotonic.ValueTimeMark public open override fun toString(): kotlin.String } } + + @kotlin.SinceKotlin(version = "1.8") + @kotlin.time.ExperimentalTime + public interface WithComparableMarks : kotlin.time.TimeSource { + public abstract override fun markNow(): kotlin.time.ComparableTimeMark + } } @kotlin.SinceKotlin(version = "1.3") diff --git a/libraries/stdlib/js/src/kotlin/time/MonoTimeSource.kt b/libraries/stdlib/js/src/kotlin/time/MonoTimeSource.kt index 3554b046842..1d15e52cb06 100644 --- a/libraries/stdlib/js/src/kotlin/time/MonoTimeSource.kt +++ b/libraries/stdlib/js/src/kotlin/time/MonoTimeSource.kt @@ -15,15 +15,16 @@ import kotlin.time.TimeSource.Monotonic.ValueTimeMark internal actual typealias ValueTimeMarkReading = Any @ExperimentalTime -internal interface DefaultTimeSource : TimeSource { +internal interface DefaultTimeSource : TimeSource.WithComparableMarks { override fun markNow(): ValueTimeMark fun elapsedFrom(timeMark: ValueTimeMark): Duration + fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark } @SinceKotlin("1.3") @ExperimentalTime -internal actual object MonotonicTimeSource : DefaultTimeSource, TimeSource { // TODO: interface should not be required here +internal actual object MonotonicTimeSource : DefaultTimeSource, TimeSource.WithComparableMarks { // TODO: interface should not be required here private val actualSource: DefaultTimeSource = run { val isNode: Boolean = js("typeof process !== 'undefined' && process.versions && !!process.versions.node") @@ -40,6 +41,8 @@ internal actual object MonotonicTimeSource : DefaultTimeSource, TimeSource { // actual override fun markNow(): ValueTimeMark = actualSource.markNow() actual override fun elapsedFrom(timeMark: ValueTimeMark): Duration = actualSource.elapsedFrom(timeMark) + actual override fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration = actualSource.differenceBetween(one, another) + actual override fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = actualSource.adjustReading(timeMark, duration) } @@ -58,11 +61,19 @@ internal class HrTimeSource(private val process: Process) : DefaultTimeSource { process.hrtime(timeMark.reading as Array) .let { (seconds, nanos) -> seconds.toDuration(DurationUnit.SECONDS) + nanos.toDuration(DurationUnit.NANOSECONDS) } + @Suppress("UNCHECKED_CAST") + override fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration { + val (s1, n1) = one.reading as Array + val (s2, n2) = another.reading as Array + return (if (s1 == s2 && n1 == n2) Duration.ZERO else (s1 - s2).toDuration(DurationUnit.SECONDS)) + (n1 - n2).toDuration(DurationUnit.NANOSECONDS) + } + override fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = @Suppress("UNCHECKED_CAST") (timeMark.reading as Array).let { (seconds, nanos) -> duration.toComponents { _, addNanos -> - arrayOf(sumCheckNaN(seconds + truncate(duration.toDouble(DurationUnit.SECONDS))), nanos + addNanos) + val resultSeconds = sumCheckNaN(seconds + truncate(duration.toDouble(DurationUnit.SECONDS))) + arrayOf(resultSeconds, if (resultSeconds.isFinite()) nanos + addNanos else 0.0) } }.let(TimeSource.Monotonic::ValueTimeMark) @@ -78,6 +89,13 @@ internal class PerformanceTimeSource(val performance: Performance) : override fun markNow(): ValueTimeMark = ValueTimeMark(read()) override fun elapsedFrom(timeMark: ValueTimeMark): Duration = (read() - timeMark.reading as Double).milliseconds + + override fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration { + val ms1 = one.reading as Double + val ms2 = another.reading as Double + return if (ms1 == ms2) Duration.ZERO else (ms1 - ms2).milliseconds + } + override fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = ValueTimeMark(sumCheckNaN(timeMark.reading as Double + duration.toDouble(DurationUnit.MILLISECONDS))) @@ -91,6 +109,13 @@ internal object DateNowTimeSource : DefaultTimeSource { override fun markNow(): ValueTimeMark = ValueTimeMark(read()) override fun elapsedFrom(timeMark: ValueTimeMark): Duration = (read() - timeMark.reading as Double).milliseconds + + override fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration { + val ms1 = one.reading as Double + val ms2 = another.reading as Double + return if (ms1 == ms2) Duration.ZERO else (ms1 - ms2).milliseconds + } + override fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = ValueTimeMark(sumCheckNaN(timeMark.reading as Double + duration.toDouble(DurationUnit.MILLISECONDS))) diff --git a/libraries/stdlib/jvm/src/kotlin/time/MonoTimeSource.kt b/libraries/stdlib/jvm/src/kotlin/time/MonoTimeSource.kt index 8089b539349..82fd259f7e8 100644 --- a/libraries/stdlib/jvm/src/kotlin/time/MonoTimeSource.kt +++ b/libraries/stdlib/jvm/src/kotlin/time/MonoTimeSource.kt @@ -9,7 +9,7 @@ import kotlin.time.TimeSource.Monotonic.ValueTimeMark @SinceKotlin("1.3") @ExperimentalTime -internal actual object MonotonicTimeSource : TimeSource { +internal actual object MonotonicTimeSource : TimeSource.WithComparableMarks { private val zero: Long = System.nanoTime() private fun read(): Long = System.nanoTime() - zero override fun toString(): String = "TimeSource(System.nanoTime())" @@ -18,6 +18,9 @@ internal actual object MonotonicTimeSource : TimeSource { actual fun elapsedFrom(timeMark: ValueTimeMark): Duration = saturatingDiff(read(), timeMark.reading) + actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration = + saturatingOriginsDiff(one.reading, another.reading) + actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = ValueTimeMark(saturatingAdd(timeMark.reading, duration)) } diff --git a/libraries/stdlib/src/kotlin/time/TimeSource.kt b/libraries/stdlib/src/kotlin/time/TimeSource.kt index 412c228c075..7d9612473b9 100644 --- a/libraries/stdlib/src/kotlin/time/TimeSource.kt +++ b/libraries/stdlib/src/kotlin/time/TimeSource.kt @@ -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 { + 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 diff --git a/libraries/stdlib/src/kotlin/time/TimeSources.kt b/libraries/stdlib/src/kotlin/time/TimeSources.kt index a70889d2f80..08c6376bc26 100644 --- a/libraries/stdlib/src/kotlin/time/TimeSources.kt +++ b/libraries/stdlib/src/kotlin/time/TimeSources.kt @@ -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.") } } diff --git a/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt b/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt index c04782d8e9d..15033c4099a 100644 --- a/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt +++ b/libraries/stdlib/src/kotlin/time/longSaturatedMath.kt @@ -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 diff --git a/libraries/stdlib/test/time/TimeMarkTest.kt b/libraries/stdlib/test/time/TimeMarkTest.kt index 197e5cc5a8c..3f8e61d5f4a 100644 --- a/libraries/stdlib/test/time/TimeMarkTest.kt +++ b/libraries/stdlib/test/time/TimeMarkTest.kt @@ -12,6 +12,7 @@ import kotlin.time.Duration.Companion.microseconds import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.nanoseconds +@OptIn(ExperimentalTime::class) class TimeMarkTest { @Test @@ -32,12 +33,19 @@ class TimeMarkTest { val mark = timeSource.markNow() val markFuture1 = (mark + 1.milliseconds).apply { assertHasPassed(false) } val markFuture2 = (mark - (-1).milliseconds).apply { assertHasPassed(false) } + assertTrue(markFuture1 > mark) + assertTrue(markFuture2 > mark) val markPast1 = (mark - 1.milliseconds).apply { assertHasPassed(true) } val markPast2 = (markFuture1 + (-2).milliseconds).apply { assertHasPassed(true) } + assertTrue(markPast1 < mark) + assertTrue(markPast2 < mark) timeSource += 500_000.nanoseconds + val markElapsed = timeSource.markNow() + val elapsedDiff = markElapsed - mark + val elapsed = mark.elapsedNow() val elapsedFromFuture = elapsed - 1.milliseconds val elapsedFromPast = elapsed + 1.milliseconds @@ -45,6 +53,10 @@ class TimeMarkTest { assertEquals(0.5.milliseconds, elapsed) assertEquals(elapsedFromFuture, markFuture1.elapsedNow()) assertEquals(elapsedFromFuture, markFuture2.elapsedNow()) + assertEquals(elapsedDiff, elapsed) + + val markToElapsed = mark + elapsedDiff + assertEqualMarks(markElapsed, markToElapsed) assertEquals(elapsedFromPast, markPast1.elapsedNow()) assertEquals(elapsedFromPast, markPast2.elapsedNow()) @@ -59,11 +71,21 @@ class TimeMarkTest { } - fun testAdjustmentInfinite(timeSource: TimeSource) { + fun testAdjustmentInfinite(timeSource: TimeSource.WithComparableMarks) { val baseMark = timeSource.markNow() val infiniteFutureMark = baseMark + Duration.INFINITE val infinitePastMark = baseMark - Duration.INFINITE + assertTrue(infinitePastMark < baseMark) + assertTrue(baseMark < infiniteFutureMark) + assertTrue(infinitePastMark < infiniteFutureMark) + + assertEquals(Duration.INFINITE, infiniteFutureMark - infinitePastMark) + assertEquals(Duration.INFINITE, infiniteFutureMark - baseMark) + assertEquals(-Duration.INFINITE, infinitePastMark - baseMark) + assertEqualMarks(infiniteFutureMark, infiniteFutureMark) + assertEqualMarks(infinitePastMark, infinitePastMark) + assertEquals(-Duration.INFINITE, infiniteFutureMark.elapsedNow()) assertTrue(infiniteFutureMark.hasNotPassedNow()) @@ -81,9 +103,15 @@ class TimeMarkTest { val futureMark = pastMark + long2Duration val sameMark = futureMark - (long2Duration - longDuration) + val elapsedMark = timeSource.markNow() val elapsedDiff = (sameMark.elapsedNow() - baseMark.elapsedNow()).absoluteValue val elapsedDiff2 = (baseMark.elapsedNow() - sameMark.elapsedNow()).absoluteValue assertTrue(maxOf(elapsedDiff, elapsedDiff2) < 1.milliseconds, "$elapsedDiff, $elapsedDiff2") + // TODO: doesn't pass exactly for double-based value time marks in JS/WASM due to rounding +// assertEquals(elapsedMark - baseMark, elapsedMark - sameMark, "$elapsedMark; $baseMark; $sameMark") + val elapsedBaseDiff = elapsedMark - baseMark + val elapsedSameDiff = elapsedMark - sameMark + assertTrue((elapsedBaseDiff - elapsedSameDiff).absoluteValue < 1.milliseconds, "elapsedMark=$elapsedMark; baseMark=$baseMark; sameMark=$sameMark") } @Test @@ -91,15 +119,19 @@ class TimeMarkTest { testAdjustmentInfinite(TestTimeSource()) } - fun testLongDisplacement(timeSource: TimeSource, wait: (Duration) -> Unit) { + fun testLongDisplacement(timeSource: TimeSource.WithComparableMarks, wait: (Duration) -> Unit) { val baseMark = timeSource.markNow() val longDuration = Long.MAX_VALUE.nanoseconds val waitDuration = 20.milliseconds val pastMark = baseMark - longDuration wait(waitDuration) + val elapsedMark = timeSource.markNow() val elapsed = pastMark.elapsedNow() + val elapsedDiff = elapsedMark - pastMark assertTrue(elapsed > longDuration) assertTrue(elapsed >= longDuration + waitDuration, "$elapsed, $longDuration, $waitDuration") + assertTrue(elapsedDiff >= longDuration + waitDuration) + assertTrue(elapsed >= elapsedDiff) } @Test @@ -108,6 +140,137 @@ class TimeMarkTest { testLongDisplacement(timeSource, { waitDuration -> timeSource += waitDuration }) } + private fun assertEqualMarks(mark1: ComparableTimeMark, mark2: ComparableTimeMark) { + assertEquals(Duration.ZERO, mark1 - mark2) + assertEquals(Duration.ZERO, mark2 - mark1) + assertEquals(0, mark1 compareTo mark2) + assertEquals(0, mark2 compareTo mark1) + assertEquals(mark1, mark2) + assertEquals(mark1.hashCode(), mark2.hashCode(), "hashCodes of: $mark1, $mark2") + } + + @Test + fun timeMarkDifferenceAndComparison() { + val timeSource = TestTimeSource() + val timeSource2 = TestTimeSource() + val baseMark = timeSource.markNow() + + var markBefore = baseMark + markBefore -= 100.microseconds + markBefore -= 100.microseconds + + val markAfter = baseMark + 100.microseconds + + assertEquals(300.microseconds,markAfter - markBefore) + assertTrue(markBefore < markAfter) + assertFalse(markBefore > markAfter) + assertEqualMarks(baseMark, baseMark) + + timeSource += 100.microseconds + val markElapsed = timeSource.markNow() + assertEqualMarks(markElapsed, markAfter) + + val differentSourceMark = TimeSource.Monotonic.markNow() + assertFailsWith { baseMark - differentSourceMark } + assertFailsWith { baseMark < differentSourceMark } + + val differentSourceMark2 = timeSource2.markNow() + assertFailsWith { baseMark - differentSourceMark2 } + assertFailsWith { baseMark < differentSourceMark2 } + } + + private class LongTimeSource(unit: DurationUnit) : AbstractLongTimeSource(unit) { + var reading: Long = 0L + override fun read(): Long = reading + } + + private class DoubleTimeSource(unit: DurationUnit) : AbstractDoubleTimeSource(unit) { + var reading: Double = 0.0 + override fun read(): Double = reading + } + + + @Test + fun longTimeMarkInfinities() { + val timeSource = LongTimeSource(unit = DurationUnit.MILLISECONDS).apply { reading = Long.MIN_VALUE + 1 } + + val mark1 = timeSource.markNow() + timeSource.reading = 0 + val mark2 = timeSource.markNow() - Duration.INFINITE + assertEquals(Duration.INFINITE, mark1.elapsedNow()) + assertEquals(Duration.INFINITE, mark2.elapsedNow()) + assertEqualMarks(mark1, mark2) + + val mark3 = mark1 + Duration.INFINITE + assertEquals(-Duration.INFINITE, mark3.elapsedNow(), "infinite offset should override distant past reading") + val mark4 = timeSource.markNow() + Duration.INFINITE + assertEquals(-Duration.INFINITE, mark4.elapsedNow()) + assertEqualMarks(mark3, mark4) // different readings, same infinite offset + } + + @Test + fun doubleTimeMarkInfiniteEqualHashCode() { + val timeSource = DoubleTimeSource(unit = DurationUnit.MILLISECONDS).apply { reading = -Double.MAX_VALUE } + + val mark1 = timeSource.markNow() + timeSource.reading = 0.0 + val mark2 = timeSource.markNow() - Duration.INFINITE + assertEquals(Duration.INFINITE, mark1.elapsedNow()) + assertEquals(Duration.INFINITE, mark2.elapsedNow()) + assertEqualMarks(mark1, mark2) + } + + @Test + fun longTimeMarkRoundingEqualHashCode() { + // TODO: small reading, small offset + run { + val step = Long.MAX_VALUE / 4 + val timeSource = LongTimeSource(DurationUnit.NANOSECONDS) + val mark0 = timeSource.markNow() + (step * 2).nanoseconds + timeSource.reading += step + val mark1 = timeSource.markNow() + step.nanoseconds + timeSource.reading += step + val mark2 = timeSource.markNow() + assertEqualMarks(mark1, mark2) + assertEqualMarks(mark0, mark2) +// assertEqualMarks(mark0, mark1) // doesn't pass + } + + + // TODO: small reading, large offset + + val unit = DurationUnit.MICROSECONDS + val baseReading = Long.MAX_VALUE - 1000 + val timeSource = LongTimeSource(unit).apply { reading = baseReading } + // large reading, small offset + val baseMark = timeSource.markNow() + for (delta in listOf((1..<500).random(), (500..<1000).random())) { + val deltaDuration = delta.toDuration(unit) + timeSource.reading = baseReading + delta + val mark1e = timeSource.markNow() + assertEquals(deltaDuration, mark1e - baseMark) + val mark1d = baseMark + deltaDuration + assertEqualMarks(mark1e, mark1d) + } + + // large reading, large offset + run { + val delta = 1000 + val deltaDuration = delta.toDuration(unit) + timeSource.reading = baseReading + 1000 + val offset = Long.MAX_VALUE.nanoseconds + val mark2 = timeSource.markNow() + assertEquals(deltaDuration, mark2 - baseMark) + val mark2e = mark2 + offset + val mark2d = baseMark + offset + deltaDuration + assertEqualMarks(mark2e, mark2d) + } + + // TODO: small offset, large offset + } + + + @Test fun defaultTimeMarkAdjustment() { val baseMark = TimeSource.Monotonic.markNow() @@ -120,11 +283,16 @@ class TimeMarkTest { MeasureTimeTest.longRunningCalc() + val elapsedMark = TimeSource.Monotonic.markNow() + val elapsedDiff = elapsedMark - baseMark + assertTrue(elapsedDiff > Duration.ZERO) + val elapsedAfter = markAfter.elapsedNow() val elapsedBase = baseMark.elapsedNow() val elapsedBefore = markBefore.elapsedNow() assertTrue(elapsedBefore >= elapsedBase + 200.microseconds) assertTrue(elapsedAfter <= elapsedBase - 100.microseconds) + assertTrue(elapsedBase >= elapsedDiff) } @Test @@ -155,5 +323,32 @@ class TimeMarkTest { val elapsedDiff = (sameMark.elapsedNow() - baseMark.elapsedNow()).absoluteValue val elapsedDiff2 = (baseMark.elapsedNow() - sameMark.elapsedNow()).absoluteValue assertTrue(maxOf(elapsedDiff, elapsedDiff2) < 1.milliseconds, "$elapsedDiff, $elapsedDiff2") + + val elapsedMark = TimeSource.Monotonic.markNow() + val elapsedBaseDiff = elapsedMark - baseMark + val elapsedSameDiff = elapsedMark - sameMark + assertTrue((elapsedBaseDiff - elapsedSameDiff).absoluteValue < 1.milliseconds, "elapsedMark=$elapsedMark; baseMark=$baseMark; sameMark=$sameMark") + } + + @Test + fun defaultTimeMarkDifferenceAndComparison() { + val baseMark = TimeSource.Monotonic.markNow() + + var markBefore = baseMark + markBefore -= 100.microseconds + markBefore -= 100.microseconds + + val markAfter = baseMark + 100.microseconds + + assertEquals(300.microseconds,markAfter - markBefore) + assertTrue(markBefore < markAfter) + assertFalse(markBefore > markAfter) + assertEquals(0,baseMark compareTo baseMark) + assertEquals(baseMark as Any, baseMark as Any) + assertEquals(baseMark.hashCode(), baseMark.hashCode()) + + val differentSourceMark = TestTimeSource().markNow() + assertFailsWith { baseMark - differentSourceMark } + assertFailsWith { baseMark < differentSourceMark } } } diff --git a/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt b/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt index 658629c5dbe..321f85cfc4b 100644 --- a/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt +++ b/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt @@ -20,7 +20,7 @@ private external fun dateNow(): Double @SinceKotlin("1.3") @ExperimentalTime -internal actual object MonotonicTimeSource : TimeSource { +internal actual object MonotonicTimeSource : TimeSource.WithComparableMarks { private val performance: ExternalInterfaceType? = tryGetPerformance() private fun read(): Double = @@ -31,6 +31,12 @@ internal actual object MonotonicTimeSource : TimeSource { actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = ValueTimeMark(sumCheckNaN(timeMark.reading as Double + duration.toDouble(DurationUnit.MILLISECONDS))) + actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration { + val ms1 = one.reading as Double + val ms2 = another.reading as Double + return if (ms1 == ms2) Duration.ZERO else (ms1 - ms2).milliseconds + } + override fun toString(): String = if (performance != null) "TimeSource(globalThis.performance.now())" else "TimeSource(Date.now())" } diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index d8a6e1da5fd..aac7d696597 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -5902,20 +5902,38 @@ public final class kotlin/text/jdk8/RegexExtensionsJDK8Kt { public static final fun get (Lkotlin/text/MatchGroupCollection;Ljava/lang/String;)Lkotlin/text/MatchGroup; } -public abstract class kotlin/time/AbstractDoubleTimeSource : kotlin/time/TimeSource { +public abstract class kotlin/time/AbstractDoubleTimeSource : kotlin/time/TimeSource$WithComparableMarks { public fun (Lkotlin/time/DurationUnit;)V protected final fun getUnit ()Lkotlin/time/DurationUnit; - public fun markNow ()Lkotlin/time/TimeMark; + public fun markNow ()Lkotlin/time/ComparableTimeMark; + public synthetic fun markNow ()Lkotlin/time/TimeMark; protected abstract fun read ()D } -public abstract class kotlin/time/AbstractLongTimeSource : kotlin/time/TimeSource { +public abstract class kotlin/time/AbstractLongTimeSource : kotlin/time/TimeSource$WithComparableMarks { public fun (Lkotlin/time/DurationUnit;)V protected final fun getUnit ()Lkotlin/time/DurationUnit; - public fun markNow ()Lkotlin/time/TimeMark; + public fun markNow ()Lkotlin/time/ComparableTimeMark; + public synthetic fun markNow ()Lkotlin/time/TimeMark; protected abstract fun read ()J } +public abstract interface class kotlin/time/ComparableTimeMark : java/lang/Comparable, kotlin/time/TimeMark { + public abstract fun compareTo (Lkotlin/time/ComparableTimeMark;)I + public abstract fun equals (Ljava/lang/Object;)Z + public abstract fun hashCode ()I + public abstract fun minus-LRDsOJo (J)Lkotlin/time/ComparableTimeMark; + public abstract fun minus-UwyO8pc (Lkotlin/time/ComparableTimeMark;)J + public abstract fun plus-LRDsOJo (J)Lkotlin/time/ComparableTimeMark; +} + +public final class kotlin/time/ComparableTimeMark$DefaultImpls { + public static fun compareTo (Lkotlin/time/ComparableTimeMark;Lkotlin/time/ComparableTimeMark;)I + public static fun hasNotPassedNow (Lkotlin/time/ComparableTimeMark;)Z + public static fun hasPassedNow (Lkotlin/time/ComparableTimeMark;)Z + public static fun minus-LRDsOJo (Lkotlin/time/ComparableTimeMark;J)Lkotlin/time/ComparableTimeMark; +} + public final class kotlin/time/Duration : java/lang/Comparable { public static final field Companion Lkotlin/time/Duration$Companion; public static final synthetic fun box-impl (J)Lkotlin/time/Duration; @@ -6086,15 +6104,20 @@ public abstract interface class kotlin/time/TimeSource { public final class kotlin/time/TimeSource$Companion { } -public final class kotlin/time/TimeSource$Monotonic : kotlin/time/TimeSource { +public final class kotlin/time/TimeSource$Monotonic : kotlin/time/TimeSource$WithComparableMarks { public static final field INSTANCE Lkotlin/time/TimeSource$Monotonic; + public synthetic fun markNow ()Lkotlin/time/ComparableTimeMark; public synthetic fun markNow ()Lkotlin/time/TimeMark; public fun markNow-z9LOYto ()J public fun toString ()Ljava/lang/String; } -public final class kotlin/time/TimeSource$Monotonic$ValueTimeMark : kotlin/time/TimeMark { +public final class kotlin/time/TimeSource$Monotonic$ValueTimeMark : kotlin/time/ComparableTimeMark { public static final synthetic fun box-impl (J)Lkotlin/time/TimeSource$Monotonic$ValueTimeMark; + public synthetic fun compareTo (Ljava/lang/Object;)I + public fun compareTo (Lkotlin/time/ComparableTimeMark;)I + public static final fun compareTo-6eNON_k (JJ)I + public static fun compareTo-impl (JLkotlin/time/ComparableTimeMark;)I public fun elapsedNow-UwyO8pc ()J public static fun elapsedNow-UwyO8pc (J)J public fun equals (Ljava/lang/Object;)Z @@ -6106,10 +6129,15 @@ public final class kotlin/time/TimeSource$Monotonic$ValueTimeMark : kotlin/time/ public static fun hasPassedNow-impl (J)Z public fun hashCode ()I public static fun hashCode-impl (J)I + public static final fun minus-6eNON_k (JJ)J public fun minus-LRDsOJo (J)J + public synthetic fun minus-LRDsOJo (J)Lkotlin/time/ComparableTimeMark; public synthetic fun minus-LRDsOJo (J)Lkotlin/time/TimeMark; public static fun minus-LRDsOJo (JJ)J + public static fun minus-UwyO8pc (JLkotlin/time/ComparableTimeMark;)J + public fun minus-UwyO8pc (Lkotlin/time/ComparableTimeMark;)J public fun plus-LRDsOJo (J)J + public synthetic fun plus-LRDsOJo (J)Lkotlin/time/ComparableTimeMark; public synthetic fun plus-LRDsOJo (J)Lkotlin/time/TimeMark; public static fun plus-LRDsOJo (JJ)J public fun toString ()Ljava/lang/String; @@ -6117,6 +6145,10 @@ public final class kotlin/time/TimeSource$Monotonic$ValueTimeMark : kotlin/time/ public final synthetic fun unbox-impl ()J } +public abstract interface class kotlin/time/TimeSource$WithComparableMarks : kotlin/time/TimeSource { + public abstract fun markNow ()Lkotlin/time/ComparableTimeMark; +} + public final class kotlin/time/TimedValue { public synthetic fun (Ljava/lang/Object;JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Object;