diff --git a/libraries/stdlib/samples/build.gradle b/libraries/stdlib/samples/build.gradle index df9fe8c78ba..0ce806d3947 100644 --- a/libraries/stdlib/samples/build.gradle +++ b/libraries/stdlib/samples/build.gradle @@ -13,7 +13,9 @@ compileTestKotlin { kotlinOptions { jdkHome = JDK_18 freeCompilerArgs = [ - "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes" + "-Xuse-experimental=kotlin.ExperimentalStdlibApi", + "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes", + "-Xuse-experimental=kotlin.time.ExperimentalTime" ] } } diff --git a/libraries/stdlib/samples/test/samples/time/durations.kt b/libraries/stdlib/samples/test/samples/time/durations.kt new file mode 100644 index 00000000000..e78ec8f4ce2 --- /dev/null +++ b/libraries/stdlib/samples/test/samples/time/durations.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package samples.time + +import samples.* + +import kotlin.time.* + +class Durations { + + @Sample + fun toIsoString() { + assertPrints(25.nanoseconds.toIsoString(), "PT0.000000025S") + assertPrints(120.3.milliseconds.toIsoString(), "PT0.120300S") + assertPrints(30.5.seconds.toIsoString(), "PT30.500S") + assertPrints(30.5.minutes.toIsoString(), "PT30M30S") + assertPrints(86420.seconds.toIsoString(), "PT24H0M20S") + assertPrints(2.days.toIsoString(), "PT48H") + assertPrints(Duration.ZERO.toIsoString(), "PT0S") + assertPrints(Duration.INFINITE.toIsoString(), "PT2147483647H") + } + + @Sample + fun toStringDefault() { + assertPrints(45.days, "45.0d") + assertPrints(1.5.days, "36.0h") + assertPrints(1230.minutes, "20.5h") + assertPrints(920.minutes, "920m") + assertPrints(1.546.seconds, "1.55s") + assertPrints(25.12.milliseconds, "25.1ms") + } + + @Sample + fun toStringDecimals() { + assertPrints(1230.minutes.toString(DurationUnit.DAYS, 2), "0.85d") + assertPrints(1230.minutes.toString(DurationUnit.HOURS, 2), "20.50h") + assertPrints(1230.minutes.toString(DurationUnit.MINUTES), "1230m") + assertPrints(1230.minutes.toString(DurationUnit.SECONDS), "73800s") + } + + + +} \ No newline at end of file diff --git a/libraries/stdlib/src/Module.md b/libraries/stdlib/src/Module.md index 27a321ba15c..d4ee3484be4 100644 --- a/libraries/stdlib/src/Module.md +++ b/libraries/stdlib/src/Module.md @@ -145,6 +145,10 @@ Functions for writing test assertions. Functions for working with text and regular expressions. +# Package kotlin.time + +Experimental API for representing [Duration][kotlin.time.Duration] values and measuring time intervals. + # Package org.khronos.webgl Kotlin JavaScript wrappers for the WebGL API. diff --git a/libraries/stdlib/src/kotlin/time/Clock.kt b/libraries/stdlib/src/kotlin/time/Clock.kt index 5b68dbdf6d5..3dcca024b16 100644 --- a/libraries/stdlib/src/kotlin/time/Clock.kt +++ b/libraries/stdlib/src/kotlin/time/Clock.kt @@ -18,6 +18,9 @@ package kotlin.time public interface Clock { /** * Marks a time point on this clock. + * + * The returned [ClockMark] instance encapsulates captured time point and allows querying + * the duration of time interval [elapsed][ClockMark.elapsed] from that point. */ public fun mark(): ClockMark } diff --git a/libraries/stdlib/src/kotlin/time/Clocks.kt b/libraries/stdlib/src/kotlin/time/Clocks.kt index 4443ef27101..f9b06906935 100644 --- a/libraries/stdlib/src/kotlin/time/Clocks.kt +++ b/libraries/stdlib/src/kotlin/time/Clocks.kt @@ -8,7 +8,10 @@ package kotlin.time import kotlin.js.JsName /** - * The most precise clock available in the platform, whose readings increase monotonically over time. + * The most precise clock available in the platform. + * + * The clock returns its readings from a source of monotonic time when it is available in a target platform, + * and resorts to a non-monotonic time source otherwise. */ @SinceKotlin("1.3") @ExperimentalTime @@ -16,10 +19,16 @@ public expect object MonoClock : Clock /** * An abstract class used to implement clocks that return their readings as [Long] values in the specified [unit]. + * + * @property unit The unit in which this clock readings are expressed. */ @SinceKotlin("1.3") @ExperimentalTime public abstract class AbstractLongClock(protected val unit: DurationUnit) : Clock { + /** + * This protected method should be overridden to return the current reading of the clock expressed as a [Long] number + * in the unit specified by the [unit] property. + */ protected abstract fun read(): Long private class LongClockMark(val startedAt: Long, val clock: AbstractLongClock, val offset: Duration) : ClockMark() { @@ -32,10 +41,16 @@ public abstract class AbstractLongClock(protected val unit: DurationUnit) : Cloc /** * An abstract class used to implement clocks that return their readings as [Double] values in the specified [unit]. + * + * @property unit The unit in which this clock readings are expressed. */ @SinceKotlin("1.3") @ExperimentalTime public abstract class AbstractDoubleClock(protected val unit: DurationUnit) : Clock { + /** + * This protected method should be overridden to return the current reading of the clock expressed as a [Double] number + * in the unit specified by the [unit] property. + */ protected abstract fun read(): Double private class DoubleClockMark(val startedAt: Double, val clock: AbstractDoubleClock, val offset: Duration) : ClockMark() { @@ -47,7 +62,12 @@ public abstract class AbstractDoubleClock(protected val unit: DurationUnit) : Cl } /** - * A clock, whose readings can be preset and changed manually. It is useful as a predictable source of time in tests. + * A clock that has programmatically updatable readings. It is useful as a predictable source of time in tests. + * + * @param reading The initial value of the clock reading. + * @param unit The unit of time in which [reading] value is expressed. + * + * @property reading Gets or sets this clock's current reading value. */ @SinceKotlin("1.3") @ExperimentalTime diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index ae5fc427e1f..cdb85e35d79 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -17,12 +17,12 @@ private val storageUnit = DurationUnit.NANOSECONDS * An infinite duration value [Duration.INFINITE] can be used to represent infinite timeouts. * * To construct a duration use either the extension function [toDuration], - * or the extension properties [hours], [minutes], [seconds] and so on, - * available on [Int], [Long] and [Double] numeric types. + * or the extension properties [hours], [minutes], [seconds], and so on, + * available on [Int], [Long], and [Double] numeric types. * * To get the value of this duration expressed in a particular [duration units][DurationUnit] - * use the functions [toInt], [toLong] and [toDouble] - * or the properties [inHours], [inMinutes], [inSeconds], [inNanoseconds] and so on. + * use the functions [toInt], [toLong], and [toDouble] + * or the properties [inHours], [inMinutes], [inSeconds], [inNanoseconds], and so on. */ @SinceKotlin("1.3") @ExperimentalTime @@ -35,7 +35,10 @@ public inline class Duration internal constructor(internal val value: Double) : // } companion object { + /** The duration equal to exactly 0 seconds. */ public val ZERO: Duration = Duration(0.0) + + /** The duration whose value is positive infinity. It is useful for representing timeouts that should never expire. */ public val INFINITE: Duration = Duration(Double.POSITIVE_INFINITY) /** Converts the given time duration [value] expressed in the specified [sourceUnit] into the specified [targetUnit]. */ @@ -45,41 +48,100 @@ public inline class Duration internal constructor(internal val value: Double) : // arithmetic operators + /** Returns the negative of this value. */ public operator fun unaryMinus(): Duration = Duration(-value) + + /** Returns a duration whose value is the sum of this and [other] duration values. */ public operator fun plus(other: Duration): Duration = Duration(value + other.value) + + /** Returns a duration whose value is the difference between this and [other] duration values. */ public operator fun minus(other: Duration): Duration = Duration(value - other.value) // should we declare symmetric extension operators? + /** Returns a duration whose value is this duration value multiplied by the given [scale] number. */ public operator fun times(scale: Int): Duration = Duration(value * scale) + + /** Returns a duration whose value is this duration value multiplied by the given [scale] number. */ public operator fun times(scale: Double): Duration = Duration(value * scale) + /** Returns a duration whose value is this duration value divided by the given [scale] number. */ public operator fun div(scale: Int): Duration = Duration(value / scale) + + /** Returns a duration whose value is this duration value divided by the given [scale] number. */ public operator fun div(scale: Double): Duration = Duration(value / scale) + /** Returns a number that is the ratio of [this] and [other] duration values. */ public operator fun div(other: Duration): Double = this.value / other.value + /** Returns true, if the duration value is less than zero. */ public fun isNegative(): Boolean = value < 0 + + /** Returns true, if the duration value is infinite. */ public fun isInfinite(): Boolean = value.isInfinite() + + /** Returns true, if the duration value is finite. */ public fun isFinite(): Boolean = value.isFinite() + /** Returns the absolute value of this value. The returned value is always non-negative. */ public val absoluteValue: Duration get() = if (isNegative()) -this else this - override fun compareTo(other: Duration): Int = this.value.compareTo(other.value) // splitting to components + /** + * Splits this duration into days, hours, minutes, seconds, and nanoseconds and executes the given [action] with these components. + * The result of [action] is returned as the result of this function. + * + * - `nanoseconds` represents the whole number of nanoseconds in this duration, and its absolute value is less than 1_000_000_000; + * - `seconds` represents the whole number of seconds in this duration, and its absolute value is less than 60; + * - `minutes` represents the whole number of minutes in this duration, and its absolute value is less than 60; + * - `hours` represents the whole number of hours in this duration, and its absolute value is less than 24; + * - `days` represents the whole number of days in this duration. + * If the value doesn't fit in [Int] range, i.e. it's greater than [Int.MAX_VALUE] or less than [Int.MIN_VALUE], + * it is coerced into that range. + */ public inline fun toComponents(action: (days: Int, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T = action(inDays.toInt(), hoursComponent, minutesComponent, secondsComponent, nanosecondsComponent) + /** + * Splits this duration into hours, minutes, seconds, and nanoseconds and executes the given [action] with these components. + * The result of [action] is returned as the result of this function. + * + * - `nanoseconds` represents the whole number of nanoseconds in this duration, and its absolute value is less than 1_000_000_000; + * - `seconds` represents the whole number of seconds in this duration, and its absolute value is less than 60; + * - `minutes` represents the whole number of minutes in this duration, and its absolute value is less than 60; + * - `hours` represents the whole number of hours in this duration. + * If the value doesn't fit in [Int] range, i.e. it's greater than [Int.MAX_VALUE] or less than [Int.MIN_VALUE], + * it is coerced into that range. + */ public inline fun toComponents(action: (hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T = action(inHours.toInt(), minutesComponent, secondsComponent, nanosecondsComponent) + /** + * Splits this duration into minutes, seconds, and nanoseconds and executes the given [action] with these components. + * The result of [action] is returned as the result of this function. + * + * - `nanoseconds` represents the whole number of nanoseconds in this duration, and its absolute value is less than 1_000_000_000; + * - `seconds` represents the whole number of seconds in this duration, and its absolute value is less than 60; + * - `minutes` represents the whole number of minutes in this duration. + * If the value doesn't fit in [Int] range, i.e. it's greater than [Int.MAX_VALUE] or less than [Int.MIN_VALUE], + * it is coerced into that range. + */ public inline fun toComponents(action: (minutes: Int, seconds: Int, nanoseconds: Int) -> T): T = action(inMinutes.toInt(), secondsComponent, nanosecondsComponent) + /** + * Splits this duration into seconds, and nanoseconds and executes the given [action] with these components. + * The result of [action] is returned as the result of this function. + * + * - `nanoseconds` represents the whole number of nanoseconds in this duration, and its absolute value is less than 1_000_000_000; + * - `seconds` represents the whole number of seconds in this duration. + * If the value doesn't fit in [Long] range, i.e. it's greater than [Long.MAX_VALUE] or less than [Long.MIN_VALUE], + * it is coerced into that range. + */ public inline fun toComponents(action: (seconds: Long, nanoseconds: Int) -> T): T = action(inSeconds.toLong(), nanosecondsComponent) @@ -95,25 +157,77 @@ public inline class Duration internal constructor(internal val value: Double) : // conversion to units + /** Returns the value of this duration expressed as a [Double] number of the specified [unit]. */ public fun toDouble(unit: DurationUnit): Double = convertDurationUnit(value, storageUnit, unit) + + /** + * Returns the value of this duration expressed as a [Long] number of the specified [unit]. + * + * If the value doesn't fit in the range of [Long] type, it is coerced into that range, see the conversion [Double.toLong] for details. + */ public fun toLong(unit: DurationUnit): Long = toDouble(unit).toLong() + + /** + * Returns the value of this duration expressed as an [Int] number of the specified [unit]. + * + * If the value doesn't fit in the range of [Int] type, it is coerced into that range, see the conversion [Double.toInt] for details. + */ public fun toInt(unit: DurationUnit): Int = toDouble(unit).toInt() - // option 1: in- properties - + /** The value of this duration expressed as a [Double] number of days. */ public val inDays: Double get() = toDouble(DurationUnit.DAYS) + + /** The value of this duration expressed as a [Double] number of hours. */ public val inHours: Double get() = toDouble(DurationUnit.HOURS) + + /** The value of this duration expressed as a [Double] number of minutes. */ public val inMinutes: Double get() = toDouble(DurationUnit.MINUTES) + + /** The value of this duration expressed as a [Double] number of seconds. */ public val inSeconds: Double get() = toDouble(DurationUnit.SECONDS) + + /** The value of this duration expressed as a [Double] number of milliseconds. */ public val inMilliseconds: Double get() = toDouble(DurationUnit.MILLISECONDS) + + /** The value of this duration expressed as a [Double] number of microseconds. */ public val inMicroseconds: Double get() = toDouble(DurationUnit.MICROSECONDS) + + /** The value of this duration expressed as a [Double] number of nanoseconds. */ public val inNanoseconds: Double get() = toDouble(DurationUnit.NANOSECONDS) // shortcuts + /** + * Returns the value of this duration expressed as a [Long] number of nanoseconds. + * + * If the value doesn't fit in the range of [Long] type, it is coerced into that range, see the conversion [Double.toLong] for details. + * + * The range of durations that can be expressed as a `Long` number of nanoseconds is approximately ±292 years. + */ public fun toLongNanoseconds(): Long = toLong(DurationUnit.NANOSECONDS) + + /** + * Returns the value of this duration expressed as a [Long] number of milliseconds. + * + * The value is coerced to the range of [Long] type, if it doesn't fit in that range, see the conversion [Double.toLong] for details. + * + * The range of durations that can be expressed as a `Long` number of milliseconds is approximately ±292 million years. + */ public fun toLongMilliseconds(): Long = toLong(DurationUnit.MILLISECONDS) + /** + * Returns a string representation of this duration value expressed in the unit which yields the most compact and readable number value. + * + * Special cases: + * - zero duration is formatted as `"0s"` + * - the infinite duration is formatted as `"Infinity"` without unit + * - very small durations (less than 1e-15 s) are expressed in seconds and formatted in scientific notation + * - very big durations (more than 1e+7 days) are expressed in days and formatted in scientific notation + * + * @return the value of duration in the automatically determined unit followed by that unit abbreviated name: `d`, `h`, `m`, `s`, `ms`, `us`, or `ns`. + * + * @sample samples.time.Durations.toStringDefault + */ override fun toString(): String = when { isInfinite() -> value.toString() value == 0.0 -> "0s" @@ -149,6 +263,19 @@ public inline class Duration internal constructor(internal val value: Double) : else -> 0 } + /** + * Returns a string representation of this duration value expressed in the given [unit] + * and formatted with the specified [decimals] number of digits after decimal point. + * + * Special cases: + * - the infinite duration is formatted as `"Infinity"` without unit + * + * @return the value of duration in the specified [unit] followed by that unit abbreviated name: `d`, `h`, `m`, `s`, `ms`, `us`, or `ns`. + * + * @throws IllegalArgumentException if [decimals] is less than zero. + * + * @sample samples.time.Durations.toStringDecimals + */ public fun toString(unit: DurationUnit, decimals: Int = 0): String { require(decimals >= 0) { "decimals must be not negative, but was $decimals" } if (isInfinite()) return value.toString() @@ -156,6 +283,20 @@ public inline class Duration internal constructor(internal val value: Double) : } + /** + * Returns an ISO-8601 based string representation of this duration. + * + * The returned value is presented in the format `PThHmMs.fS`, where `h`, `m`, `s` are the integer components of this duration (see [toComponents]) + * and `f` is a fractional part of second. Depending on the roundness of the value the fractional part can be formatted with either + * 0, 3, 6, or 9 decimal digits. + * + * If the hours component absolute value of this duration is greater than [Int.MAX_VAlUE], it is replaced with [Int.MAX_VALUE], + * so the infinite duration is formatted as `"PT2147483647H". + * + * Negative durations are indicated with the sign `-` in the beginning of the returned string, for example, `"-PT5M30S"`. + * + * @sample samples.time.Durations.toIsoString + */ public fun toIsoString(): String = buildString { if (isNegative()) append('-') append("PT") @@ -190,14 +331,17 @@ public inline class Duration internal constructor(internal val value: Double) : // constructing from number of units // extension functions +/** Returns a [Duration] equal to this [Int] number of the specified [unit]. */ @SinceKotlin("1.3") @ExperimentalTime public fun Int.toDuration(unit: DurationUnit): Duration = toDouble().toDuration(unit) +/** Returns a [Duration] equal to this [Long] number of the specified [unit]. */ @SinceKotlin("1.3") @ExperimentalTime public fun Long.toDuration(unit: DurationUnit): Duration = toDouble().toDuration(unit) +/** Returns a [Duration] equal to this [Double] number of the specified [unit]. */ @SinceKotlin("1.3") @ExperimentalTime public fun Double.toDuration(unit: DurationUnit): Duration = Duration(convertDurationUnit(this, unit, storageUnit)) @@ -205,86 +349,107 @@ public fun Double.toDuration(unit: DurationUnit): Duration = Duration(convertDur // constructing from number of units // extension properties +/** Returns a [Duration] equal to this [Int] number of nanoseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS) +/** Returns a [Duration] equal to this [Long] number of nanoseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS) +/** Returns a [Duration] equal to this [Double] number of nanoseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS) +/** Returns a [Duration] equal to this [Int] number of microseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.microseconds get() = toDuration(DurationUnit.MICROSECONDS) +/** Returns a [Duration] equal to this [Long] number of microseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.microseconds get() = toDuration(DurationUnit.MICROSECONDS) +/** Returns a [Duration] equal to this [Double] number of microseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.microseconds get() = toDuration(DurationUnit.MICROSECONDS) +/** Returns a [Duration] equal to this [Int] number of milliseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.milliseconds get() = toDuration(DurationUnit.MILLISECONDS) +/** Returns a [Duration] equal to this [Long] number of milliseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.milliseconds get() = toDuration(DurationUnit.MILLISECONDS) +/** Returns a [Duration] equal to this [Double] number of milliseconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.milliseconds get() = toDuration(DurationUnit.MILLISECONDS) +/** Returns a [Duration] equal to this [Int] number of seconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.seconds get() = toDuration(DurationUnit.SECONDS) +/** Returns a [Duration] equal to this [Long] number of seconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.seconds get() = toDuration(DurationUnit.SECONDS) +/** Returns a [Duration] equal to this [Double] number of seconds. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.seconds get() = toDuration(DurationUnit.SECONDS) +/** Returns a [Duration] equal to this [Int] number of minutes. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.minutes get() = toDuration(DurationUnit.MINUTES) +/** Returns a [Duration] equal to this [Long] number of minutes. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.minutes get() = toDuration(DurationUnit.MINUTES) +/** Returns a [Duration] equal to this [Double] number of minutes. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.minutes get() = toDuration(DurationUnit.MINUTES) +/** Returns a [Duration] equal to this [Int] number of hours. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.hours get() = toDuration(DurationUnit.HOURS) +/** Returns a [Duration] equal to this [Long] number of hours. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.hours get() = toDuration(DurationUnit.HOURS) +/** Returns a [Duration] equal to this [Double] number of hours. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.hours get() = toDuration(DurationUnit.HOURS) +/** Returns a [Duration] equal to this [Int] number of days. */ @SinceKotlin("1.3") @ExperimentalTime public val Int.days get() = toDuration(DurationUnit.DAYS) +/** Returns a [Duration] equal to this [Long] number of days. */ @SinceKotlin("1.3") @ExperimentalTime public val Long.days get() = toDuration(DurationUnit.DAYS) +/** Returns a [Duration] equal to this [Double] number of days. */ @SinceKotlin("1.3") @ExperimentalTime public val Double.days get() = toDuration(DurationUnit.DAYS)