diff --git a/libraries/stdlib/api/js-v1/kotlin.time.kt b/libraries/stdlib/api/js-v1/kotlin.time.kt index b8a9a347f8c..b059d8e4cf9 100644 --- a/libraries/stdlib/api/js-v1/kotlin.time.kt +++ b/libraries/stdlib/api/js-v1/kotlin.time.kt @@ -253,11 +253,11 @@ public final inline class Duration : kotlin.Comparable { public final operator fun times(scale: kotlin.Int): kotlin.time.Duration - public final inline fun toComponents(action: (days: kotlin.Int, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T + public final inline fun toComponents(action: (days: kotlin.Long, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T - public final inline fun toComponents(action: (hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T + public final inline fun toComponents(action: (hours: kotlin.Long, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T - public final inline fun toComponents(action: (minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T + public final inline fun toComponents(action: (minutes: kotlin.Long, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T public final inline fun toComponents(action: (seconds: kotlin.Long, nanoseconds: kotlin.Int) -> T): T diff --git a/libraries/stdlib/api/js/kotlin.time.kt b/libraries/stdlib/api/js/kotlin.time.kt index b8a9a347f8c..b059d8e4cf9 100644 --- a/libraries/stdlib/api/js/kotlin.time.kt +++ b/libraries/stdlib/api/js/kotlin.time.kt @@ -253,11 +253,11 @@ public final inline class Duration : kotlin.Comparable { public final operator fun times(scale: kotlin.Int): kotlin.time.Duration - public final inline fun toComponents(action: (days: kotlin.Int, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T + public final inline fun toComponents(action: (days: kotlin.Long, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T - public final inline fun toComponents(action: (hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T + public final inline fun toComponents(action: (hours: kotlin.Long, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T - public final inline fun toComponents(action: (minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T + public final inline fun toComponents(action: (minutes: kotlin.Long, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T public final inline fun toComponents(action: (seconds: kotlin.Long, nanoseconds: kotlin.Int) -> T): T diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index c3ed666b766..3d3f97a9f03 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -444,12 +444,13 @@ public value class Duration internal constructor(private val rawValue: Long) : C * - `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. + * + * Infinite durations are represented as either [Long.MAX_VALUE] days, or [Long.MIN_VALUE] days (depending on the sign of infinity), + * and zeroes in the lower components. */ - public inline fun toComponents(action: (days: Int, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { + public inline fun toComponents(action: (days: Long, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } - return action(toInt(DurationUnit.DAYS), hoursComponent, minutesComponent, secondsComponent, nanosecondsComponent) + return action(inWholeDays, hoursComponent, minutesComponent, secondsComponent, nanosecondsComponent) } /** @@ -460,12 +461,13 @@ public value class Duration internal constructor(private val rawValue: Long) : C * - `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. + * + * Infinite durations are represented as either [Long.MAX_VALUE] hours, or [Long.MIN_VALUE] hours (depending on the sign of infinity), + * and zeroes in the lower components. */ - public inline fun toComponents(action: (hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { + public inline fun toComponents(action: (hours: Long, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } - return action(toInt(DurationUnit.HOURS), minutesComponent, secondsComponent, nanosecondsComponent) + return action(inWholeHours, minutesComponent, secondsComponent, nanosecondsComponent) } /** @@ -475,12 +477,13 @@ public value class Duration internal constructor(private val rawValue: Long) : C * - `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. + * + * Infinite durations are represented as either [Long.MAX_VALUE] minutes, or [Long.MIN_VALUE] minutes (depending on the sign of infinity), + * and zeroes in the lower components. */ - public inline fun toComponents(action: (minutes: Int, seconds: Int, nanoseconds: Int) -> T): T { + public inline fun toComponents(action: (minutes: Long, seconds: Int, nanoseconds: Int) -> T): T { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } - return action(toInt(DurationUnit.MINUTES), secondsComponent, nanosecondsComponent) + return action(inWholeMinutes, secondsComponent, nanosecondsComponent) } /** @@ -489,8 +492,9 @@ public value class Duration internal constructor(private val rawValue: Long) : C * * - `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. + * + * Infinite durations are represented as either [Long.MAX_VALUE] seconds, or [Long.MIN_VALUE] seconds (depending on the sign of infinity), + * and zero nanoseconds. */ public inline fun toComponents(action: (seconds: Long, nanoseconds: Int) -> T): T { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } @@ -728,41 +732,38 @@ public value class Duration internal constructor(private val rawValue: Long) : C val isNegative = isNegative() buildString { if (isNegative) append('-') - absoluteValue.run { - toComponents { _, hours, minutes, seconds, nanoseconds -> - val days = inWholeDays - val hasDays = days != 0L - val hasHours = hours != 0 - val hasMinutes = minutes != 0 - val hasSeconds = seconds != 0 || nanoseconds != 0 - var components = 0 - if (hasDays) { - append(days).append('d') - components++ - } - if (hasHours || (hasDays && (hasMinutes || hasSeconds))) { - if (components++ > 0) append(' ') - append(hours).append('h') - } - if (hasMinutes || (hasSeconds && (hasHours || hasDays))) { - if (components++ > 0) append(' ') - append(minutes).append('m') - } - if (hasSeconds) { - if (components++ > 0) append(' ') - when { - seconds != 0 || hasDays || hasHours || hasMinutes -> - appendFractional(seconds, nanoseconds, 9, "s", isoZeroes = false) - nanoseconds >= 1_000_000 -> - appendFractional(nanoseconds / 1_000_000, nanoseconds % 1_000_000, 6, "ms", isoZeroes = false) - nanoseconds >= 1_000 -> - appendFractional(nanoseconds / 1_000, nanoseconds % 1_000, 3, "us", isoZeroes = false) - else -> - append(nanoseconds).append("ns") - } - } - if (isNegative && components > 1) insert(1, '(').append(')') + absoluteValue.toComponents { days, hours, minutes, seconds, nanoseconds -> + val hasDays = days != 0L + val hasHours = hours != 0 + val hasMinutes = minutes != 0 + val hasSeconds = seconds != 0 || nanoseconds != 0 + var components = 0 + if (hasDays) { + append(days).append('d') + components++ } + if (hasHours || (hasDays && (hasMinutes || hasSeconds))) { + if (components++ > 0) append(' ') + append(hours).append('h') + } + if (hasMinutes || (hasSeconds && (hasHours || hasDays))) { + if (components++ > 0) append(' ') + append(minutes).append('m') + } + if (hasSeconds) { + if (components++ > 0) append(' ') + when { + seconds != 0 || hasDays || hasHours || hasMinutes -> + appendFractional(seconds, nanoseconds, 9, "s", isoZeroes = false) + nanoseconds >= 1_000_000 -> + appendFractional(nanoseconds / 1_000_000, nanoseconds % 1_000_000, 6, "ms", isoZeroes = false) + nanoseconds >= 1_000 -> + appendFractional(nanoseconds / 1_000, nanoseconds % 1_000, 3, "us", isoZeroes = false) + else -> + append(nanoseconds).append("ns") + } + } + if (isNegative && components > 1) insert(1, '(').append(')') } } } @@ -822,9 +823,9 @@ public value class Duration internal constructor(private val rawValue: Long) : C public fun toIsoString(): String = buildString { if (isNegative()) append('-') append("PT") - val absoluteValue = this@Duration.absoluteValue - absoluteValue.toComponents { _, minutes, seconds, nanoseconds -> - var hours = absoluteValue.inWholeHours + this@Duration.absoluteValue.toComponents { hours, minutes, seconds, nanoseconds -> + @Suppress("NAME_SHADOWING") + var hours = hours if (isInfinite()) { // use large enough value instead of Long.MAX_VALUE hours = 9_999_999_999_999 diff --git a/libraries/stdlib/test/time/DurationTest.kt b/libraries/stdlib/test/time/DurationTest.kt index bc6d4a49ade..6c72aeddf8c 100644 --- a/libraries/stdlib/test/time/DurationTest.kt +++ b/libraries/stdlib/test/time/DurationTest.kt @@ -210,33 +210,38 @@ class DurationTest { @Test fun componentsOfProperSum() { repeat(100) { - val d = Random.nextInt(365 * 50) // fits in Int seconds + val isNsRange = Random.nextBoolean() + val d = if (isNsRange) + Random.nextLong(365L * 146) + else + Random.nextLong(365L * 150, 365L * 146_000_000) val h = Random.nextInt(24) val m = Random.nextInt(60) val s = Random.nextInt(60) val ns = Random.nextInt(1e9.toInt()) + val expectedNs = if (isNsRange) ns else ns - (ns % NANOS_IN_MILLIS) (Duration.days(d) + Duration.hours(h) + Duration.minutes(m) + Duration.seconds(s) + Duration.nanoseconds(ns)).run { toComponents { seconds, nanoseconds -> - assertEquals(d.toLong() * 86400 + h * 3600 + m * 60 + s, seconds) - assertEquals(ns, nanoseconds) + assertEquals(d * 86400 + h * 3600 + m * 60 + s, seconds) + assertEquals(expectedNs, nanoseconds) } toComponents { minutes, seconds, nanoseconds -> assertEquals(d * 1440 + h * 60 + m, minutes) assertEquals(s, seconds) - assertEquals(ns, nanoseconds) + assertEquals(expectedNs, nanoseconds) } toComponents { hours, minutes, seconds, nanoseconds -> assertEquals(d * 24 + h, hours) assertEquals(m, minutes) assertEquals(s, seconds) - assertEquals(ns, nanoseconds) + assertEquals(expectedNs, nanoseconds) } toComponents { days, hours, minutes, seconds, nanoseconds -> assertEquals(d, days) assertEquals(h, hours) assertEquals(m, minutes) assertEquals(s, seconds) - assertEquals(ns, nanoseconds) + assertEquals(expectedNs, nanoseconds) } } } @@ -255,6 +260,35 @@ class DurationTest { } } + @Test + fun componentsOfInfinity() { + for (d in listOf(Duration.INFINITE, -Duration.INFINITE)) { + val expected = if (d.isPositive()) Long.MAX_VALUE else Long.MIN_VALUE + d.toComponents { seconds, nanoseconds -> + assertEquals(expected, seconds) + assertEquals(0, nanoseconds) + } + d.toComponents { minutes: Long, seconds: Int, nanoseconds: Int -> + assertEquals(expected, minutes) + assertEquals(0, seconds) + assertEquals(0, nanoseconds) + } + d.toComponents { hours, minutes, seconds, nanoseconds -> + assertEquals(expected, hours) + assertEquals(0, minutes) + assertEquals(0, seconds) + assertEquals(0, nanoseconds) + } + d.toComponents { days, hours, minutes, seconds, nanoseconds -> + assertEquals(expected, days) + assertEquals(0, hours) + assertEquals(0, minutes) + assertEquals(0, seconds) + assertEquals(0, nanoseconds) + } + } + } + @Test fun infinite() { assertTrue(Duration.INFINITE.isInfinite())