/* * 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.native.concurrent.SharedImmutable 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 @SharedImmutable private val units = DurationUnit.values() 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 { Double.NaN.toDuration(DurationUnit.SECONDS) } } @Test fun equality() { val data = listOf>( 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 { 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 { 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 { Duration.INFINITE * 0 } assertFailsWith { 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 { Duration.INFINITE / Double.POSITIVE_INFINITY } assertFailsWith { Duration.ZERO / 0 } assertFailsWith { 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 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 ", "P1Y", "P1M", "P1S", "PT1D", "PT1Y", "PT1S2S", "PT1S2H", "P9999999999999DT-9999999999999H", "PT1.5H", "PT0.5D", "PT.5S", "PT0.25.25S", )) { assertNull(Duration.parseIsoStringOrNull(invalidValue), invalidValue) assertFailsWith(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(invalidValue) { Duration.parse(invalidValue) }.let { e -> assertContains(e.message!!, "'$invalidValue'") } } } }