korge
korge copied to clipboard
korlibs.time parsing millis precision issue
I've found an issue with parsing milliseconds using custom format with optional ([.S]
).
0.1 sec is 100 ms, but parsing as 1 millisecond; 0.10 sec is 100 ms, but parsing as 10 milliseconds; 0.100 sec is 100 ms, parsing as 100 milliseconds, that's OK.
@Test
fun testMillisKorlibs() {
//val format = ISO8601.DATETIME_UTC_COMPLETE_FRACTION // Can't set variable size for fraction of second
val format = DateFormat("yyyy-MM-dd'T'HH:mm[:ss[.S]]Z").withOptional()
val date = korlibs.time.DateTime(2020, 1, 1, 13, 12, 30, 100)
assertEquals(1, format.parseUtc("2020-01-01T13:12:30.001Z").milliseconds) //1
assertEquals(10, format.parseUtc("2020-01-01T13:12:30.010Z").milliseconds) //10
assertEquals(100, format.parseUtc("2020-01-01T13:12:30.100Z").milliseconds) //100
assertEquals(100, format.parseUtc("2020-01-01T13:12:30.1Z").milliseconds) // ❌ parsed as 1
assertEquals(100, format.parseUtc("2020-01-01T13:12:30.10Z").milliseconds) // ❌ parsed as 10
assertEquals(100, format.parseUtc("2020-01-01T13:12:30.100Z").milliseconds) //100
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.1Z")) // ❌ parsed as 2020-01-01T13:12:30.001Z
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.10Z")) // ❌ parsed as 2020-01-01T13:12:30.010Z
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.100Z"))
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.1000Z"))
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.10000Z"))
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.100000Z"))
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.1000000Z"))
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.10000000Z")) // ❌ parsed as 2020-01-01T13:12:30.099Z
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.100000000Z"))
// Out of precision
assertEquals(date, format.parseUtc("2020-01-01T13:12:30.1000000000Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.01Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.001Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.0001Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.00001Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.000001Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.0000001Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.00000001Z"))
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.000000001Z"))
// Out of precision
assertNotEquals(date, format.parseUtc("2020-01-01T13:12:30.0000000001Z"))
}
Tested on com.soywiz.korlibs.korio:korio:4.0.10, com.soywiz.korge:korlibs-time:5.3.2 and 5.4.0.
For example, Java Time and kotlinx.datetime same parsing is OK, but I used existing format for each test, because I can't create format from string with variable length for milliseconds:
@Test
fun testMillisJavaTime() {
//val format = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss[.S]]X") // Can't set variable size for fraction of second
val format = DateTimeFormatter.ISO_OFFSET_DATE_TIME
val date = ZonedDateTime.of(2020, 1, 1, 13, 12, 30, 100_000_000, ZoneOffset.UTC) // 100 millis
// ZonedDateTime:
assertEquals(date, format.parse("2020-01-01T13:12:30.1Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.10Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.100Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.1000Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.10000Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.100000Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.1000000Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.10000000Z", ZonedDateTime::from))
assertEquals(date, format.parse("2020-01-01T13:12:30.100000000Z", ZonedDateTime::from))
assertFailsWith<DateTimeParseException> {
// Out of precision: Text '2020-01-01T13:12:30.1000000000Z' could not be parsed at index 29
format.parse("2020-01-01T13:12:30.1000000000Z", ZonedDateTime::from)
}
assertNotEquals(date, format.parse("2020-01-01T13:12:30.01Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.001Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.0001Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.00001Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.000001Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.0000001Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.00000001Z", ZonedDateTime::from))
assertNotEquals(date, format.parse("2020-01-01T13:12:30.000000001Z", ZonedDateTime::from))
assertFailsWith<DateTimeParseException> {
// Out of precision: Text '2020-01-01T13:12:30.0000000001Z' could not be parsed at index 29
format.parse("2020-01-01T13:12:30.0000000001Z", ZonedDateTime::from)
}
}
@Test
fun testMillisKotlinxDateTime() {
//val format = DateTimeComponents.Format { byUnicodePattern("yyyy-MM-dd'T'HH:mm[:ss[.SSS]]X") } // Can't set variable size for fraction of second
val format = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET
val date = LocalDateTime(2020, 1, 1, 13, 12, 30, 100_000_000) //100 millis
// DateTimeFormat:
assertEquals(date, format.parse("2020-01-01T13:12:30.1Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.10Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.100Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.1000Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.10000Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.100000Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.1000000Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.10000000Z").toLocalDateTime())
assertEquals(date, format.parse("2020-01-01T13:12:30.100000000Z").toLocalDateTime())
assertFailsWith<IllegalArgumentException> {
// Out of precision: Failed to parse value from '2020-01-01T13:12:30.1000000000Z'
assertEquals(date, format.parse("2020-01-01T13:12:30.1000000000Z").toLocalDateTime())
}
assertNotEquals(date, format.parse("2020-01-01T13:12:30.01Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.001Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.0001Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.00001Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.000001Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.0000001Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.00000001Z").toLocalDateTime())
assertNotEquals(date, format.parse("2020-01-01T13:12:30.000000001Z").toLocalDateTime())
assertFailsWith<IllegalArgumentException> {
// Out of precision: Failed to parse value from '2020-01-01T13:12:30.0000000001Z'
assertNotEquals(date, format.parse("2020-01-01T13:12:30.0000000001Z").toLocalDateTime())
}
}