diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index ad88fa25c1b..ef0060be648 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -721,8 +721,9 @@ public value class Duration internal constructor(private val rawValue: Long) : C INFINITE.rawValue -> "Infinity" NEG_INFINITE.rawValue -> "-Infinity" else -> { + val isNegative = isNegative() buildString { - if (isNegative()) append('-') + if (isNegative) append('-') absoluteValue.run { toComponents { _, hours, minutes, seconds, nanoseconds -> val days = inWholeDays @@ -730,22 +731,21 @@ public value class Duration internal constructor(private val rawValue: Long) : C val hasHours = hours != 0 val hasMinutes = minutes != 0 val hasSeconds = seconds != 0 || nanoseconds != 0 + var components = 0 if (hasDays) { - append(days) - append('d') + append(days).append('d') + components++ } if (hasHours || (hasDays && (hasMinutes || hasSeconds))) { - if (length > 1) append(' ') - append(hours) - append('h') + if (components++ > 0) append(' ') + append(hours).append('h') } if (hasMinutes || (hasSeconds && (hasHours || hasDays))) { - if (length > 1) append(' ') - append(minutes) - append('m') + if (components++ > 0) append(' ') + append(minutes).append('m') } if (hasSeconds) { - if (length > 1) append(' ') + if (components++ > 0) append(' ') when { seconds != 0 || hasDays || hasHours || hasMinutes -> appendFractional(seconds, nanoseconds, "s") @@ -757,6 +757,7 @@ public value class Duration internal constructor(private val rawValue: Long) : C append(nanoseconds).append("ns") } } + if (isNegative && components > 1) insert(1, '(').append(')') } } } @@ -1065,7 +1066,7 @@ public inline operator fun Double.times(duration: Duration): Duration = duration @ExperimentalTime private fun parseDuration(value: String, strictIso: Boolean): Duration { - val length = value.length + var length = value.length if (length == 0) throw IllegalArgumentException("The string is empty") var index = 0 var result = Duration.ZERO @@ -1073,9 +1074,10 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration { when (value[index]) { '+', '-' -> index++ } + val isNegative = value.startsWith('-') when { length <= index -> - throw IllegalArgumentException() + throw IllegalArgumentException("No components") value[index] == 'P' -> { if (++index == length) throw IllegalArgumentException() val signedDigits = "+-0123456789." @@ -1115,8 +1117,13 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration { val digits = "0123456789." var prevUnit: DurationUnit? = null var afterFirst = false + var allowSpaces = !isNegative + if (isNegative && value[index] == '(' && value.last() == ')') { + allowSpaces = true + if (++index == --length) throw IllegalArgumentException("No components") + } while (index < length) { - if (afterFirst) { + if (afterFirst && allowSpaces) { index = value.skipWhile(index) { it == ' ' } } afterFirst = true @@ -1140,7 +1147,7 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration { } } } - return if (value.startsWith('-')) -result else result + return if (isNegative) -result else result } diff --git a/libraries/stdlib/test/time/DurationTest.kt b/libraries/stdlib/test/time/DurationTest.kt index 965f6872cc8..146ed119159 100644 --- a/libraries/stdlib/test/time/DurationTest.kt +++ b/libraries/stdlib/test/time/DurationTest.kt @@ -564,17 +564,30 @@ class DurationTest { @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 > Duration.ZERO) { - assertEquals("-$actual", (-duration).toString()) + if (duration.isPositive()) { + if (' ' in actual) { + assertEquals("-($actual)", (-duration).toString()) + } else { + assertEquals("-$actual", (-duration).toString()) + } } for (string in expected) { - assertEquals(duration, Duration.parse(string), string) - assertEquals(duration, Duration.parseOrNull(string), string) + testParsing(string, duration) + if (duration.isPositive() && duration.isFinite()) { + testParsing("-($string)", -duration) + if (' ' !in string) + testParsing("-$string", -duration) + } } } @@ -642,7 +655,8 @@ class DurationTest { // test(universeAge, "5.04e+12d") // test(planckTime, "5.40e-44s") // test(Duration.nanoseconds(Double.MAX_VALUE), "2.08e+294d") - test(Duration.INFINITE, "Infinity") + test(Duration.INFINITE, "Infinity", "53375995583d 20h", "+Infinity") + test(-Duration.INFINITE, "-Infinity", "-(53375995583d 20h)") } @Test @@ -652,7 +666,7 @@ class DurationTest { "1234567890123456789012ns", "Inf", "-Infinity value", "1s ", " 1s", "1d 1m 1h", "1s 2s", - "-12m -15s", + "-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 ",