Skip to content

Commit 9a596b1

Browse files
committed
Improve the error messages
1 parent c768602 commit 9a596b1

File tree

2 files changed

+41
-34
lines changed

2 files changed

+41
-34
lines changed

core/native/src/Instant.kt

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,18 @@ private class UnboundedLocalDateTime(
240240
}
241241

242242
internal fun parseIso(isoString: String): Instant {
243+
fun parseFailure(error: String): Nothing {
244+
throw IllegalArgumentException("$error when parsing an Instant from $isoString")
245+
}
246+
inline fun expect(what: String, where: Int, predicate: (Char) -> Boolean) {
247+
val c = isoString[where]
248+
if (!predicate(c)) {
249+
parseFailure("Expected $what, but got $c at position $where")
250+
}
251+
}
243252
val s = isoString
244253
var i = 0
245-
if (s.isEmpty()) {
246-
TODO()
247-
}
254+
require(s.isNotEmpty()) { "An empty string is not a valid Instant" }
248255
val yearSign = when (s[i]) {
249256
'+' -> { ++i; '+' }
250257
'-' -> { ++i; '-' }
@@ -257,36 +264,34 @@ internal fun parseIso(isoString: String): Instant {
257264
++i
258265
}
259266
val year = when {
260-
i - yearStart > 9 -> {
261-
TODO()
267+
i > yearStart + 9 -> {
268+
parseFailure("Expected at most 9 digits for the year number, got ${i - yearStart}")
262269
}
263270
i - yearStart < 4 -> {
264-
TODO()
271+
parseFailure("The year number must be padded to 4 digits, got ${i - yearStart} digits")
265272
}
266273
else -> {
267274
if (yearSign == '+' && i - yearStart == 4) {
268-
TODO()
275+
parseFailure("The '+' sign at the start is only valid for year numbers longer than 4 digits")
269276
}
270277
if (yearSign == ' ' && i - yearStart != 4) {
271-
TODO()
278+
parseFailure("The '+' sign is required for year numbers longer than 4 digits")
272279
}
273280
if (yearSign == '-') -absYear else absYear
274281
}
275282
}
276283
// reading at least -MM-DDTHH:MM:SSZ
277284
// 0123456789012345 16 chars
278285
if (s.length < i + 16) {
279-
TODO()
286+
parseFailure("The input string is too short")
280287
}
281-
if (s[i] != '-') { TODO() }
282-
if (s[i + 3] != '-') { TODO() }
283-
if (s[i + 6] != 'T' && s[i + 6] != 't') { TODO() }
284-
if (s[i + 9] != ':') { TODO() }
285-
if (s[i + 12] != ':') { TODO() }
288+
expect("'-'", i) { it == '-' }
289+
expect("'-'", i + 3) { it == '-' }
290+
expect("'T' or 't'", i + 6) { it == 'T' || it == 't' }
291+
expect("':'", i + 9) { it == ':' }
292+
expect("':'", i + 12) { it == ':' }
286293
for (j in listOf(1, 2, 4, 5, 7, 8, 10, 11, 13, 14)) {
287-
if (s[i + j] !in '0'..'9') {
288-
TODO()
289-
}
294+
expect("an ASCII digit", i + j) { it in '0'..'9' }
290295
}
291296
fun twoDigitNumber(index: Int) = s[index].code * 10 + s[index + 1].code - '0'.code * 11
292297
val month = twoDigitNumber(i + 1)
@@ -305,52 +310,54 @@ internal fun parseIso(isoString: String): Instant {
305310
if (i <= fractionStart + 9) {
306311
fraction * POWERS_OF_TEN[fractionStart + 9 - i]
307312
} else {
308-
TODO()
313+
parseFailure("At most 9 digits are supported for the fraction of the second, got {i - fractionStart}")
309314
}
310315
} else {
311316
i += 15
312317
0
313318
}
314319
val offsetSeconds = when (val sign = s.getOrNull(i)) {
315320
null -> {
316-
TODO()
321+
parseFailure("The UTC offset at the end of the string is missing")
317322
}
318323
'z', 'Z' -> if (s.length == i + 1) {
319324
0
320325
} else {
321-
TODO()
326+
parseFailure("Extra text after the instant at position ${i + 1}")
322327
}
323328
'-', '+' -> {
324329
val offsetStrLength = s.length - i
325-
if (offsetStrLength % 3 != 0) { TODO() }
326-
if (offsetStrLength > 9) { TODO() }
330+
if (offsetStrLength % 3 != 0) { parseFailure("Invalid UTC offset string '${s.substring(i)}'") }
331+
if (offsetStrLength > 9) { parseFailure("The UTC offset string '${s.substring(i)}' is too long") }
327332
for (j in listOf(3, 6)) {
328333
if (s.getOrNull(i + j) ?: break != ':')
329-
TODO("Expected ':' at index ${i + j}, got '${s[i + j]}' when parsing instant from $s")
334+
parseFailure("Expected ':' at index ${i + j}, got '${s[i + j]}'")
330335
}
331336
for (j in listOf(1, 2, 4, 5, 7, 8)) {
332337
if (s.getOrNull(i + j) ?: break !in '0'..'9')
333-
TODO("Expected a digit at index ${i + j}, got '${s[i + j]}' when parsing instant from $s")
338+
parseFailure("Expected a digit at index ${i + j}, got '${s[i + j]}'")
334339
}
335340
val offsetHour = twoDigitNumber(i + 1)
336341
val offsetMinute = if (offsetStrLength > 3) { twoDigitNumber(i + 4) } else { 0 }
337342
val offsetSecond = if (offsetStrLength > 6) { twoDigitNumber(i + 7) } else { 0 }
338-
if (offsetMinute !in 0..59) { TODO("Expected offset-minute-of-hour in 0..59, got $offsetMinute when parsing instant from $s") }
339-
if (offsetSecond !in 0..59) { TODO("Expected offset-second-of-minute in 0..59, got $offsetSecond when parsing instant from $s") }
343+
if (offsetMinute !in 0..59) { parseFailure("Expected offset-minute-of-hour in 0..59, got $offsetMinute") }
344+
if (offsetSecond !in 0..59) { parseFailure("Expected offset-second-of-minute in 0..59, got $offsetSecond") }
340345
if (offsetHour !in 0..17 && !(offsetHour == 18 && offsetMinute == 0 && offsetMinute == 0)) {
341-
TODO()
346+
parseFailure("Expected an offset in -18:00..+18:00, got $sign$offsetHour:$offsetMinute:$offsetSecond")
342347
}
343348
(offsetHour * 3600 + offsetMinute * 60 + offsetSecond) * if (sign == '-') -1 else 1
344349
}
345350
else -> {
346-
TODO()
351+
parseFailure("Expected the UTC offset at position $i, got '$sign'")
347352
}
348353
}
349-
if (month !in 1..12) { TODO() }
350-
if (day !in 1..month.monthLength(isLeapYear(year))) { TODO() }
351-
if (hour !in 0..23) { TODO() }
352-
if (minute !in 0..59) { TODO() }
353-
if (second !in 0..59) { TODO() }
354+
if (month !in 1..12) { parseFailure("Expected a month number in 1..12, got $month") }
355+
if (day !in 1..month.monthLength(isLeapYear(year))) {
356+
parseFailure("Expected a valid day-of-month for $year-$month, got $day")
357+
}
358+
if (hour !in 0..23) { parseFailure("Expected hour in 0..23, got $hour") }
359+
if (minute !in 0..59) { parseFailure("Expected minute-of-hour in 0..59, got $minute") }
360+
if (second !in 0..59) { parseFailure("Expected second-of-minute in 0..59, got $second") }
354361
return UnboundedLocalDateTime(year, month, day, hour, minute, second, nanosecond).toInstant(offsetSeconds)
355362
}
356363

core/native/test/InstantIsoStringsTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class InstantIsoStringsTest {
9999
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
100100
@kotlin.internal.InlineOnly
101101
private fun <T> assertInvalidFormat(message: String? = null, f: () -> T) {
102-
assertFailsWith<NotImplementedError>(message) {
102+
assertFailsWith<IllegalArgumentException>(message) {
103103
val result = f()
104104
fail(result.toString())
105105
}

0 commit comments

Comments
 (0)