Duration: parse and format negative values parenthesized

This commit is contained in:
Ilya Gorbunov
2021-07-05 21:57:55 +03:00
committed by Space
parent ca1a9e4ca3
commit 1be1e5279c
2 changed files with 41 additions and 20 deletions
+21 -14
View File
@@ -721,8 +721,9 @@ public value class Duration internal constructor(private val rawValue: Long) : C
INFINITE.rawValue -> "Infinity" INFINITE.rawValue -> "Infinity"
NEG_INFINITE.rawValue -> "-Infinity" NEG_INFINITE.rawValue -> "-Infinity"
else -> { else -> {
val isNegative = isNegative()
buildString { buildString {
if (isNegative()) append('-') if (isNegative) append('-')
absoluteValue.run { absoluteValue.run {
toComponents { _, hours, minutes, seconds, nanoseconds -> toComponents { _, hours, minutes, seconds, nanoseconds ->
val days = inWholeDays val days = inWholeDays
@@ -730,22 +731,21 @@ public value class Duration internal constructor(private val rawValue: Long) : C
val hasHours = hours != 0 val hasHours = hours != 0
val hasMinutes = minutes != 0 val hasMinutes = minutes != 0
val hasSeconds = seconds != 0 || nanoseconds != 0 val hasSeconds = seconds != 0 || nanoseconds != 0
var components = 0
if (hasDays) { if (hasDays) {
append(days) append(days).append('d')
append('d') components++
} }
if (hasHours || (hasDays && (hasMinutes || hasSeconds))) { if (hasHours || (hasDays && (hasMinutes || hasSeconds))) {
if (length > 1) append(' ') if (components++ > 0) append(' ')
append(hours) append(hours).append('h')
append('h')
} }
if (hasMinutes || (hasSeconds && (hasHours || hasDays))) { if (hasMinutes || (hasSeconds && (hasHours || hasDays))) {
if (length > 1) append(' ') if (components++ > 0) append(' ')
append(minutes) append(minutes).append('m')
append('m')
} }
if (hasSeconds) { if (hasSeconds) {
if (length > 1) append(' ') if (components++ > 0) append(' ')
when { when {
seconds != 0 || hasDays || hasHours || hasMinutes -> seconds != 0 || hasDays || hasHours || hasMinutes ->
appendFractional(seconds, nanoseconds, "s") appendFractional(seconds, nanoseconds, "s")
@@ -757,6 +757,7 @@ public value class Duration internal constructor(private val rawValue: Long) : C
append(nanoseconds).append("ns") 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 @ExperimentalTime
private fun parseDuration(value: String, strictIso: Boolean): Duration { 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") if (length == 0) throw IllegalArgumentException("The string is empty")
var index = 0 var index = 0
var result = Duration.ZERO var result = Duration.ZERO
@@ -1073,9 +1074,10 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration {
when (value[index]) { when (value[index]) {
'+', '-' -> index++ '+', '-' -> index++
} }
val isNegative = value.startsWith('-')
when { when {
length <= index -> length <= index ->
throw IllegalArgumentException() throw IllegalArgumentException("No components")
value[index] == 'P' -> { value[index] == 'P' -> {
if (++index == length) throw IllegalArgumentException() if (++index == length) throw IllegalArgumentException()
val signedDigits = "+-0123456789." val signedDigits = "+-0123456789."
@@ -1115,8 +1117,13 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration {
val digits = "0123456789." val digits = "0123456789."
var prevUnit: DurationUnit? = null var prevUnit: DurationUnit? = null
var afterFirst = false var afterFirst = false
var allowSpaces = !isNegative
if (isNegative && value[index] == '(' && value.last() == ')') {
allowSpaces = true
if (++index == --length) throw IllegalArgumentException("No components")
}
while (index < length) { while (index < length) {
if (afterFirst) { if (afterFirst && allowSpaces) {
index = value.skipWhile(index) { it == ' ' } index = value.skipWhile(index) { it == ' ' }
} }
afterFirst = true 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
} }
+20 -6
View File
@@ -564,17 +564,30 @@ class DurationTest {
@Test @Test
fun parseAndFormatDefault() { 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) { fun test(duration: Duration, vararg expected: String) {
val actual = duration.toString() val actual = duration.toString()
assertEquals(expected.first(), actual) assertEquals(expected.first(), actual)
if (duration > Duration.ZERO) { if (duration.isPositive()) {
assertEquals("-$actual", (-duration).toString()) if (' ' in actual) {
assertEquals("-($actual)", (-duration).toString())
} else {
assertEquals("-$actual", (-duration).toString())
}
} }
for (string in expected) { for (string in expected) {
assertEquals(duration, Duration.parse(string), string) testParsing(string, duration)
assertEquals(duration, Duration.parseOrNull(string), string) 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(universeAge, "5.04e+12d")
// test(planckTime, "5.40e-44s") // test(planckTime, "5.40e-44s")
// test(Duration.nanoseconds(Double.MAX_VALUE), "2.08e+294d") // 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 @Test
@@ -652,7 +666,7 @@ class DurationTest {
"1234567890123456789012ns", "Inf", "-Infinity value", "1234567890123456789012ns", "Inf", "-Infinity value",
"1s ", " 1s", "1s ", " 1s",
"1d 1m 1h", "1s 2s", "1d 1m 1h", "1s 2s",
"-12m -15s", "-12m 15s", "-12m -15s", "(12m 30s)", "-()", "()", "-(12m 30s",
"12.5m 11.5s", ".2s", "0.1553.39m", "12.5m 11.5s", ".2s", "0.1553.39m",
"P+12+34D", "P12-34D", "PT1234567890-1234567890S", "P+12+34D", "P12-34D", "PT1234567890-1234567890S",
" P1D", "PT1S ", " P1D", "PT1S ",