Duration.toComponents: change the highest component type to Long

This commit is contained in:
Ilya Gorbunov
2021-08-21 06:26:31 +03:00
committed by Space
parent d306c0e9ac
commit fbc43cbebe
4 changed files with 98 additions and 63 deletions
+3 -3
View File
@@ -253,11 +253,11 @@ public final inline class Duration : kotlin.Comparable<kotlin.time.Duration> {
public final operator fun times(scale: kotlin.Int): kotlin.time.Duration
public final inline fun <T> toComponents(action: (days: kotlin.Int, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (days: kotlin.Long, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (hours: kotlin.Long, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (minutes: kotlin.Long, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (seconds: kotlin.Long, nanoseconds: kotlin.Int) -> T): T
+3 -3
View File
@@ -253,11 +253,11 @@ public final inline class Duration : kotlin.Comparable<kotlin.time.Duration> {
public final operator fun times(scale: kotlin.Int): kotlin.time.Duration
public final inline fun <T> toComponents(action: (days: kotlin.Int, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (days: kotlin.Long, hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (hours: kotlin.Int, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (hours: kotlin.Long, minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (minutes: kotlin.Int, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (minutes: kotlin.Long, seconds: kotlin.Int, nanoseconds: kotlin.Int) -> T): T
public final inline fun <T> toComponents(action: (seconds: kotlin.Long, nanoseconds: kotlin.Int) -> T): T
+52 -51
View File
@@ -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 <T> toComponents(action: (days: Int, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T {
public inline fun <T> 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 <T> toComponents(action: (hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T {
public inline fun <T> 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 <T> toComponents(action: (minutes: Int, seconds: Int, nanoseconds: Int) -> T): T {
public inline fun <T> 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 <T> 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
+40 -6
View File
@@ -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())