Skip to content

Commit bf6e723

Browse files
committed
Test Instant parsing more extensively
1 parent f864b19 commit bf6e723

File tree

2 files changed

+137
-21
lines changed

2 files changed

+137
-21
lines changed

core/native/src/Instant.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,9 @@ internal fun parseIso(isoString: String): Instant {
337337
val offsetHour = twoDigitNumber(i + 1)
338338
val offsetMinute = if (offsetStrLength > 3) { twoDigitNumber(i + 4) } else { 0 }
339339
val offsetSecond = if (offsetStrLength > 6) { twoDigitNumber(i + 7) } else { 0 }
340-
if (offsetMinute !in 0..59) { parseFailure("Expected offset-minute-of-hour in 0..59, got $offsetMinute") }
341-
if (offsetSecond !in 0..59) { parseFailure("Expected offset-second-of-minute in 0..59, got $offsetSecond") }
342-
if (offsetHour !in 0..17 && !(offsetHour == 18 && offsetMinute == 0 && offsetMinute == 0)) {
340+
if (offsetMinute > 59) { parseFailure("Expected offset-minute-of-hour in 0..59, got $offsetMinute") }
341+
if (offsetSecond > 59) { parseFailure("Expected offset-second-of-minute in 0..59, got $offsetSecond") }
342+
if (offsetHour > 17 && !(offsetHour == 18 && offsetMinute == 0 && offsetSecond == 0)) {
343343
parseFailure("Expected an offset in -18:00..+18:00, got $sign$offsetHour:$offsetMinute:$offsetSecond")
344344
}
345345
(offsetHour * 3600 + offsetMinute * 60 + offsetSecond) * if (sign == '-') -1 else 1
@@ -352,9 +352,9 @@ internal fun parseIso(isoString: String): Instant {
352352
if (day !in 1..month.monthLength(isLeapYear(year))) {
353353
parseFailure("Expected a valid day-of-month for $year-$month, got $day")
354354
}
355-
if (hour !in 0..23) { parseFailure("Expected hour in 0..23, got $hour") }
356-
if (minute !in 0..59) { parseFailure("Expected minute-of-hour in 0..59, got $minute") }
357-
if (second !in 0..59) { parseFailure("Expected second-of-minute in 0..59, got $second") }
355+
if (hour > 23) { parseFailure("Expected hour in 0..23, got $hour") }
356+
if (minute > 59) { parseFailure("Expected minute-of-hour in 0..59, got $minute") }
357+
if (second > 59) { parseFailure("Expected second-of-minute in 0..59, got $second") }
358358
return UnboundedLocalDateTime(year, month, day, hour, minute, second, nanosecond).toInstant(offsetSeconds)
359359
}
360360

core/native/test/InstantIsoStringsTest.kt

Lines changed: 131 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,34 @@ import kotlin.test.*
99

1010
class InstantIsoStringsTest {
1111

12-
/* Based on the ThreeTenBp project.
13-
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
14-
*/
12+
@Test
13+
fun parseAndFormatCanonicalStrings() {
14+
for (canonicalString in arrayOf(
15+
"1970-01-01T00:00:00Z",
16+
"1970-01-01T00:00:00.100Z",
17+
"1970-01-01T00:00:00.010Z",
18+
"1970-01-01T00:00:00.001Z",
19+
"1970-01-01T00:00:00.000100Z",
20+
"1970-01-01T00:00:00.000010Z",
21+
"1970-01-01T00:00:00.000001Z",
22+
"1970-01-01T00:00:00.000000100Z",
23+
"1970-01-01T00:00:00.000000010Z",
24+
"1970-01-01T00:00:00.000000001Z",
25+
)) {
26+
val instant = parseInstant(canonicalString)
27+
assertEquals(canonicalString, instant.toString())
28+
}
29+
}
30+
1531
@Test
1632
fun parseIsoString() {
17-
val instants = arrayOf(
33+
for ((str, seconds, nanos) in arrayOf(
34+
// both upper and lower case is supported
1835
Triple("1970-01-01T00:00:00Z", 0, 0),
1936
Triple("1970-01-01t00:00:00Z", 0, 0),
2037
Triple("1970-01-01T00:00:00z", 0, 0),
2138
Triple("1970-01-01T00:00:00.0Z", 0, 0),
39+
// all components are taken into accout
2240
Triple("1970-01-01T00:00:00.000000000Z", 0, 0),
2341
Triple("1970-01-01T00:00:00.000000001Z", 0, 1),
2442
Triple("1970-01-01T00:00:00.100000000Z", 0, 100000000),
@@ -28,51 +46,145 @@ class InstantIsoStringsTest {
2846
Triple("1970-01-01T00:01:01.000000001Z", 61, 1),
2947
Triple("1970-01-01T01:00:00.000000000Z", 3600, 0),
3048
Triple("1970-01-01T01:01:01.000000001Z", 3661, 1),
31-
Triple("1970-01-02T01:01:01.100000000Z", 90061, 100000000))
32-
instants.forEach {
33-
val (str, seconds, nanos) = it
49+
Triple("1970-01-02T01:01:01.100000000Z", 90061, 100000000),
50+
Triple("1970-02-02T01:01:01.100000000Z", 31 * 86400 + 90061, 100000000),
51+
Triple("1971-02-02T01:01:01.100000000Z", (365 + 31) * 86400 + 90061, 100000000),
52+
// current time
53+
Triple("2024-07-15T16:06:29.461245691+02:00", 1721052389, 461245691),
54+
)) {
3455
val instant = parseInstant(str)
35-
assertEquals(seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds())
56+
assertEquals(
57+
seconds.toLong() * 1000 + nanos / 1000000, instant.toEpochMilliseconds(),
58+
"Parsed $instant from $str, with Unix time = `$seconds + 10^-9 * $nanos`"
59+
)
3660
}
61+
}
3762

63+
@Test
64+
fun nonParseableInstantStrings() {
3865
for (nonIsoString in listOf(
66+
// empty string
3967
"",
68+
// a non-empty but clearly unsuitable string
4069
"x",
70+
// something other than a sign at the beginning
4171
" 1970-01-01T00:00:00Z",
72+
// too many digits for the year
4273
"+1234567890-01-01T00:00:00Z",
4374
"-1234567890-01-01T00:00:00Z",
75+
// not enough padding for the year
4476
"003-01-01T00:00:00Z",
4577
"-003-01-01T00:00:00Z",
78+
// a plus sign even though there is only 4 digits
4679
"+1970-01-01T00:00:00Z",
80+
// too many digits without padding
4781
"11970-01-01T00:00:00Z",
48-
"1970-01-01T00:00:00Z",
82+
// incorrect separators between the components
4983
"1970/01-01T00:00:00Z",
5084
"1970-01/01T00:00:00Z",
5185
"1970-01-01 00:00:00Z",
5286
"1970-01-01T00-00:00Z",
5387
"1970-01-01T00:00-00Z",
88+
// non-digits where digits are expected
5489
"1970-X1-01T00:00:00Z",
5590
"1970-1X-01T00:00:00Z",
5691
"1970-11-X1T00:00:00Z",
5792
"1970-11-1XT00:00:00Z",
58-
"1970-11-10T00:00:00Z",
5993
"1970-11-10TX0:00:00Z",
6094
"1970-11-10T0X:00:00Z",
6195
"1970-11-10T00:X0:00Z",
6296
"1970-11-10T00:0X:00Z",
6397
"1970-11-10T00:00:X0Z",
6498
"1970-11-10T00:00:0XZ",
99+
// a non-ascii digit
65100
"1970-11-10T00:00:0٩Z",
101+
// not enough components
102+
"1970-11-10T00:00Z",
103+
// not enough components, even if the length is sufficient
104+
"1970-11-10T00:00+01:15",
105+
// a dot without any fraction of the second following it
66106
"1970-11-10T00:00:00.Z",
107+
// too many digits in the fraction of the second
67108
"1970-11-10T00:00:00.1234567890Z",
109+
// out-of-range values
110+
"1970-00-10T00:00:00Z",
111+
"1970-13-10T00:00:00Z",
112+
"1970-01-32T00:00:00Z",
113+
"1970-02-29T00:00:00Z",
114+
"1972-02-30T00:00:00Z",
115+
"2000-02-30T00:00:00Z",
116+
"2100-02-29T00:00:00Z",
117+
"2004-02-30T00:00:00Z",
118+
"2005-02-29T00:00:00Z",
119+
"2005-04-31T00:00:00Z",
120+
"2005-04-01T24:00:00Z",
121+
"2005-04-01T00:60:00Z",
122+
"2005-04-01T00:00:60Z",
123+
// leap second
124+
"1970-01-01T23:59:60Z",
125+
// lack of padding
126+
"1970-1-10T00:00:00+05:00",
127+
"1970-10-1T00:00:00+05:00",
128+
"1970-10-10T0:00:00+05:00",
129+
"1970-10-10T00:0:00+05:00",
130+
"1970-10-10T00:00:0+05:00",
131+
// no offset
132+
"1970-02-03T04:05:06.123456789",
133+
// some invalid single-character offsets
134+
"1970-02-03T04:05:06.123456789A",
135+
"1970-02-03T04:05:06.123456789+",
136+
"1970-02-03T04:05:06.123456789-",
137+
// too many components in the offset
138+
"1970-02-03T04:05:06.123456789+03:02:01:00",
139+
"1970-02-03T04:05:06.123456789+03:02:01.02",
140+
// single-digit offset
141+
"1970-02-03T04:05:06.123456789+3",
142+
// incorrect sign in the offset
143+
"1970-02-03T04:05:06.123456789 03",
144+
// non-digits in the offset
145+
"1970-02-03T04:05:06.123456789+X3",
146+
"1970-02-03T04:05:06.123456789+1X",
147+
"1970-02-03T04:05:06.123456789+X3:12",
148+
"1970-02-03T04:05:06.123456789+1X:12",
149+
"1970-02-03T04:05:06.123456789+X3:12",
150+
"1970-02-03T04:05:06.123456789+13:X2",
151+
"1970-02-03T04:05:06.123456789+13:1X",
152+
"1970-02-03T04:05:06.123456789+X3:12:59",
153+
"1970-02-03T04:05:06.123456789+1X:12:59",
154+
"1970-02-03T04:05:06.123456789+X3:12:59",
155+
"1970-02-03T04:05:06.123456789+13:X2:59",
156+
"1970-02-03T04:05:06.123456789+13:1X:59",
157+
"1970-02-03T04:05:06.123456789+13:12:X9",
158+
"1970-02-03T04:05:06.123456789+13:12:5X",
159+
// incorrect separators in the offset
160+
"1970-02-03T04:05:06.123456789+13/12",
161+
"1970-02-03T04:05:06.123456789+13/12:59",
162+
"1970-02-03T04:05:06.123456789+13:12/59",
163+
"1970-02-03T04:05:06.123456789+0130",
164+
"1970-02-03T04:05:06.123456789-0130",
165+
// incorrect field length
166+
"1970-02-03T04:05:06.123456789-18:001",
167+
// out-of-range offsets
168+
"1970-02-03T04:05:06.123456789+18:12:59",
169+
"1970-02-03T04:05:06.123456789-18:12:59",
170+
"1970-02-03T04:05:06.123456789+18:00:01",
171+
"1970-02-03T04:05:06.123456789-18:00:01",
172+
"1970-02-03T04:05:06.123456789+18:01",
173+
"1970-02-03T04:05:06.123456789-18:01",
174+
"1970-02-03T04:05:06.123456789+19",
175+
"1970-02-03T04:05:06.123456789-19",
176+
// out-of-range fields of the offset
177+
"1970-02-03T04:05:06.123456789+01:12:60",
178+
"1970-02-03T04:05:06.123456789-01:12:60",
179+
"1970-02-03T04:05:06.123456789+01:60",
180+
"1970-02-03T04:05:06.123456789-01:60",
181+
// lack of padding in the offset
182+
"1970-02-03T04:05:06.123456789+1:12:50",
183+
"1970-02-03T04:05:06.123456789+01:2:60",
184+
"1970-02-03T04:05:06.123456789+01:12:6",
68185
)) {
69186
assertInvalidFormat(nonIsoString) { parseInstant(nonIsoString) }
70187
}
71-
assertInvalidFormat { parseInstant("1970-01-01T23:59:60Z")}
72-
assertInvalidFormat { parseInstant("1970-01-01T24:00:00Z")}
73-
assertInvalidFormat { parseInstant("1970-01-01T23:59Z")}
74-
assertInvalidFormat { parseInstant("x") }
75-
assertInvalidFormat { parseInstant("12020-12-31T23:59:59.000000000Z") }
76188
// this string represents an Instant that is currently larger than Instant.MAX any of the implementations:
77189
assertInvalidFormat { parseInstant ("+1000000001-12-31T23:59:59.000000000Z") }
78190
}
@@ -130,6 +242,10 @@ class InstantIsoStringsTest {
130242
private fun parseInstant(isoString: String): Instant {
131243
return parseIso(isoString)
132244
}
245+
246+
private fun displayInstant(instant: Instant): String {
247+
return formatIso(instant)
248+
}
133249
}
134250

135251

0 commit comments

Comments
 (0)