| /* |
| * Copyright 2010-2025 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.TestPlatform |
| import test.current |
| 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 longBoundaryValuesDurationConversion() { |
| val testCases = listOf( |
| Long.MIN_VALUE to Duration.NEG_INFINITE, |
| Long.MAX_VALUE to Duration.INFINITE |
| ) |
| |
| for ((value, expectedInfinite) in testCases) { |
| assertEquals(expectedInfinite, value.days) |
| assertEquals(expectedInfinite, value.hours) |
| assertEquals(expectedInfinite, value.minutes) |
| assertEquals(expectedInfinite, value.seconds) |
| assertEquals(expectedInfinite, value.milliseconds) |
| } |
| |
| val usDuration = 106751991.days + 4.hours + 54.seconds + 775.milliseconds |
| assertEquals(-usDuration, Long.MIN_VALUE.microseconds) |
| assertEquals(usDuration, Long.MAX_VALUE.microseconds) |
| |
| val nsDuration = 106751.days + 23.hours + 47.minutes + 16.seconds + 854.milliseconds |
| assertEquals(-nsDuration, Long.MIN_VALUE.nanoseconds) |
| assertEquals(nsDuration, Long.MAX_VALUE.nanoseconds) |
| } |
| |
| @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", "PT000000000000000000000000H") |
| |
| // single unit |
| test(1.days, "PT24H", "P1D", "PT1440M", "PT86400S") |
| test(1.hours, "PT1H") |
| test(1.minutes, "PT1M") |
| test(1.seconds, "PT1S", "PT000000000000000000000001S") |
| 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(1.hours + 500.milliseconds, "PT1H0M0.500S", "PT1H0.5S") |
| test(90_500.milliseconds, "PT1M30.500S") |
| test(20.minutes + 34.seconds + 100000395.nanoseconds, "PT20M34.100000395S", "PT1234.1000003945S") |
| |
| // 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", "-PT25H-44M-60.0S") |
| test(Duration.ZERO, "PT0S", "P1DT-24H", "+PT-1H+60M", "-PT1M-60S", "PT-000000000000000000000000H") |
| test((-1.1).seconds, "-PT1.100S", "PT-1.1S") |
| test((-0.5).seconds, "-PT0.500S", "PT-0.5S", "PT-1M+59.5S") |
| |
| // nanoseconds rounding |
| for (i in 0..4) { |
| test(Duration.ZERO, "PT0S", "PT0.000000000${i}S") |
| test(1.nanoseconds, "PT0.000000001S", "PT0.000000000${i + 5}S") |
| } |
| |
| // close to infinite |
| test( |
| 53375995583.days + 15.hours + 36.minutes + 27.seconds + 902.milliseconds + 999999.nanoseconds, |
| "PT1281023894007H36M27.902S", "P53375995583DT15H36M27.9029999994S", "PT4611686018427387.902S" |
| ) |
| test( |
| (-53375995583).days - 15.hours - 36.minutes - 27.seconds - 902.milliseconds - 999999.nanoseconds, |
| "-PT1281023894007H36M27.902S", "P-53375995583DT-15H-36M-27.9029999994S", "PT-4611686018427387.902S" |
| ) |
| |
| // infinite |
| test( |
| Duration.INFINITE, "PT9999999999999H", "PT+10000000000000H", "-PT-9999999999999H", "-PT-1234567890123456789012S", |
| "PT+000000000000000001234567890123456789012H", "P+9999999999999DT+9999999999999H", |
| "PT9999999999999H1M99999999999999999999999999.1S", "P12345678901234567890D", |
| "P999999999999DT1S", "P999999999999DT-1S", "P1DT12345678901234567890S", "P-1DT12345678901234567890S", |
| "PT106751991100H9223372036854000S", "PT106751991100H9223372036854000.1S", |
| "P53375995583DT15H36M27.9029999995S", "PT4611686018427387.903S", |
| "PT1H9223372036854775.0S", "PT76861433640456M12345S", "PT4611686018427387908S", "PT4611686018427387901S" |
| ) |
| test( |
| -Duration.INFINITE, "-PT9999999999999H", "P-12345678901234567890D", "-PT10000000000000H", |
| "PT-1234567890123456789012S", "PT-000000000000000001234567890123456789012H", "P-9999999999999DT-9999999999999H", |
| "PT-9999999999999H1M-99999999999999999999999999.1S", |
| "P-999999999999DT1S", "P-999999999999DT-1S", "P1DT-12345678901234567890S", "P-1DT-12345678901234567890S", |
| "PT-106751991100H-9223372036854000S", "PT-106751991100H-9223372036854000.1S", |
| "P-53375995583DT-15H-36M-28.0S", "PT1H-9223372036854775.0S", |
| "P-53375995583DT-15H-36M-27.9029999995S", "PT-4611686018427387.903S" |
| ) |
| } |
| |
| @Test |
| fun parseIsoStringFailing() { |
| for (invalidValue in listOf( |
| "", " ", "P", "PT", "P1DT", "P1", "PT1", "0", "+P", "+", "-", "h", "H", "something", "PT1HT", ",", "PT,123H", |
| "1m", "1d", "2d 11s", "Infinity", "-Infinity", |
| "P+12+34D", "P12-34D", "PT1234567890-1234567890S", "PhD", "PT+H", "PT0.123", "P1DW", "PT1H+", "PT1H+W", "PT1M-", "PT1M+W", |
| "PT0.1234567890123456789", "PT1234567890123456789", "PT0", |
| " P1D", "PT1S ", |
| "P3W", "PT4611686018427387908999W", |
| "P1Y", "P1M", "P1S", "PT1D", "PT1Y", |
| "PT1S2S", "PT1S2H", "PT1H1D", "PT3M5D", "PT0.1S1H", |
| "P9999999999999DT-9999999999999H", "P-9999999999999DT+9999999999999H", |
| "PT1.5H", "PT0.5D", "PT.5S", "PT0.25.25S", "PT1.S", |
| "PT+-2H", "PT-+2H", "PT+-01234567890123456S", |
| "PT-9999999999999H1M99999999999999999999999999.1S", |
| "P9999999999999DT-999999999999999999M", "P9999999999999DT-999999999999999999.0S", "P9999999999999DT-999999999999999999S", |
| "PT9999999999999H-999999999999999999M", "PT9999999999999H-999999999999999999.0S", "PT9999999999999H-999999999999999999S", |
| "PT999999999999999M-999999999999999999S", "PT-9999999999999H999999999999999999.0S", |
| "PT9223372036854775H-9223372036854775.0S", "PT-9223372036854775H9223372036854775.0S", |
| "PT4611686018427387908999" |
| )) { |
| assertNull(Duration.parseIsoStringOrNull(invalidValue), invalidValue) |
| assertFailsWith<IllegalArgumentException>(invalidValue) { Duration.parseIsoString(invalidValue) }.let { e -> |
| assertContains(e.message!!, "'$invalidValue'") |
| } |
| } |
| |
| } |
| |
| @Test |
| fun parseAndFormatInUnits() { |
| if (TestPlatform.current == TestPlatform.WasmWasi) return |
| |
| 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() { |
| testDefaultParsing(101.days, "101d", "2424h") |
| testDefaultParsing(45.3.days, "45d 7h 12m", "45.3d", "45d 7.2h") // 0.3d == 7.2h |
| testDefaultParsing(45.days, "45d") |
| |
| testDefaultParsing(40.5.days, "40d 12h", "40.5d", "40d 720m") |
| testDefaultParsing(40.days + 20.minutes, "40d 0h 20m", "40d 20m", "40d 1200s") |
| testDefaultParsing(40.days + 20.seconds, "40d 0h 0m 20s", "40d 20s") |
| testDefaultParsing(40.days + 100.nanoseconds, "40d 0h 0m 0.000000100s", "40d 100ns") |
| |
| testDefaultParsing(40.hours + 15.minutes, "1d 16h 15m", "40h 15m") |
| testDefaultParsing(40.hours, "1d 16h", "40h") |
| |
| testDefaultParsing(12.5.hours, "12h 30m") |
| testDefaultParsing(12.hours + 15.seconds, "12h 0m 15s") |
| testDefaultParsing(12.hours + 1.nanoseconds, "12h 0m 0.000000001s") |
| testDefaultParsing(30.minutes, "30m") |
| testDefaultParsing(17.5.minutes, "17m 30s") |
| |
| testDefaultParsing(16.5.minutes, "16m 30s") |
| testDefaultParsing(1097.1.seconds, "18m 17.1s") |
| testDefaultParsing(90.36.seconds, "1m 30.36s") |
| testDefaultParsing(50.seconds, "50s") |
| testDefaultParsing(1.3.seconds, "1.3s") |
| testDefaultParsing(1.seconds, "1s") |
| |
| testDefaultParsing(0.5.seconds, "500ms") |
| testDefaultParsing(40.2.milliseconds, "40.2ms") |
| testDefaultParsing(4.225.milliseconds, "4.225ms") |
| testDefaultParsing(4.24501.milliseconds, "4.245010ms", "4ms 245us 10ns") |
| testDefaultParsing(1.milliseconds, "1ms") |
| |
| testDefaultParsing(0.75.milliseconds, "750us") |
| testDefaultParsing(75.35.microseconds, "75.35us") |
| testDefaultParsing(7.25.microseconds, "7.25us") |
| testDefaultParsing(1.035.microseconds, "1.035us") |
| testDefaultParsing(1.005.microseconds, "1.005us") |
| testDefaultParsing(1800.nanoseconds, "1.8us", "1800ns", "0.0000000005h") |
| |
| testDefaultParsing(950.5.nanoseconds, "951ns") |
| testDefaultParsing(85.23.nanoseconds, "85ns") |
| testDefaultParsing(8.235.nanoseconds, "8ns") |
| testDefaultParsing(1.nanoseconds, "1ns", "0.9ns", "0.001us", "0.0009us") |
| testDefaultParsing(1.3.nanoseconds, "1ns") |
| testDefaultParsing(0.75.nanoseconds, "1ns") |
| testDefaultParsing(0.7512.nanoseconds, "1ns") |
| |
| // equal to zero |
| testDefaultParsing(Duration.ZERO, "0s", "0.4ns", "0000.0000ns") |
| testDefaultParsing(0.023.nanoseconds, "0s", "0.023ns") |
| testDefaultParsing(0.0034.nanoseconds, "0s", "0.0034ns") |
| testDefaultParsing(0.0000035.nanoseconds, "0s", "0.0000035ns") |
| |
| testDefaultParsing(365.days * 10000, "3650000d") |
| testDefaultParsing(300.days * 100000, "30000000d") |
| testDefaultParsing(365.days * 100000, "36500000d") |
| testDefaultParsing((MAX_MILLIS - 1).milliseconds, "53375995583d 15h 36m 27.902s") // max finite value |
| |
| testDefaultParsing(10.days, "10d") |
| testDefaultParsing(1.days + 12.hours, "1d 12h", "1d12h") |
| testDefaultParsing((-5).days - 23.hours - 2.minutes, "-(5d 23h 2m)", "-5d23h2m") |
| testDefaultParsing(9.days + 7.hours + 28.minutes + 6.seconds, "9d 7h 28m 6s", "8d 31h 28m 6s") |
| testDefaultParsing( |
| 15.days + 1.hours + 45.minutes + 31.seconds + 30123.microseconds, |
| "15d 1h 45m 31.030123s", |
| "15d 98m 451s 30.123ms" |
| ) |
| testDefaultParsing( |
| 102.days + 9.hours + 12.minutes + 45.seconds + 31.milliseconds + 210.123.microseconds, |
| "102d 9h 12m 45.031210123s", |
| "100d 57h 12m 45s 28ms 3210.12345us" |
| ) |
| testDefaultParsing( |
| 8771.days + 14.hours + 52.minutes + 42.seconds + 997.milliseconds + 438.microseconds + 653.nanoseconds, |
| "8771d 14h 52m 42.997438653s", |
| "8765d 151h 452m 1233s 9873ms 123451us 987653.12345678ns" |
| ) |
| testDefaultParsing( |
| -(1835.days + 14.hours + 27.minutes + 46.619332752.seconds), |
| "-(1835d 14h 27m 46.619332752s)", |
| "-(01257d 012395h 0087542m 000115874s 0871542ms 00951487us 000125845751.985487515ns)" |
| ) |
| testDefaultParsing( |
| 219124.days + 8.hours + 54.minutes + 38.909.seconds, |
| "219124d 8h 54m 38.909s", |
| "12345678910ms 18901234567890123us 18765432109876543ns" |
| ) |
| testDefaultParsing( |
| 10_000_000_000_000_100L.microseconds + 10_000_000_000_900_000L.nanoseconds, |
| "115856d 11h 33m 20s", |
| "10000000000000100us 10000000000900000ns" |
| ) |
| testDefaultParsing( |
| 4602453423018496273.milliseconds + Long.MAX_VALUE.microseconds + Long.MAX_VALUE.nanoseconds, |
| "53375995583d 15h 36m 27.902s", |
| "4602453423018496273ms ${Long.MAX_VALUE}us ${Long.MAX_VALUE}ns" |
| ) |
| testDefaultParsing(Long.MAX_VALUE.microseconds, "106751991d 4h 0m 54.775s", "${Long.MAX_VALUE}.99999999999us") |
| testDefaultParsing( |
| 1.days + 2.hours + 3.minutes + 4.seconds + 5.milliseconds + 6.microseconds + 7.nanoseconds, |
| "1d 2h 3m 4.005006007s", |
| "1d2h3m 4s 5ms 6us 7ns" |
| ) |
| |
| // close to infinite |
| testDefaultParsing( |
| 53375995583.days, |
| "${MAX_MILLIS / MILLIS_IN_DAY}d" |
| ) |
| testDefaultParsing( |
| 53375995583.days + 15.hours, |
| "53375995583d 15h", "${MAX_MILLIS / MILLIS_IN_HOUR}h" |
| ) |
| testDefaultParsing( |
| 53375995583.days + 15.hours + 36.minutes, |
| "53375995583d 15h 36m", "${MAX_MILLIS / MILLIS_IN_MINUTE}m" |
| ) |
| testDefaultParsing( |
| 53375995583.days + 15.hours + 36.minutes + 27.seconds, |
| "53375995583d 15h 36m 27s", "${MAX_MILLIS / MILLIS_IN_SECOND}s" |
| ) |
| testDefaultParsing( |
| 53375995583.days + 15.hours + 36.minutes + 27.seconds + 902.milliseconds, |
| "53375995583d 15h 36m 27.902s", "${MAX_MILLIS - 1}ms" |
| ) |
| |
| // 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") |
| testDefaultParsing( |
| Duration.INFINITE, "Infinity", "53375995583d 20h", "+Infinity", "123456789012345d 123456789012345h", |
| "4602453423018496274ms ${Long.MAX_VALUE}us ${Long.MAX_VALUE}ns" |
| ) |
| testDefaultParsing(90_000_000_000_000.minutes, "Infinity", "90000000000000m") |
| testDefaultParsing((-90_000_000_000_000).minutes, "-Infinity", "-90000000000000m") |
| testDefaultParsing( |
| Duration.INFINITE, "Infinity", "${MAX_MILLIS / MILLIS_IN_DAY + 1}d", "${MAX_MILLIS / MILLIS_IN_HOUR + 1}h", |
| "${MAX_MILLIS / MILLIS_IN_MINUTE + 1}m", "${MAX_MILLIS / MILLIS_IN_SECOND + 1}s", "${MAX_MILLIS}ms" |
| ) |
| testDefaultParsing(-Duration.INFINITE, "-Infinity", "-(53375995583d 20h)") |
| } |
| |
| @Test |
| fun nanosecondsRounding() { |
| for (i in 0..4) { |
| testDefaultParsing(Duration.ZERO, "0s", "0.000000000${i}s", "0.000000${i}ms", "0.000${i}us", "0.${i}ns") |
| testDefaultParsing(1.nanoseconds, "1ns", "0.000000000${i + 5}s", "0.000000${i + 5}ms", "0.000${i + 5}us", "0.${i + 5}ns") |
| testDefaultParsing(1.seconds - 1.nanoseconds, "999.999999ms", "0.999999999${i}s") |
| testDefaultParsing(1.seconds, "1s", "0.999999999${i + 5}s") |
| testDefaultParsing(1.milliseconds - 1.nanoseconds, "999.999us", "0.999999${i}ms") |
| testDefaultParsing(1.milliseconds, "1ms", "0.999999${i + 5}ms") |
| testDefaultParsing(1.microseconds - 1.nanoseconds, "999ns", "0.999${i}us") |
| testDefaultParsing(1.microseconds, "1us", "0.999${i + 5}us") |
| } |
| } |
| |
| @Test |
| fun parseDefaultFailing() { |
| for (invalidValue in listOf( |
| "", " ", "P", "PT", "P1DT", "P1", "PT1", "0", "+P", "+", "-", "h", "H", "something", "P,D", |
| "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)", "-(12m) 30s", "(12m) 30s", "12m (30s)", |
| "12.5m 11.5s", ".2s", "0.1553.39m", |
| "12.h", "12.55", "123", "123uw", "876nm", "531n", |
| "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'") |
| } |
| } |
| } |
| |
| @Test |
| fun parseComponentOrdering() { |
| val validDefaultFormat = listOf( |
| "1d 2h 3m 4s 5ms 6us 7ns", |
| "1d 2h 3m 4s", |
| "2h 3m 4s", |
| "3m 4s 5ms", |
| "4s 5ms 6us 7ns", |
| "1d 3m", |
| "1d 4s", |
| "2h 5ms", |
| ) |
| |
| for (valid in validDefaultFormat) { |
| assertNotNull(Duration.parseOrNull(valid), "Should parse valid ordering: $valid") |
| Duration.parse(valid) // should not throw |
| } |
| |
| val invalidDefaultFormat = listOf( |
| "1m 2h", |
| "1s 2m", |
| "1us 2ms", |
| "1ms 2s", |
| "1ns 2us", |
| "1h 2d", |
| "3m 2h 1d", |
| "1d 3m 2h", |
| "2h 4s 3m", |
| "1d 2h 3m 4s 3m", |
| "5ms 4s 3m 2h 1d", |
| "1ns 1us 1ms 1s 1m 1h 1d", |
| ) |
| |
| for (invalid in invalidDefaultFormat) { |
| assertNull(Duration.parseOrNull(invalid), "Should not parse invalid ordering: $invalid") |
| assertFailsWith<IllegalArgumentException>("Should throw for invalid ordering: $invalid") { |
| Duration.parse(invalid) |
| } |
| } |
| |
| val validIsoFormat = listOf( |
| "P1DT2H3M4S", |
| "PT2H3M4S", |
| "PT3M4S", |
| "P1DT4S", |
| "PT2H4S", |
| ) |
| |
| for (valid in validIsoFormat) { |
| assertNotNull(Duration.parseIsoStringOrNull(valid), "Should parse valid ISO ordering: $valid") |
| Duration.parseIsoString(valid) // should not throw |
| } |
| |
| val invalidIsoFormat = listOf( |
| "PT1M2H", |
| "PT1S2M", |
| "PT2H1S3M", |
| "P1DT3M2H", |
| "PT1S2M3H", |
| "PT2H3M2H", |
| ) |
| |
| for (invalid in invalidIsoFormat) { |
| assertNull(Duration.parseIsoStringOrNull(invalid), "Should not parse invalid ISO ordering: $invalid") |
| assertFailsWith<IllegalArgumentException>("Should throw for invalid ISO ordering: $invalid") { |
| Duration.parseIsoString(invalid) |
| } |
| } |
| } |
| } |