| /* |
| * Copyright 2010-2021 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. |
| */ |
| |
| @file:Suppress("INVISIBLE_MEMBER") |
| package test.time |
| |
| import test.numbers.assertAlmostEquals |
| import kotlin.math.nextDown |
| import kotlin.math.pow |
| import kotlin.test.* |
| import kotlin.time.* |
| import kotlin.random.* |
| import kotlin.time.Duration.Companion.days |
| import kotlin.time.Duration.Companion.hours |
| import kotlin.time.Duration.Companion.microseconds |
| import kotlin.time.Duration.Companion.milliseconds |
| import kotlin.time.Duration.Companion.minutes |
| import kotlin.time.Duration.Companion.nanoseconds |
| import kotlin.time.Duration.Companion.seconds |
| |
| private val units = DurationUnit.entries |
| |
| class DurationTest { |
| |
| @Test |
| fun constructionFromNumber() { |
| // nanosecond precision |
| val testValues = listOf(0L, 1L, MAX_NANOS) + List(100) { Random.nextLong(0, MAX_NANOS) } |
| for (value in testValues) { |
| assertEquals(value, value.toDuration(DurationUnit.NANOSECONDS).inWholeNanoseconds) |
| assertEquals(-value, -value.toDuration(DurationUnit.NANOSECONDS).inWholeNanoseconds) |
| } |
| // expressible as long nanoseconds but stored as milliseconds |
| for (delta in testValues) { |
| val value = (MAX_NANOS + 1) + delta |
| val expected = value - (value % NANOS_IN_MILLIS) |
| assertEquals(expected, value.toDuration(DurationUnit.NANOSECONDS).inWholeNanoseconds) |
| assertEquals(-expected, -value.toDuration(DurationUnit.NANOSECONDS).inWholeNanoseconds) |
| } |
| // any int value of small units can always be represented in nanoseconds |
| for (unit in units.filter { it <= DurationUnit.SECONDS }) { |
| val scale = convertDurationUnitOverflow(1L, unit, DurationUnit.NANOSECONDS) |
| repeat(100) { |
| val value = Random.nextInt() |
| assertEquals(value * scale, value.toDuration(unit).inWholeNanoseconds) |
| } |
| } |
| |
| for (unit in units) { |
| val borderValue = convertDurationUnit(MAX_NANOS, DurationUnit.NANOSECONDS, unit) |
| val d1 = borderValue.toDuration(unit) |
| val d2 = (borderValue + 1).toDuration(unit) |
| assertNotEquals(d1, d1 + 1.nanoseconds) |
| assertEquals(d2, d2 + 1.nanoseconds) |
| } |
| |
| assertEquals(Long.MAX_VALUE / 1000, Long.MAX_VALUE.toDuration(DurationUnit.MICROSECONDS).inWholeMilliseconds) |
| assertEquals(Long.MAX_VALUE / 1000 * 1000, Long.MAX_VALUE.toDuration(DurationUnit.MICROSECONDS).toLong(DurationUnit.MICROSECONDS)) |
| |
| assertEquals(Duration.INFINITE, (MAX_MILLIS).toDuration(DurationUnit.MILLISECONDS)) |
| assertEquals(-Duration.INFINITE, (-MAX_MILLIS).toDuration(DurationUnit.MILLISECONDS)) |
| |
| run { |
| val maxNsDouble = MAX_NANOS.toDouble() |
| val lessThanMaxDouble = maxNsDouble.nextDown() |
| val maxNs = maxNsDouble.toDuration(DurationUnit.NANOSECONDS).inWholeNanoseconds |
| val lessThanMaxNs = lessThanMaxDouble.toDuration(DurationUnit.NANOSECONDS).inWholeNanoseconds |
| assertTrue(maxNs > lessThanMaxNs, "$maxNs should be > $lessThanMaxNs") |
| } |
| |
| assertFailsWith<IllegalArgumentException> { Double.NaN.toDuration(DurationUnit.SECONDS) } |
| } |
| |
| @Test |
| fun equality() { |
| val data = listOf<Pair<Double, DurationUnit>>( |
| Pair(2.0, DurationUnit.DAYS), |
| Pair(2.0, DurationUnit.HOURS), |
| Pair(0.25, DurationUnit.MINUTES), |
| Pair(1.0, DurationUnit.SECONDS), |
| Pair(50.0, DurationUnit.MILLISECONDS), |
| Pair(0.3, DurationUnit.MICROSECONDS), |
| Pair(20_000_000_000.0, DurationUnit.NANOSECONDS), |
| Pair(1.0, DurationUnit.NANOSECONDS) |
| ) |
| |
| for ((value, unit) in data) { |
| repeat(10) { |
| val d1 = value.toDuration(unit) |
| val unit2 = units.random() |
| @OptIn(ExperimentalTime::class) |
| val value2 = Duration.convert(value, unit, unit2) |
| val d2 = value2.toDuration(unit2) |
| assertEquals(d1, d2, "$value $unit in $unit2") |
| assertEquals(d1.hashCode(), d2.hashCode()) |
| |
| val d3 = (value2 * 2).toDuration(unit2) |
| assertNotEquals(d1, d3, "$value $unit in $unit2") |
| } |
| } |
| |
| run { // invariant Duration.nanoseconds(d.inWholeNanoseconds) == d when whole nanoseconds fits into Long range |
| val d1 = (MAX_NANOS + 1).nanoseconds |
| val d2 = d1.inWholeNanoseconds.nanoseconds |
| assertEquals(d1.inWholeNanoseconds, d2.inWholeNanoseconds) |
| assertEquals(d1, d2) |
| } |
| } |
| |
| @Test |
| fun comparison() { |
| fun assertGreater(d1: Duration, d2: Duration, message: String) { |
| assertTrue(d1 > d2, message) |
| assertFalse(d1 <= d2, message) |
| assertTrue( |
| d1.inWholeNanoseconds > d2.inWholeNanoseconds || |
| d1.inWholeNanoseconds == d2.inWholeNanoseconds && d1.inWholeMilliseconds > d2.inWholeMilliseconds, |
| message |
| ) |
| } |
| |
| val d4 = Long.MAX_VALUE.nanoseconds |
| val d3 = (MAX_NANOS + 1).nanoseconds |
| val d2 = MAX_NANOS.nanoseconds |
| val d1 = (MAX_NANOS - 1).nanoseconds |
| |
| assertGreater(d4, d2, "same sign, different ranges") |
| assertGreater(d3, d2, "same sign, different ranges 2") |
| assertGreater(d2, d1, "same sign, same range nanos") |
| assertGreater(d4, d3, "same sign, same range millis") |
| assertGreater(d2, -d3, "different signs, different ranges") |
| assertGreater(d3, -d4, "different signs, same ranges") |
| assertGreater(d1, -d2, "different signs, same ranges 2") |
| } |
| |
| |
| @Test |
| fun constructionFactoryFunctions() { |
| val n1 = Random.nextInt(Int.MAX_VALUE) |
| val n2 = Random.nextLong(Long.MAX_VALUE) |
| val n3 = Random.nextDouble() |
| |
| assertEquals(n1.toDuration(DurationUnit.DAYS), n1.days) |
| assertEquals(n2.toDuration(DurationUnit.DAYS), n2.days) |
| assertEquals(n3.toDuration(DurationUnit.DAYS), n3.days) |
| |
| assertEquals(n1.toDuration(DurationUnit.HOURS), n1.hours) |
| assertEquals(n2.toDuration(DurationUnit.HOURS), n2.hours) |
| assertEquals(n3.toDuration(DurationUnit.HOURS), n3.hours) |
| |
| assertEquals(n1.toDuration(DurationUnit.MINUTES), n1.minutes) |
| assertEquals(n2.toDuration(DurationUnit.MINUTES), n2.minutes) |
| assertEquals(n3.toDuration(DurationUnit.MINUTES), n3.minutes) |
| |
| assertEquals(n1.toDuration(DurationUnit.SECONDS), n1.seconds) |
| assertEquals(n2.toDuration(DurationUnit.SECONDS), n2.seconds) |
| assertEquals(n3.toDuration(DurationUnit.SECONDS), n3.seconds) |
| |
| assertEquals(n1.toDuration(DurationUnit.MILLISECONDS), n1.milliseconds) |
| assertEquals(n2.toDuration(DurationUnit.MILLISECONDS), n2.milliseconds) |
| assertEquals(n3.toDuration(DurationUnit.MILLISECONDS), n3.milliseconds) |
| |
| assertEquals(n1.toDuration(DurationUnit.MICROSECONDS), n1.microseconds) |
| assertEquals(n2.toDuration(DurationUnit.MICROSECONDS), n2.microseconds) |
| assertEquals(n3.toDuration(DurationUnit.MICROSECONDS), n3.microseconds) |
| |
| assertEquals(n1.toDuration(DurationUnit.NANOSECONDS), n1.nanoseconds) |
| assertEquals(n2.toDuration(DurationUnit.NANOSECONDS), n2.nanoseconds) |
| assertEquals(n3.toDuration(DurationUnit.NANOSECONDS), n3.nanoseconds) |
| } |
| |
| @Test |
| fun conversionToNumber() { |
| assertEquals(24, 1.days.inWholeHours) |
| assertEquals(0.5, 12.hours.toDouble(DurationUnit.DAYS)) |
| assertEquals(0, 12.hours.inWholeDays) |
| assertEquals(15, 0.25.hours.inWholeMinutes) |
| assertEquals(600, 10.minutes.inWholeSeconds) |
| assertEquals(500, 0.5.seconds.inWholeMilliseconds) |
| assertEquals(50_000, 0.05.seconds.inWholeMicroseconds) |
| assertEquals(50_000, 0.05.milliseconds.inWholeNanoseconds) |
| |
| assertEquals(365 * 86400 * 1_000_000_000L, 365.days.inWholeNanoseconds) |
| |
| assertEquals(0, Duration.ZERO.inWholeNanoseconds) |
| assertEquals(0, Duration.ZERO.inWholeMicroseconds) |
| assertEquals(0, Duration.ZERO.inWholeMilliseconds) |
| |
| assertEquals(10500, 10.5.seconds.inWholeMilliseconds) |
| assertEquals(11, 11.5.milliseconds.inWholeMilliseconds) |
| assertEquals(-11, (-11.5).milliseconds.inWholeMilliseconds) |
| assertEquals(252_000_000, 252.milliseconds.inWholeNanoseconds) |
| assertEquals(Long.MAX_VALUE, (365.days * 293).inWholeNanoseconds) // clamping overflowed value |
| |
| repeat(100) { |
| val value = Random.nextLong(1000) |
| val unit = units.random() |
| val unit2 = units.random() |
| |
| @OptIn(ExperimentalTime::class) |
| assertAlmostEquals(Duration.convert(value.toDouble(), unit, unit2), value.toDuration(unit).toDouble(unit2)) |
| } |
| |
| for (unit in units) { |
| assertEquals(Long.MAX_VALUE, Duration.INFINITE.toLong(unit)) |
| assertEquals(Int.MAX_VALUE, Duration.INFINITE.toInt(unit)) |
| assertEquals(Double.POSITIVE_INFINITY, Duration.INFINITE.toDouble(unit)) |
| assertEquals(Long.MIN_VALUE, (-Duration.INFINITE).toLong(unit)) |
| assertEquals(Int.MIN_VALUE, (-Duration.INFINITE).toInt(unit)) |
| assertEquals(Double.NEGATIVE_INFINITY, (-Duration.INFINITE).toDouble(unit)) |
| } |
| } |
| |
| @Test |
| fun componentsOfProperSum() { |
| repeat(100) { |
| 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) |
| (d.days + h.hours + m.minutes + s.seconds + ns.nanoseconds).run { |
| toComponents { seconds, 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(expectedNs, nanoseconds) |
| } |
| toComponents { hours, minutes, seconds, nanoseconds -> |
| assertEquals(d * 24 + h, hours) |
| assertEquals(m, minutes) |
| assertEquals(s, seconds) |
| assertEquals(expectedNs, nanoseconds) |
| } |
| toComponents { days, hours, minutes, seconds, nanoseconds -> |
| assertEquals(d, days) |
| assertEquals(h, hours) |
| assertEquals(m, minutes) |
| assertEquals(s, seconds) |
| assertEquals(expectedNs, nanoseconds) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun componentsOfCarriedSum() { |
| (36.hours + 90.minutes + 90.seconds + 1500.milliseconds).run { |
| toComponents { days, hours, minutes, seconds, nanoseconds -> |
| assertEquals(1, days) |
| assertEquals(13, hours) |
| assertEquals(31, minutes) |
| assertEquals(31, seconds) |
| assertEquals(500_000_000, nanoseconds) |
| } |
| } |
| } |
| |
| @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()) |
| assertTrue((-Duration.INFINITE).isInfinite()) |
| assertTrue(Double.POSITIVE_INFINITY.nanoseconds.isInfinite()) |
| |
| // seconds converted to nanoseconds overflow to infinite |
| assertTrue(Double.MAX_VALUE.seconds.isInfinite()) |
| assertTrue((-Double.MAX_VALUE).seconds.isInfinite()) |
| } |
| |
| |
| @Test |
| fun negation() { |
| repeat(100) { |
| val value = Random.nextLong() |
| val unit = units.random() |
| |
| assertEquals((-value).toDuration(unit), -value.toDuration(unit)) |
| } |
| } |
| |
| @Test |
| fun signAndAbsoluteValue() { |
| val negative = -1.seconds |
| val positive = 1.seconds |
| val zero = Duration.ZERO |
| |
| assertTrue(negative.isNegative()) |
| assertFalse(zero.isNegative()) |
| assertFalse(positive.isNegative()) |
| |
| assertFalse(negative.isPositive()) |
| assertFalse(zero.isPositive()) |
| assertTrue(positive.isPositive()) |
| |
| assertEquals(positive, negative.absoluteValue) |
| assertEquals(positive, positive.absoluteValue) |
| assertEquals(zero, zero.absoluteValue) |
| } |
| |
| @Test |
| fun negativeZero() { |
| fun equivalentToZero(value: Duration) { |
| assertEquals(Duration.ZERO, value) |
| assertEquals(Duration.ZERO, value.absoluteValue) |
| assertEquals(value, value.absoluteValue) |
| assertEquals(value, value.absoluteValue) |
| assertFalse(value.isNegative()) |
| assertFalse(value.isPositive()) |
| assertEquals(Duration.ZERO.toString(), value.toString()) |
| assertEquals(Duration.ZERO.toIsoString(), value.toIsoString()) |
| assertEquals(Duration.ZERO.toDouble(DurationUnit.SECONDS), value.toDouble(DurationUnit.SECONDS)) |
| assertEquals(0, Duration.ZERO.compareTo(value)) |
| assertEquals(0, Duration.ZERO.toDouble(DurationUnit.NANOSECONDS).compareTo(value.toDouble(DurationUnit.NANOSECONDS))) |
| } |
| equivalentToZero((-0.0).seconds) |
| equivalentToZero((-0.0).toDuration(DurationUnit.DAYS)) |
| equivalentToZero(-Duration.ZERO) |
| equivalentToZero((-1).seconds / Double.POSITIVE_INFINITY) |
| equivalentToZero(0.seconds / -1) |
| equivalentToZero((-1).seconds * 0.0) |
| equivalentToZero(0.seconds * -1) |
| } |
| |
| |
| @Test |
| fun addition() { |
| 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) |
| |
| for (value in listOf(Duration.ZERO, 1.nanoseconds, (500 * 365).days)) { |
| for (inf in listOf(Duration.INFINITE, -Duration.INFINITE)) { |
| for (result in listOf(inf + inf, inf + value, inf + (-value), value + inf, (-value) + inf)) { |
| assertEquals(inf, result) |
| } |
| } |
| } |
| |
| assertFailsWith<IllegalArgumentException> { Duration.INFINITE + (-Duration.INFINITE) } |
| } |
| |
| @Test |
| fun subtraction() { |
| assertEquals(10.hours, 0.5.days - 120.minutes) |
| assertEquals(850.milliseconds, 1.seconds - 150.milliseconds) |
| assertEquals(1150.milliseconds, 1.seconds - (-150).milliseconds) |
| assertEquals(1.milliseconds, Long.MAX_VALUE.microseconds - (Long.MAX_VALUE - 1_000).microseconds) |
| assertEquals((-1).milliseconds, (Long.MAX_VALUE - 1_000).microseconds - Long.MAX_VALUE.microseconds) |
| |
| run { |
| val offset = 2L * NANOS_IN_MILLIS |
| val value = MAX_NANOS + offset |
| val base = value.nanoseconds |
| val baseNs = base.inWholeMilliseconds * NANOS_IN_MILLIS |
| assertEquals(baseNs, base.inWholeNanoseconds) // base stored as millis |
| |
| val smallDeltas = listOf(1L, 2L, 1000L, NANOS_IN_MILLIS - 1L) + List(10) { Random.nextLong(NANOS_IN_MILLIS.toLong()) } |
| for (smallDeltaNs in smallDeltas) { |
| assertEquals(base, base - smallDeltaNs.nanoseconds, "delta: $smallDeltaNs") |
| } |
| |
| val deltas = listOf(offset + 1L, offset + 1500L) + |
| List(10) { Random.nextLong(offset + 1500, offset + 10000) } + |
| List(100) { Random.nextLong(offset + 1500, MAX_NANOS) } |
| for (deltaNs in deltas) { |
| val delta = deltaNs.nanoseconds |
| assertEquals(deltaNs, delta.inWholeNanoseconds) |
| assertEquals(baseNs - deltaNs, (base - delta).inWholeNanoseconds, "base: $baseNs, delta: $deltaNs") |
| } |
| } |
| |
| for (value in listOf(Duration.ZERO, 1.nanoseconds, (500 * 365).days)) { |
| for (inf in listOf(Duration.INFINITE, -Duration.INFINITE)) { |
| for (result in listOf(inf - (-inf), inf - value, inf - (-value), value - (-inf), (-value) - (-inf))) { |
| assertEquals(inf, result) |
| } |
| } |
| } |
| |
| assertFailsWith<IllegalArgumentException> { Duration.INFINITE - Duration.INFINITE } |
| } |
| |
| @Test |
| fun multiplication() { |
| assertEquals(1.days, 12.hours * 2) |
| assertEquals(1.days, 60.minutes * 24.0) |
| assertEquals(1.microseconds, 20.nanoseconds * 50) |
| |
| 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) |
| |
| run { // promoting nanos range to millis range after multiplication |
| val value = MAX_NANOS |
| assertEquals(value, (value.nanoseconds * 1_000_000).inWholeMilliseconds) |
| assertEquals(value / 1000, (value.nanoseconds * 1_000).inWholeMilliseconds) |
| assertEquals(Duration.INFINITE, (Long.MAX_VALUE / 1000 + 1).nanoseconds * 1_000_000_000) |
| } |
| |
| run { |
| val value = MAX_NANOS / Int.MAX_VALUE |
| assertTrue((value.nanoseconds * Int.MIN_VALUE).inWholeNanoseconds < -MAX_NANOS) |
| } |
| |
| assertEquals(Duration.INFINITE, Int.MAX_VALUE.days * Int.MAX_VALUE) |
| assertEquals(-Duration.INFINITE, Int.MAX_VALUE.days * Int.MIN_VALUE) |
| |
| assertEquals(Duration.INFINITE, Duration.INFINITE * Double.POSITIVE_INFINITY) |
| assertEquals(Duration.INFINITE, Duration.INFINITE * Double.MIN_VALUE) |
| assertEquals(-Duration.INFINITE, Duration.INFINITE * Double.NEGATIVE_INFINITY) |
| assertEquals(-Duration.INFINITE, Duration.INFINITE * -1) |
| |
| assertFailsWith<IllegalArgumentException> { Duration.INFINITE * 0 } |
| assertFailsWith<IllegalArgumentException> { 0.0 * Duration.INFINITE } |
| } |
| |
| @Test |
| fun divisionByNumber() { |
| assertEquals(12.hours, 1.days / 2) |
| assertEquals(60.minutes, 1.days / 24.0) |
| assertEquals(20.seconds, 2.minutes / 6) |
| assertEquals(365.days, (365 * 299).days / 299) |
| assertEquals(365.days, (365 * 299.5).days / 299.5) |
| |
| run { |
| val value = MAX_NANOS |
| assertEquals(value, (value.milliseconds / 1_000_000).inWholeNanoseconds) |
| } |
| |
| assertEquals(Duration.INFINITE, 1.seconds / 0) |
| assertEquals(-Duration.INFINITE, -1.seconds / 0.0) |
| assertEquals(Duration.INFINITE, -1.seconds / (-0.0)) |
| |
| assertEquals(Duration.INFINITE, Duration.INFINITE / Int.MAX_VALUE) |
| assertEquals(Duration.INFINITE, -Duration.INFINITE / Int.MIN_VALUE) |
| assertEquals(-Duration.INFINITE, Duration.INFINITE / -1) |
| assertEquals(Duration.INFINITE, Duration.INFINITE / Double.MAX_VALUE) |
| |
| assertFailsWith<IllegalArgumentException> { Duration.INFINITE / Double.POSITIVE_INFINITY } |
| assertFailsWith<IllegalArgumentException> { Duration.ZERO / 0 } |
| assertFailsWith<IllegalArgumentException> { Duration.ZERO / 0.0 } |
| } |
| |
| @Test |
| fun divisionByDuration() { |
| assertEquals(24.0, 1.days / 1.hours) |
| assertEquals(0.1, 9.minutes / 1.5.hours) |
| assertEquals(50.0, 1.microseconds / 20.nanoseconds) |
| assertEquals(299.0, (365 * 299).days / 365.days) |
| |
| assertTrue((Duration.INFINITE / Duration.INFINITE).isNaN()) |
| assertTrue((Duration.ZERO / Duration.ZERO).isNaN()) |
| } |
| |
| @Test |
| fun truncation() { |
| fun expect(expected: Duration, value: Duration, unit: DurationUnit) { |
| assertEquals(expected, value.truncateTo(unit)) |
| assertEquals(-expected, (-value).truncateTo(unit)) |
| } |
| for (unit in units) { |
| expect(Duration.ZERO, Duration.ZERO, unit) |
| expect(Duration.INFINITE, Duration.INFINITE, unit) |
| expect(Duration.ZERO, 1.toDuration(unit) - 1.nanoseconds, unit) |
| repeat(100) { |
| val whole = Random.nextInt(100_000).toDuration(unit) |
| expect(whole, whole, unit) |
| if (unit > DurationUnit.NANOSECONDS) { |
| val part = Random.nextLong(1, 1.toDuration(unit).inWholeNanoseconds).nanoseconds |
| expect(Duration.ZERO, part, unit) |
| expect(whole, whole + part, unit) |
| } |
| } |
| } |
| repeat(10) { |
| val d = Random.nextLong().nanoseconds |
| expect(d, d, DurationUnit.NANOSECONDS) |
| } |
| expect(12.microseconds, 12998.nanoseconds, DurationUnit.MICROSECONDS) |
| expect(1503.milliseconds, 1503_889_404.nanoseconds, DurationUnit.MILLISECONDS) |
| expect(340.seconds, 340_990_567_444L.nanoseconds, DurationUnit.SECONDS) |
| expect(3.minutes, 200.seconds, DurationUnit.MINUTES) |
| expect(4.hours, 250.minutes, DurationUnit.HOURS) |
| expect(1.days, 30.hours, DurationUnit.DAYS) |
| |
| // big durations |
| run { |
| val d = (Long.MAX_VALUE / 4).milliseconds |
| for (unit in units) { |
| if (unit <= DurationUnit.MILLISECONDS) { |
| expect(d, d, unit) |
| } else { |
| expect(d.toLong(unit).toDuration(unit), d, unit) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun parseAndFormatIsoString() { |
| fun test(duration: Duration, vararg isoStrings: String) { |
| assertEquals(isoStrings.first(), duration.toIsoString()) |
| for (isoString in isoStrings) { |
| assertEquals(duration, Duration.parseIsoString(isoString), isoString) |
| assertEquals(duration, Duration.parse(isoString), isoString) |
| assertEquals(duration, Duration.parseIsoStringOrNull(isoString), isoString) |
| assertEquals(duration, Duration.parseOrNull(isoString), isoString) |
| } |
| } |
| |
| // zero |
| test(Duration.ZERO, "PT0S", "P0D", "PT0H", "PT0M", "P0DT0H", "PT0H0M", "PT0H0S") |
| |
| // single unit |
| test(1.days, "PT24H", "P1D", "PT1440M", "PT86400S") |
| test(1.hours, "PT1H") |
| test(1.minutes, "PT1M") |
| test(1.seconds, "PT1S") |
| test(1.milliseconds, "PT0.001S") |
| test(1.microseconds, "PT0.000001S") |
| test(1.nanoseconds, "PT0.000000001S", "PT0.0000000009S") |
| test(0.9.nanoseconds, "PT0.000000001S") |
| |
| // rounded to zero |
| test(0.1.nanoseconds, "PT0S") |
| test(Duration.ZERO, "PT0S", "PT0.0000000004S") |
| |
| // several units combined |
| test(1.days + 1.minutes, "PT24H1M") |
| test(1.days + 1.seconds, "PT24H0M1S") |
| test(1.days + 1.milliseconds, "PT24H0M0.001S") |
| test(1.hours + 30.minutes, "PT1H30M") |
| test(1.hours + 500.milliseconds, "PT1H0M0.500S") |
| test(2.minutes + 500.milliseconds, "PT2M0.500S") |
| test(90_500.milliseconds, "PT1M30.500S") |
| |
| // with sign |
| test(-1.days + 15.minutes, "-PT23H45M", "PT-23H-45M", "+PT-24H+15M") |
| test(-1.days - 15.minutes, "-PT24H15M", "PT-24H-15M", "-PT25H-45M") |
| test(Duration.ZERO, "PT0S", "P1DT-24H", "+PT-1H+60M", "-PT1M-60S") |
| |
| // infinite |
| test(Duration.INFINITE, "PT9999999999999H", "PT+10000000000000H", "-PT-9999999999999H", "-PT-1234567890123456789012S") |
| test(-Duration.INFINITE, "-PT9999999999999H", "-PT10000000000000H", "PT-1234567890123456789012S") |
| } |
| |
| @Test |
| fun parseIsoStringFailing() { |
| for (invalidValue in listOf( |
| "", " ", "P", "PT", "P1DT", "P1", "PT1", "0", "+P", "+", "-", "h", "H", "something", |
| "1m", "1d", "2d 11s", "Infinity", "-Infinity", |
| "P+12+34D", "P12-34D", "PT1234567890-1234567890S", |
| " P1D", "PT1S ", |
| "P3W", |
| "P1Y", "P1M", "P1S", "PT1D", "PT1Y", |
| "PT1S2S", "PT1S2H", |
| "P9999999999999DT-9999999999999H", |
| "PT1.5H", "PT0.5D", "PT.5S", "PT0.25.25S", |
| )) { |
| assertNull(Duration.parseIsoStringOrNull(invalidValue), invalidValue) |
| assertFailsWith<IllegalArgumentException>(invalidValue) { Duration.parseIsoString(invalidValue) }.let { e -> |
| assertContains(e.message!!, "'$invalidValue'") |
| } |
| } |
| |
| } |
| |
| @Test |
| fun parseAndFormatInUnits() { |
| var d = 1.days + 15.hours + 31.minutes + 45.seconds + |
| 678.milliseconds + 920.microseconds + 516.34.nanoseconds |
| |
| fun test(unit: DurationUnit, vararg representations: String) { |
| assertFails { d.toString(unit, -1) } |
| assertEquals(representations.toList(), representations.indices.map { d.toString(unit, it) }) |
| for ((decimals, string) in representations.withIndex()) { |
| val d1 = Duration.parse(string) |
| assertEquals(d1, Duration.parseOrNull(string)) |
| if (!(d1 == d || (d1 - d).absoluteValue <= (0.5 * 10.0.pow(-decimals)).toDuration(unit))) { |
| fail("Parsed value $d1 (from $string) is too far from the real value $d") |
| } |
| } |
| } |
| |
| test(DurationUnit.DAYS, "2d", "1.6d", "1.65d", "1.647d") |
| test(DurationUnit.HOURS, "40h", "39.5h", "39.53h") |
| test(DurationUnit.MINUTES, "2372m", "2371.8m", "2371.76m") |
| d -= 39.hours |
| test(DurationUnit.SECONDS, "1906s", "1905.7s", "1905.68s", "1905.679s") |
| d -= 1904.seconds |
| test(DurationUnit.MILLISECONDS, "1679ms", "1678.9ms", "1678.92ms", "1678.921ms") |
| d -= 1678.milliseconds |
| test(DurationUnit.MICROSECONDS, "921us", "920.5us", "920.52us", "920.516us") |
| d -= 920.microseconds |
| // no sub-nanosecond precision |
| test(DurationUnit.NANOSECONDS, "516ns", "516.0ns", "516.00ns", "516.000ns", "516.0000ns") |
| d = (d - 516.nanoseconds) / 17 |
| test(DurationUnit.NANOSECONDS, "0ns", "0.0ns", "0.00ns", "0.000ns", "0.0000ns") |
| |
| // infinite |
| // d = Duration.nanoseconds(Double.MAX_VALUE) |
| // test(DurationUnit.DAYS, "2.08e+294d") |
| // test(DurationUnit.NANOSECONDS, "1.80e+308ns") |
| |
| assertEquals("0.500000000000s", 0.5.seconds.toString(DurationUnit.SECONDS, 100)) |
| assertEquals("99999000000000.000000000000ns", 99_999.seconds.toString(DurationUnit.NANOSECONDS, 15)) |
| assertContains( |
| listOf( |
| "-4611686018427388000000000.000000000000ns", |
| "-4611686018427387904000000.000000000000ns" |
| ), |
| (-(MAX_MILLIS - 1).milliseconds).toString(DurationUnit.NANOSECONDS, 15) |
| ) |
| |
| d = Duration.INFINITE |
| test(DurationUnit.DAYS, "Infinity", "Infinity") |
| d = -Duration.INFINITE |
| test(DurationUnit.NANOSECONDS, "-Infinity", "-Infinity") |
| } |
| |
| |
| @Test |
| fun parseAndFormatDefault() { |
| fun testParsing(string: String, expectedDuration: Duration) { |
| assertEquals(expectedDuration, Duration.parse(string), string) |
| assertEquals(expectedDuration, Duration.parseOrNull(string), string) |
| } |
| |
| fun test(duration: Duration, vararg expected: String) { |
| val actual = duration.toString() |
| assertEquals(expected.first(), actual) |
| |
| if (duration.isPositive()) { |
| if (' ' in actual) { |
| assertEquals("-($actual)", (-duration).toString()) |
| } else { |
| assertEquals("-$actual", (-duration).toString()) |
| } |
| } |
| |
| for (string in expected) { |
| testParsing(string, duration) |
| if (duration.isPositive() && duration.isFinite()) { |
| testParsing("+($string)", duration) |
| testParsing("-($string)", -duration) |
| if (' ' !in string) { |
| testParsing("+$string", duration) |
| testParsing("-$string", -duration) |
| } |
| } |
| } |
| } |
| |
| test(101.days, "101d", "2424h") |
| test(45.3.days, "45d 7h 12m", "45.3d", "45d 7.2h") // 0.3d == 7.2h |
| test(45.days, "45d") |
| |
| test(40.5.days, "40d 12h", "40.5d", "40d 720m") |
| test(40.days + 20.minutes, "40d 0h 20m", "40d 20m", "40d 1200s") |
| test(40.days + 20.seconds, "40d 0h 0m 20s", "40d 20s") |
| test(40.days + 100.nanoseconds, "40d 0h 0m 0.000000100s", "40d 100ns") |
| |
| test(40.hours + 15.minutes, "1d 16h 15m", "40h 15m") |
| test(40.hours, "1d 16h", "40h") |
| |
| test(12.5.hours, "12h 30m") |
| test(12.hours + 15.seconds, "12h 0m 15s") |
| test(12.hours + 1.nanoseconds, "12h 0m 0.000000001s") |
| test(30.minutes, "30m") |
| test(17.5.minutes, "17m 30s") |
| |
| test(16.5.minutes, "16m 30s") |
| test(1097.1.seconds, "18m 17.1s") |
| test(90.36.seconds, "1m 30.36s") |
| test(50.seconds, "50s") |
| test(1.3.seconds, "1.3s") |
| test(1.seconds, "1s") |
| |
| test(0.5.seconds, "500ms") |
| test(40.2.milliseconds, "40.2ms") |
| test(4.225.milliseconds, "4.225ms") |
| test(4.24501.milliseconds, "4.245010ms", "4ms 245us 10ns") |
| test(1.milliseconds, "1ms") |
| |
| test(0.75.milliseconds, "750us") |
| test(75.35.microseconds, "75.35us") |
| test(7.25.microseconds, "7.25us") |
| test(1.035.microseconds, "1.035us") |
| test(1.005.microseconds, "1.005us") |
| test(1800.nanoseconds, "1.8us", "1800ns", "0.0000000005h") |
| |
| test(950.5.nanoseconds, "951ns") |
| test(85.23.nanoseconds, "85ns") |
| test(8.235.nanoseconds, "8ns") |
| test(1.nanoseconds, "1ns", "0.9ns", "0.001us", "0.0009us") |
| test(1.3.nanoseconds, "1ns") |
| test(0.75.nanoseconds, "1ns") |
| test(0.7512.nanoseconds, "1ns") |
| |
| // equal to zero |
| // test(0.023.nanoseconds, "0.023ns") |
| // test(0.0034.nanoseconds, "0.0034ns") |
| // test(0.0000035.nanoseconds, "0.0000035ns") |
| |
| test(Duration.ZERO, "0s", "0.4ns", "0000.0000ns") |
| test(365.days * 10000, "3650000d") |
| test(300.days * 100000, "30000000d") |
| test(365.days * 100000, "36500000d") |
| test((MAX_MILLIS - 1).milliseconds, "53375995583d 15h 36m 27.902s") // max finite value |
| |
| // all infinite |
| // val universeAge = Duration.days(365.25) * 13.799e9 |
| // val planckTime = Duration.seconds(5.4e-44) |
| |
| // test(universeAge, "5.04e+12d") |
| // test(planckTime, "5.40e-44s") |
| // test(Duration.nanoseconds(Double.MAX_VALUE), "2.08e+294d") |
| test(Duration.INFINITE, "Infinity", "53375995583d 20h", "+Infinity") |
| test(-Duration.INFINITE, "-Infinity", "-(53375995583d 20h)") |
| } |
| |
| @Test |
| fun parseDefaultFailing() { |
| for (invalidValue in listOf( |
| "", " ", "P", "PT", "P1DT", "P1", "PT1", "0", "+P", "+", "-", "h", "H", "something", |
| "1234567890123456789012ns", "Inf", "-Infinity value", |
| "1s ", " 1s", |
| "1d 1m 1h", "1s 2s", |
| "-12m 15s", "-12m -15s", "-()", "-(12m 30s", |
| "+12m 15s", "+12m +15s", "+()", "+(12m 30s", |
| "()", "(12m 30s)", |
| "12.5m 11.5s", ".2s", "0.1553.39m", |
| "P+12+34D", "P12-34D", "PT1234567890-1234567890S", |
| " P1D", "PT1S ", |
| "P1Y", "P1M", "P1S", "PT1D", "PT1Y", |
| "PT1S2S", "PT1S2H", |
| "P9999999999999DT-9999999999999H", |
| "PT1.5H", "PT0.5D", "PT.5S", "PT0.25.25S", |
| )) { |
| assertNull(Duration.parseOrNull(invalidValue), invalidValue) |
| assertFailsWith<IllegalArgumentException>(invalidValue) { Duration.parse(invalidValue) }.let { e -> |
| assertContains(e.message!!, "'$invalidValue'") |
| } |
| } |
| } |
| |
| } |