From 8004c34e445f076029d8ddfabf65fc4ecff5d943 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 23 May 2019 21:07:44 +0300 Subject: [PATCH] Reduce Duration API surface, add top-level docs - toDuration(unit) extension instead of constructors - toDouble/Long/Int(unit) function instead of inUnits() - remove static factory functions - remove MIN_VALUE/MAX_VALUE constants --- libraries/stdlib/src/kotlin/time/Clocks.kt | 4 +- libraries/stdlib/src/kotlin/time/Duration.kt | 139 ++++++++----------- libraries/stdlib/test/time/DurationTest.kt | 70 +++++----- 3 files changed, 89 insertions(+), 124 deletions(-) diff --git a/libraries/stdlib/src/kotlin/time/Clocks.kt b/libraries/stdlib/src/kotlin/time/Clocks.kt index 79bf4e97d88..5657f8a78a3 100644 --- a/libraries/stdlib/src/kotlin/time/Clocks.kt +++ b/libraries/stdlib/src/kotlin/time/Clocks.kt @@ -20,7 +20,7 @@ public abstract class AbstractLongClock(protected val unit: DurationUnit) : Cloc override fun mark(): ClockMark = object : ClockMark { val startedAt = read() - override fun elapsed(): Duration = Duration(read() - startedAt, this@AbstractLongClock.unit) + override fun elapsed(): Duration = (read() - startedAt).toDuration(this@AbstractLongClock.unit) } } @@ -32,7 +32,7 @@ public abstract class AbstractDoubleClock(protected val unit: DurationUnit) : Cl override fun mark(): ClockMark = object : ClockMark { val startedAt = read() - override fun elapsed(): Duration = Duration(read() - startedAt, this@AbstractDoubleClock.unit) + override fun elapsed(): Duration = (read() - startedAt).toDuration(this@AbstractDoubleClock.unit) } } diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index 228d79825af..07b2d5f843f 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -9,6 +9,20 @@ import kotlin.math.abs private val storageUnit = DurationUnit.NANOSECONDS +/** + * Represents the amount of time one instant of time is away from another instant. + * + * A negative duration is possible in a situation when the second instant is earlier than the first one. + * 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. + * + * 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. + */ @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") public inline class Duration internal constructor(internal val _value: Double) : Comparable { // TODO: backend fails on init block, wait for KT-28055 @@ -19,48 +33,9 @@ public inline class Duration internal constructor(internal val _value: Double) : companion object { val ZERO: Duration = Duration(0.0) - val MAX_VALUE: Duration = Duration(Double.MAX_VALUE) - val MIN_VALUE: Duration = Duration(-Double.MAX_VALUE) val INFINITE: Duration = Duration(Double.POSITIVE_INFINITY) - - - // constructing from number of units - // option 1: static-like constructor functions - - fun days(value: Int) = Duration(value, DurationUnit.DAYS) - fun days(value: Long) = Duration(value, DurationUnit.DAYS) - fun days(value: Double) = Duration(value, DurationUnit.DAYS) - fun hours(value: Int) = Duration(value, DurationUnit.HOURS) - fun hours(value: Long) = Duration(value, DurationUnit.HOURS) - fun hours(value: Double) = Duration(value, DurationUnit.HOURS) - fun minutes(value: Int) = Duration(value, DurationUnit.MINUTES) - fun minutes(value: Long) = Duration(value, DurationUnit.MINUTES) - fun minutes(value: Double) = Duration(value, DurationUnit.MINUTES) - fun seconds(value: Int) = Duration(value, DurationUnit.SECONDS) - fun seconds(value: Long) = Duration(value, DurationUnit.SECONDS) - fun seconds(value: Double) = Duration(value, DurationUnit.SECONDS) - fun milliseconds(value: Int) = Duration(value, DurationUnit.MILLISECONDS) - fun milliseconds(value: Long) = Duration(value, DurationUnit.MILLISECONDS) - fun milliseconds(value: Double) = Duration(value, DurationUnit.MILLISECONDS) - fun microseconds(value: Int) = Duration(value, DurationUnit.MICROSECONDS) - fun microseconds(value: Long) = Duration(value, DurationUnit.MICROSECONDS) - fun microseconds(value: Double) = Duration(value, DurationUnit.MICROSECONDS) - fun nanoseconds(value: Int) = Duration(value, DurationUnit.NANOSECONDS) - fun nanoseconds(value: Long) = Duration(value, DurationUnit.NANOSECONDS) - fun nanoseconds(value: Double) = Duration(value, DurationUnit.NANOSECONDS) } - - // constructing from number of units - // option 0: constructors - - // unit is lost after construction - - constructor(value: Int, unit: DurationUnit) : this(convertDurationUnit(value.toDouble(), unit, storageUnit)) - constructor(value: Long, unit: DurationUnit) : this(convertDurationUnit(value.toDouble(), unit, storageUnit)) - constructor(value: Double, unit: DurationUnit) : this(convertDurationUnit(value, unit, storageUnit)) - - // arithmetic operators operator fun unaryMinus(): Duration = Duration(-_value) @@ -111,29 +86,19 @@ public inline class Duration internal constructor(internal val _value: Double) : // conversion to units + fun toDouble(unit: DurationUnit): Double = convertDurationUnit(_value, storageUnit, unit) + fun toLong(unit: DurationUnit): Long = toDouble(unit).toLong() + fun toInt(unit: DurationUnit): Int = toDouble(unit).toInt() + // option 1: in- properties - val inDays: Double get() = inUnits(DurationUnit.DAYS) - val inHours: Double get() = inUnits(DurationUnit.HOURS) - val inMinutes: Double get() = inUnits(DurationUnit.MINUTES) - val inSeconds: Double get() = inUnits(DurationUnit.SECONDS) - val inMilliseconds: Double get() = inUnits(DurationUnit.MILLISECONDS) - val inMicroseconds: Double get() = inUnits(DurationUnit.MICROSECONDS) - val inNanoseconds: Double get() = inUnits(DurationUnit.NANOSECONDS) - - fun inUnits(unit: DurationUnit) = convertDurationUnit(_value, storageUnit, unit) - - // option 2: to- functions - - fun toDays(): Double = toUnits(DurationUnit.DAYS) - fun toHours(): Double = toUnits(DurationUnit.HOURS) - fun toMinutes(): Double = toUnits(DurationUnit.MINUTES) - fun toSeconds(): Double = toUnits(DurationUnit.SECONDS) - fun toMilliseconds(): Double = toUnits(DurationUnit.MILLISECONDS) - fun toMicroseconds(): Double = toUnits(DurationUnit.MICROSECONDS) - fun toNanoseconds(): Double = toUnits(DurationUnit.NANOSECONDS) - - fun toUnits(unit: DurationUnit): Double = convertDurationUnit(_value, storageUnit, unit) + val inDays: Double get() = toDouble(DurationUnit.DAYS) + val inHours: Double get() = toDouble(DurationUnit.HOURS) + val inMinutes: Double get() = toDouble(DurationUnit.MINUTES) + val inSeconds: Double get() = toDouble(DurationUnit.SECONDS) + val inMilliseconds: Double get() = toDouble(DurationUnit.MILLISECONDS) + val inMicroseconds: Double get() = toDouble(DurationUnit.MICROSECONDS) + val inNanoseconds: Double get() = toDouble(DurationUnit.NANOSECONDS) override fun toString(): String = buildString { @@ -156,7 +121,7 @@ public inline class Duration internal constructor(internal val _value: Double) : absNs < 86400e9 * 1e7 -> DurationUnit.DAYS else -> DurationUnit.DAYS.also { scientific = true } } - val value = inUnits(unit) + val value = toDouble(unit) return when { scientific -> formatScientific(value) maxDecimals > 0 -> formatUpToDecimals(value, maxDecimals) @@ -175,7 +140,7 @@ public inline class Duration internal constructor(internal val _value: Double) : fun toString(unit: DurationUnit, decimals: Int = 0): String { require(decimals >= 0) { "decimals must be not negative, but was $decimals" } if (isInfinite()) return _value.toString() - return formatToExactDecimals(inUnits(unit), decimals) + unit.shortName() + return formatToExactDecimals(toDouble(unit), decimals) + unit.shortName() } @@ -219,36 +184,42 @@ public inline class Duration internal constructor(internal val _value: Double) : } // constructing from number of units -// option 2: constructor extension functions +// extension functions +fun Int.toDuration(unit: DurationUnit): Duration = toDouble().toDuration(unit) +fun Long.toDuration(unit: DurationUnit): Duration = toDouble().toDuration(unit) +fun Double.toDuration(unit: DurationUnit): Duration = Duration(convertDurationUnit(this, unit, storageUnit)) -val Int.nanoseconds get() = Duration(this, DurationUnit.NANOSECONDS) -val Long.nanoseconds get() = Duration(this, DurationUnit.NANOSECONDS) -val Double.nanoseconds get() = Duration(this, DurationUnit.NANOSECONDS) +// constructing from number of units +// extension properties -val Int.microseconds get() = Duration(this, DurationUnit.MICROSECONDS) -val Long.microseconds get() = Duration(this, DurationUnit.MICROSECONDS) -val Double.microseconds get() = Duration(this, DurationUnit.MICROSECONDS) +val Int.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS) +val Long.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS) +val Double.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS) -val Int.milliseconds get() = Duration(this, DurationUnit.MILLISECONDS) -val Long.milliseconds get() = Duration(this, DurationUnit.MILLISECONDS) -val Double.milliseconds get() = Duration(this, DurationUnit.MILLISECONDS) +val Int.microseconds get() = toDuration(DurationUnit.MICROSECONDS) +val Long.microseconds get() = toDuration(DurationUnit.MICROSECONDS) +val Double.microseconds get() = toDuration(DurationUnit.MICROSECONDS) -val Int.seconds get() = Duration(this, DurationUnit.SECONDS) -val Long.seconds get() = Duration(this, DurationUnit.SECONDS) -val Double.seconds get() = Duration(this, DurationUnit.SECONDS) +val Int.milliseconds get() = toDuration(DurationUnit.MILLISECONDS) +val Long.milliseconds get() = toDuration(DurationUnit.MILLISECONDS) +val Double.milliseconds get() = toDuration(DurationUnit.MILLISECONDS) -val Int.minutes get() = Duration(this, DurationUnit.MINUTES) -val Long.minutes get() = Duration(this, DurationUnit.MINUTES) -val Double.minutes get() = Duration(this, DurationUnit.MINUTES) +val Int.seconds get() = toDuration(DurationUnit.SECONDS) +val Long.seconds get() = toDuration(DurationUnit.SECONDS) +val Double.seconds get() = toDuration(DurationUnit.SECONDS) -val Int.hours get() = Duration(this, DurationUnit.HOURS) -val Long.hours get() = Duration(this, DurationUnit.HOURS) -val Double.hours get() = Duration(this, DurationUnit.HOURS) +val Int.minutes get() = toDuration(DurationUnit.MINUTES) +val Long.minutes get() = toDuration(DurationUnit.MINUTES) +val Double.minutes get() = toDuration(DurationUnit.MINUTES) -val Int.days get() = Duration(this, DurationUnit.DAYS) -val Long.days get() = Duration(this, DurationUnit.DAYS) -val Double.days get() = Duration(this, DurationUnit.DAYS) +val Int.hours get() = toDuration(DurationUnit.HOURS) +val Long.hours get() = toDuration(DurationUnit.HOURS) +val Double.hours get() = toDuration(DurationUnit.HOURS) + +val Int.days get() = toDuration(DurationUnit.DAYS) +val Long.days get() = toDuration(DurationUnit.DAYS) +val Double.days get() = toDuration(DurationUnit.DAYS) diff --git a/libraries/stdlib/test/time/DurationTest.kt b/libraries/stdlib/test/time/DurationTest.kt index 21ee4e22fc6..243f2fb1f3c 100644 --- a/libraries/stdlib/test/time/DurationTest.kt +++ b/libraries/stdlib/test/time/DurationTest.kt @@ -25,13 +25,13 @@ class DurationTest { val value = Random.nextInt(1_000_000) val unit = units.random() val expected = convertDurationUnit(value.toDouble(), unit, expectStorageUnit) - assertEquals(expected, Duration(value, unit)._value) - assertEquals(expected, Duration(value.toLong(), unit)._value) - assertEquals(expected, Duration(value.toDouble(), unit)._value) + assertEquals(expected, value.toDuration(unit)._value) + assertEquals(expected, value.toLong().toDuration(unit)._value) + assertEquals(expected, value.toDouble().toDuration(unit)._value) } todo { - assertFails { Duration(Double.NaN, DurationUnit.SECONDS) } + assertFails { Double.NaN.toDuration(DurationUnit.SECONDS) } } } @@ -50,14 +50,14 @@ class DurationTest { for ((value, unit) in data) { repeat(10) { - val d1 = Duration(value, unit) + val d1 = value.toDuration(unit) val unit2 = units.random() val value2 = convertDurationUnit(value, unit, unit2) - val d2 = Duration(value2, unit2) + val d2 = value2.toDuration(unit2) assertEquals(d1, d2, "$value $unit in $unit2") assertEquals(d1.hashCode(), d2.hashCode()) - val d3 = Duration(value2 * 2, unit2) + val d3 = (value2 * 2).toDuration(unit2) assertNotEquals(d1, d3, "$value $unit in $unit2") } } @@ -70,33 +70,33 @@ class DurationTest { val n2 = Random.nextLong(Long.MAX_VALUE) val n3 = Random.nextDouble() - assertEquals(Duration(n1, DurationUnit.DAYS), n1.days) - assertEquals(Duration(n2, DurationUnit.DAYS), n2.days) - assertEquals(Duration(n3, DurationUnit.DAYS), n3.days) + assertEquals(n1.toDuration(DurationUnit.DAYS), n1.days) + assertEquals(n2.toDuration(DurationUnit.DAYS), n2.days) + assertEquals(n3.toDuration(DurationUnit.DAYS), n3.days) - assertEquals(Duration(n1, DurationUnit.HOURS), n1.hours) - assertEquals(Duration(n2, DurationUnit.HOURS), n2.hours) - assertEquals(Duration(n3, DurationUnit.HOURS), n3.hours) + assertEquals(n1.toDuration(DurationUnit.HOURS), n1.hours) + assertEquals(n2.toDuration(DurationUnit.HOURS), n2.hours) + assertEquals(n3.toDuration(DurationUnit.HOURS), n3.hours) - assertEquals(Duration(n1, DurationUnit.MINUTES), n1.minutes) - assertEquals(Duration(n2, DurationUnit.MINUTES), n2.minutes) - assertEquals(Duration(n3, DurationUnit.MINUTES), n3.minutes) + assertEquals(n1.toDuration(DurationUnit.MINUTES), n1.minutes) + assertEquals(n2.toDuration(DurationUnit.MINUTES), n2.minutes) + assertEquals(n3.toDuration(DurationUnit.MINUTES), n3.minutes) - assertEquals(Duration(n1, DurationUnit.SECONDS), n1.seconds) - assertEquals(Duration(n2, DurationUnit.SECONDS), n2.seconds) - assertEquals(Duration(n3, DurationUnit.SECONDS), n3.seconds) + assertEquals(n1.toDuration(DurationUnit.SECONDS), n1.seconds) + assertEquals(n2.toDuration(DurationUnit.SECONDS), n2.seconds) + assertEquals(n3.toDuration(DurationUnit.SECONDS), n3.seconds) - assertEquals(Duration(n1, DurationUnit.MILLISECONDS), n1.milliseconds) - assertEquals(Duration(n2, DurationUnit.MILLISECONDS), n2.milliseconds) - assertEquals(Duration(n3, DurationUnit.MILLISECONDS), n3.milliseconds) + assertEquals(n1.toDuration(DurationUnit.MILLISECONDS), n1.milliseconds) + assertEquals(n2.toDuration(DurationUnit.MILLISECONDS), n2.milliseconds) + assertEquals(n3.toDuration(DurationUnit.MILLISECONDS), n3.milliseconds) - assertEquals(Duration(n1, DurationUnit.MICROSECONDS), n1.microseconds) - assertEquals(Duration(n2, DurationUnit.MICROSECONDS), n2.microseconds) - assertEquals(Duration(n3, DurationUnit.MICROSECONDS), n3.microseconds) + assertEquals(n1.toDuration(DurationUnit.MICROSECONDS), n1.microseconds) + assertEquals(n2.toDuration(DurationUnit.MICROSECONDS), n2.microseconds) + assertEquals(n3.toDuration(DurationUnit.MICROSECONDS), n3.microseconds) - assertEquals(Duration(n1, DurationUnit.NANOSECONDS), n1.nanoseconds) - assertEquals(Duration(n2, DurationUnit.NANOSECONDS), n2.nanoseconds) - assertEquals(Duration(n3, DurationUnit.NANOSECONDS), n3.nanoseconds) + assertEquals(n1.toDuration(DurationUnit.NANOSECONDS), n1.nanoseconds) + assertEquals(n2.toDuration(DurationUnit.NANOSECONDS), n2.nanoseconds) + assertEquals(n3.toDuration(DurationUnit.NANOSECONDS), n3.nanoseconds) } @Test @@ -118,7 +118,7 @@ class DurationTest { val unit = units.random() val unit2 = units.random() - assertAlmostEquals(convertDurationUnit(value.toDouble(), unit, unit2), Duration(value, unit).inUnits(unit2)) + assertAlmostEquals(convertDurationUnit(value.toDouble(), unit, unit2), value.toDuration(unit).toDouble(unit2)) } } @@ -166,12 +166,6 @@ class DurationTest { assertTrue((-Duration.INFINITE).isInfinite()) assertTrue(Double.POSITIVE_INFINITY.nanoseconds.isInfinite()) - assertTrue(Duration.MAX_VALUE.isFinite()) - assertTrue(Duration.MIN_VALUE.isFinite()) - - assertFalse((Duration.MAX_VALUE * 2).isFinite()) - assertFalse((Duration.MIN_VALUE * 2).isFinite()) - // seconds converted to nanoseconds overflow to infinite assertTrue(Double.MAX_VALUE.seconds.isInfinite()) assertTrue((-Double.MAX_VALUE).seconds.isInfinite()) @@ -184,7 +178,7 @@ class DurationTest { val value = Random.nextLong() val unit = units.random() - assertEquals(Duration(-value, unit), -Duration(value, unit)) + assertEquals((-value).toDuration(unit), -value.toDuration(unit)) } } @@ -298,8 +292,8 @@ class DurationTest { d = (d - 516.nanoseconds) / 17 test(DurationUnit.NANOSECONDS, "0ns", "0.0ns", "0.02ns", "0.020ns", "0.0202ns") - d = Duration.MAX_VALUE testOnJvm { + d = Double.MAX_VALUE.nanoseconds // are such long representations ok? test(DurationUnit.DAYS, "20806633505350875${"0".repeat(278)}d") test(DurationUnit.NANOSECONDS, "17976931348623157${"0".repeat(292)}ns") @@ -375,7 +369,7 @@ class DurationTest { test(universeAge, "5.04e12d") test(planckTime, "5.4e-44s", "5.40e-44s") - test(Duration.MAX_VALUE, "2.08e294d") + test(Double.MAX_VALUE.nanoseconds, "2.08e294d") test(Duration.INFINITE, "Infinity") }