Duration: longer than long values in ISO components, test negative cases

This commit is contained in:
Ilya Gorbunov
2021-07-05 21:24:24 +03:00
committed by Space
parent 1c6ab08220
commit ca1a9e4ca3
2 changed files with 91 additions and 33 deletions
+26 -13
View File
@@ -7,10 +7,7 @@ package kotlin.time
import kotlin.contracts.*
import kotlin.jvm.JvmInline
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.math.roundToLong
import kotlin.math.sign
import kotlin.math.*
/**
* Represents the amount of time one instant of time is away from another instant.
@@ -1080,13 +1077,13 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration {
length <= index ->
throw IllegalArgumentException()
value[index] == 'P' -> {
index++
if (++index == length) throw IllegalArgumentException()
val signedDigits = "+-0123456789."
var isTimeComponent = false
var prevUnit: DurationUnit? = null
while (index < length) {
if (value[index] == 'T') {
if (isTimeComponent || index++ == length) throw IllegalArgumentException()
if (isTimeComponent || ++index == length) throw IllegalArgumentException()
isTimeComponent = true
continue
}
@@ -1098,11 +1095,13 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration {
val unit = durationUnitByIsoChar(unitChar, isTimeComponent)
if (prevUnit != null && prevUnit <= unit) throw IllegalArgumentException("Unexpected order of duration components")
prevUnit = unit
if (unit == DurationUnit.SECONDS && '.' in component) {
// TODO: parse fractional part separately
result += component.toDouble().toDuration(unit)
val dotIndex = component.indexOf('.')
if (unit == DurationUnit.SECONDS && dotIndex > 0) {
val whole = component.substring(0, dotIndex)
result += parseOverLongIsoComponent(whole).toDuration(unit)
result += component.substring(dotIndex).toDouble().toDuration(unit)
} else {
result += component.toLong().toDuration(unit)
result += parseOverLongIsoComponent(component).toDuration(unit)
}
}
}
@@ -1129,9 +1128,11 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration {
val unit = durationUnitByShortName(unitName)
if (prevUnit != null && prevUnit <= unit) throw IllegalArgumentException("Unexpected order of duration components")
prevUnit = unit
if ('.' in component) {
// TODO: parse fractional part separately
result += component.toDouble().toDuration(unit)
val dotIndex = component.indexOf('.')
if (dotIndex > 0) {
val whole = component.substring(0, dotIndex)
result += whole.toLong().toDuration(unit)
result += component.substring(dotIndex).toDouble().toDuration(unit)
if (index < length) throw IllegalArgumentException("Fractional component must be last")
} else {
result += component.toLong().toDuration(unit)
@@ -1143,6 +1144,18 @@ private fun parseDuration(value: String, strictIso: Boolean): Duration {
}
private fun parseOverLongIsoComponent(value: String): Long {
val length = value.length
var startIndex = 0
if (length > 0 && value[0] in "+-") startIndex++
if ((length - startIndex) > 16 && (startIndex..value.lastIndex).all { value[it] in '0'..'9' }) {
// all chars are digits, but more than ceiling(log10(MAX_MILLIS / 1000)) of them
return if (value[0] == '-') Long.MIN_VALUE else Long.MAX_VALUE
}
return value.toLong()
}
private inline fun String.substringWhile(startIndex: Int, predicate: (Char) -> Boolean): String =
substring(startIndex, skipWhile(startIndex, predicate))
+65 -20
View File
@@ -454,7 +454,7 @@ class DurationTest {
}
@Test
fun isoStringFormatAndParse() {
fun parseAndFormatIsoString() {
fun test(duration: Duration, vararg isoStrings: String) {
assertEquals(isoStrings.first(), duration.toIsoString())
for (isoString in isoStrings) {
@@ -497,12 +497,30 @@ class DurationTest {
test(Duration.ZERO, "PT0S", "P1DT-24H", "PT-1H60M", "-PT1M-60S")
// infinite
test(Duration.INFINITE, "PT9999999999999H", "PT10000000000000H")
test(-Duration.INFINITE, "-PT9999999999999H", "-PT10000000000000H")
test(Duration.INFINITE, "PT9999999999999H", "PT10000000000000H", "-PT-9999999999999H", "-PT-1234567890123456789012S")
test(-Duration.INFINITE, "-PT9999999999999H", "-PT10000000000000H", "PT-1234567890123456789012S")
}
@Test
fun toStringInUnits() {
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<IllegalArgumentException>(invalidValue) { Duration.parseIsoString(invalidValue) }
}
}
@Test
fun formatInUnits() {
var d = with(Duration) {
days(1) + hours(15) + minutes(31) + seconds(45) +
milliseconds(678) + microseconds(920) + nanoseconds(516.34)
@@ -545,28 +563,32 @@ class DurationTest {
@Test
fun toStringDefault() {
fun test(duration: Duration, vararg expectedOptions: String) {
fun parseAndFormatDefault() {
fun test(duration: Duration, vararg expected: String) {
val actual = duration.toString()
assertEquals(expected.first(), actual)
if (!expectedOptions.contains(actual)) {
assertEquals<Any>(expectedOptions.toList(), duration.toString())
}
if (duration > Duration.ZERO)
if (duration > Duration.ZERO) {
assertEquals("-$actual", (-duration).toString())
}
for (string in expected) {
assertEquals(duration, Duration.parse(string), string)
assertEquals(duration, Duration.parseOrNull(string), string)
}
}
test(Duration.days(101), "101d")
test(Duration.days(45.3), "45d 7h 12m") // 0.3d == 7.2h
test(Duration.days(101), "101d", "2424h")
test(Duration.days(45.3), "45d 7h 12m", "45.3d", "45d 7.2h") // 0.3d == 7.2h
test(Duration.days(45), "45d")
test(Duration.days(40.5), "40d 12h")
test(Duration.days(40) + Duration.minutes(20), "40d 0h 20m")
test(Duration.days(40) + Duration.seconds(20), "40d 0h 0m 20s")
test(Duration.days(40) + Duration.nanoseconds(100), "40d 0h 0m 0.000000100s")
test(Duration.days(40.5), "40d 12h", "40.5d", "40d 720m")
test(Duration.days(40) + Duration.minutes(20), "40d 0h 20m", "40d 20m", "40d 1200s")
test(Duration.days(40) + Duration.seconds(20), "40d 0h 0m 20s", "40d 20s")
test(Duration.days(40) + Duration.nanoseconds(100), "40d 0h 0m 0.000000100s", "40d 100ns")
test(Duration.hours(40) + Duration.minutes(15), "1d 16h 15m")
test(Duration.hours(40), "1d 16h")
test(Duration.hours(40) + Duration.minutes(15), "1d 16h 15m", "40h 15m")
test(Duration.hours(40), "1d 16h", "40h")
test(Duration.hours(12.5), "12h 30m")
test(Duration.hours(12) + Duration.seconds(15), "12h 0m 15s")
@@ -584,7 +606,7 @@ class DurationTest {
test(Duration.seconds(0.5), "500ms")
test(Duration.milliseconds(40.2), "40.200ms")
test(Duration.milliseconds(4.225), "4.225ms")
test(Duration.milliseconds(4.24501), "4.245010ms")
test(Duration.milliseconds(4.24501), "4.245010ms", "4ms 245us 10ns")
test(Duration.milliseconds(1), "1ms")
test(Duration.milliseconds(0.75), "750us")
@@ -592,10 +614,12 @@ class DurationTest {
test(Duration.microseconds(7.25), "7.250us")
test(Duration.microseconds(1.035), "1.035us")
test(Duration.microseconds(1.005), "1.005us")
test(Duration.nanoseconds(1800), "1.800us", "1800ns", "0.0000000005h")
test(Duration.nanoseconds(950.5), "951ns")
test(Duration.nanoseconds(85.23), "85ns")
test(Duration.nanoseconds(8.235), "8ns")
test(Duration.nanoseconds(1), "1ns", "0.9ns", "0.001us", "0.0009us")
test(Duration.nanoseconds(1.3), "1ns")
test(Duration.nanoseconds(0.75), "1ns")
test(Duration.nanoseconds(0.7512), "1ns")
@@ -605,7 +629,7 @@ class DurationTest {
// test(Duration.nanoseconds(0.0034), "0.0034ns")
// test(Duration.nanoseconds(0.0000035), "0.0000035ns")
test(Duration.ZERO, "0s")
test(Duration.ZERO, "0s", "0.4ns", "0000.0000ns")
test(Duration.days(365) * 10000, "3650000d")
test(Duration.days(300) * 100000, "30000000d")
test(Duration.days(365) * 100000, "36500000d")
@@ -621,4 +645,25 @@ class DurationTest {
test(Duration.INFINITE, "Infinity")
}
@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",
"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) }
}
}
}