Duration: reject NaN duration values (KT-44168)

This commit is contained in:
Ilya Gorbunov
2020-12-24 02:04:09 +03:00
parent 1d40ed39d0
commit ae6f10df3b
2 changed files with 96 additions and 25 deletions
+78 -21
View File
@@ -27,13 +27,10 @@ private inline val storageUnit get() = DurationUnit.NANOSECONDS
*/
@SinceKotlin("1.3")
@ExperimentalTime
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
public inline class Duration internal constructor(internal val value: Double) : Comparable<Duration> {
// TODO: backend fails on init block, wait for KT-28055
// init {
// require(_value.isNaN().not())
// }
init {
require(!value.isNaN()) { "Duration value cannot be NaN." }
}
companion object {
/** The duration equal to exactly 0 seconds. */
@@ -52,22 +49,46 @@ public inline class Duration internal constructor(internal val value: Double) :
/** 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. */
/**
* Returns a duration whose value is the sum of this and [other] duration values.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
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. */
/**
* Returns a duration whose value is the difference between this and [other] duration values.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
public operator fun minus(other: Duration): Duration = Duration(value - other.value)
/** Returns a duration whose value is this duration value multiplied by the given [scale] number. */
/**
* Returns a duration whose value is this duration value multiplied by the given [scale] number.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
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. */
/**
* Returns a duration whose value is this duration value multiplied by the given [scale] number.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
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. */
/**
* Returns a duration whose value is this duration value divided by the given [scale] number.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
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. */
/**
* Returns a duration whose value is this duration value divided by the given [scale] number.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
public operator fun div(scale: Double): Duration = Duration(value / scale)
/** Returns a number that is the ratio of this and [other] duration values. */
@@ -356,7 +377,11 @@ public fun Int.toDuration(unit: DurationUnit): Duration = toDouble().toDuration(
@ExperimentalTime
public fun Long.toDuration(unit: DurationUnit): Duration = toDouble().toDuration(unit)
/** Returns a [Duration] equal to this [Double] number of the specified [unit]. */
/**
* Returns a [Duration] equal to this [Double] number of the specified [unit].
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public fun Double.toDuration(unit: DurationUnit): Duration = Duration(convertDurationUnit(this, unit, storageUnit))
@@ -374,7 +399,11 @@ public val Int.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS)
@ExperimentalTime
public val Long.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS)
/** Returns a [Duration] equal to this [Double] number of nanoseconds. */
/**
* Returns a [Duration] equal to this [Double] number of nanoseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.nanoseconds get() = toDuration(DurationUnit.NANOSECONDS)
@@ -389,7 +418,11 @@ public val Int.microseconds get() = toDuration(DurationUnit.MICROSECONDS)
@ExperimentalTime
public val Long.microseconds get() = toDuration(DurationUnit.MICROSECONDS)
/** Returns a [Duration] equal to this [Double] number of microseconds. */
/**
* Returns a [Duration] equal to this [Double] number of microseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.microseconds get() = toDuration(DurationUnit.MICROSECONDS)
@@ -404,7 +437,11 @@ public val Int.milliseconds get() = toDuration(DurationUnit.MILLISECONDS)
@ExperimentalTime
public val Long.milliseconds get() = toDuration(DurationUnit.MILLISECONDS)
/** Returns a [Duration] equal to this [Double] number of milliseconds. */
/**
* Returns a [Duration] equal to this [Double] number of milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.milliseconds get() = toDuration(DurationUnit.MILLISECONDS)
@@ -419,7 +456,11 @@ public val Int.seconds get() = toDuration(DurationUnit.SECONDS)
@ExperimentalTime
public val Long.seconds get() = toDuration(DurationUnit.SECONDS)
/** Returns a [Duration] equal to this [Double] number of seconds. */
/**
* Returns a [Duration] equal to this [Double] number of seconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.seconds get() = toDuration(DurationUnit.SECONDS)
@@ -434,7 +475,11 @@ public val Int.minutes get() = toDuration(DurationUnit.MINUTES)
@ExperimentalTime
public val Long.minutes get() = toDuration(DurationUnit.MINUTES)
/** Returns a [Duration] equal to this [Double] number of minutes. */
/**
* Returns a [Duration] equal to this [Double] number of minutes.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.minutes get() = toDuration(DurationUnit.MINUTES)
@@ -449,7 +494,11 @@ public val Int.hours get() = toDuration(DurationUnit.HOURS)
@ExperimentalTime
public val Long.hours get() = toDuration(DurationUnit.HOURS)
/** Returns a [Duration] equal to this [Double] number of hours. */
/**
* Returns a [Duration] equal to this [Double] number of hours.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.hours get() = toDuration(DurationUnit.HOURS)
@@ -464,7 +513,11 @@ public val Int.days get() = toDuration(DurationUnit.DAYS)
@ExperimentalTime
public val Long.days get() = toDuration(DurationUnit.DAYS)
/** Returns a [Duration] equal to this [Double] number of days. */
/**
* Returns a [Duration] equal to this [Double] number of days.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
@SinceKotlin("1.3")
@ExperimentalTime
public val Double.days get() = toDuration(DurationUnit.DAYS)
@@ -476,7 +529,11 @@ public val Double.days get() = toDuration(DurationUnit.DAYS)
@kotlin.internal.InlineOnly
public inline operator fun Int.times(duration: Duration): Duration = duration * this
/** Returns a duration whose value is the specified [duration] value multiplied by this number. */
/**
* Returns a duration whose value is the specified [duration] value multiplied by this number.
*
* @throws IllegalArgumentException if the operation results in a `NaN` value.
*/
@SinceKotlin("1.3")
@ExperimentalTime
@kotlin.internal.InlineOnly
+18 -4
View File
@@ -34,9 +34,7 @@ class DurationTest {
assertEquals(expected, value.toDouble().toDuration(unit).value)
}
todo {
assertFails { Double.NaN.toDuration(DurationUnit.SECONDS) }
}
assertFailsWith<IllegalArgumentException> { Double.NaN.toDuration(DurationUnit.SECONDS) }
}
@Test
@@ -226,13 +224,16 @@ class DurationTest {
assertEquals(1.5.hours, 1.hours + 30.minutes)
assertEquals(0.5.days, 6.hours + 360.minutes)
assertEquals(0.5.seconds, 200.milliseconds + 300_000.microseconds)
assertFailsWith<IllegalArgumentException> { Duration.INFINITE + (-Duration.INFINITE) }
}
@Test
fun subtraction() {
assertEquals(10.hours, 0.5.days - 120.minutes)
assertEquals(850.milliseconds, 1.seconds - 150.milliseconds)
// TODO decide on INFINITE - INFINITE
assertFailsWith<IllegalArgumentException> { Duration.INFINITE - Duration.INFINITE }
}
@Test
@@ -244,6 +245,12 @@ class DurationTest {
assertEquals(1.days, 2 * 12.hours)
assertEquals(12.5.hours, 12.5 * 60.minutes)
assertEquals(1.microseconds, 50 * 20.nanoseconds)
assertEquals(Duration.ZERO, 0 * 1.hours)
assertEquals(Duration.ZERO, 1.seconds * 0.0)
assertFailsWith<IllegalArgumentException> { Duration.INFINITE * 0 }
assertFailsWith<IllegalArgumentException> { 0 * Duration.INFINITE }
}
@Test
@@ -251,6 +258,11 @@ class DurationTest {
assertEquals(12.hours, 1.days / 2)
assertEquals(60.minutes, 1.days / 24.0)
assertEquals(20.seconds, 2.minutes / 6)
assertEquals(Duration.INFINITE, 1.seconds / 0.0)
assertEquals(-Duration.INFINITE, -1.seconds / 0.0)
assertFailsWith<IllegalArgumentException> { Duration.INFINITE / Double.POSITIVE_INFINITY }
assertFailsWith<IllegalArgumentException> { Duration.ZERO / 0 }
}
@Test
@@ -258,6 +270,8 @@ class DurationTest {
assertEquals(24.0, 1.days / 1.hours)
assertEquals(0.1, 9.minutes / 1.5.hours)
assertEquals(50.0, 1.microseconds / 20.nanoseconds)
assertTrue((Duration.INFINITE / Duration.INFINITE).isNaN())
}
@Test