@@ -240,11 +240,18 @@ private class UnboundedLocalDateTime(
240
240
}
241
241
242
242
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
+ }
243
252
val s = isoString
244
253
var i = 0
245
- if (s.isEmpty()) {
246
- TODO ()
247
- }
254
+ require(s.isNotEmpty()) { " An empty string is not a valid Instant" }
248
255
val yearSign = when (s[i]) {
249
256
' +' -> { ++ i; ' +' }
250
257
' -' -> { ++ i; ' -' }
@@ -257,36 +264,34 @@ internal fun parseIso(isoString: String): Instant {
257
264
++ i
258
265
}
259
266
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} " )
262
269
}
263
270
i - yearStart < 4 -> {
264
- TODO ( )
271
+ parseFailure( " The year number must be padded to 4 digits, got ${i - yearStart} digits " )
265
272
}
266
273
else -> {
267
274
if (yearSign == ' +' && i - yearStart == 4 ) {
268
- TODO ( )
275
+ parseFailure( " The '+' sign at the start is only valid for year numbers longer than 4 digits " )
269
276
}
270
277
if (yearSign == ' ' && i - yearStart != 4 ) {
271
- TODO ( )
278
+ parseFailure( " The '+' sign is required for year numbers longer than 4 digits " )
272
279
}
273
280
if (yearSign == ' -' ) - absYear else absYear
274
281
}
275
282
}
276
283
// reading at least -MM-DDTHH:MM:SSZ
277
284
// 0123456789012345 16 chars
278
285
if (s.length < i + 16 ) {
279
- TODO ( )
286
+ parseFailure( " The input string is too short " )
280
287
}
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 == ' :' }
286
293
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' }
290
295
}
291
296
fun twoDigitNumber (index : Int ) = s[index].code * 10 + s[index + 1 ].code - ' 0' .code * 11
292
297
val month = twoDigitNumber(i + 1 )
@@ -305,52 +310,54 @@ internal fun parseIso(isoString: String): Instant {
305
310
if (i <= fractionStart + 9 ) {
306
311
fraction * POWERS_OF_TEN [fractionStart + 9 - i]
307
312
} else {
308
- TODO ( )
313
+ parseFailure( " At most 9 digits are supported for the fraction of the second, got {i - fractionStart} " )
309
314
}
310
315
} else {
311
316
i + = 15
312
317
0
313
318
}
314
319
val offsetSeconds = when (val sign = s.getOrNull(i)) {
315
320
null -> {
316
- TODO ( )
321
+ parseFailure( " The UTC offset at the end of the string is missing " )
317
322
}
318
323
' z' , ' Z' -> if (s.length == i + 1 ) {
319
324
0
320
325
} else {
321
- TODO ( )
326
+ parseFailure( " Extra text after the instant at position ${i + 1 } " )
322
327
}
323
328
' -' , ' +' -> {
324
329
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 " ) }
327
332
for (j in listOf (3 , 6 )) {
328
333
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]} '" )
330
335
}
331
336
for (j in listOf (1 , 2 , 4 , 5 , 7 , 8 )) {
332
337
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]} '" )
334
339
}
335
340
val offsetHour = twoDigitNumber(i + 1 )
336
341
val offsetMinute = if (offsetStrLength > 3 ) { twoDigitNumber(i + 4 ) } else { 0 }
337
342
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 " ) }
340
345
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 " )
342
347
}
343
348
(offsetHour * 3600 + offsetMinute * 60 + offsetSecond) * if (sign == ' -' ) - 1 else 1
344
349
}
345
350
else -> {
346
- TODO ( )
351
+ parseFailure( " Expected the UTC offset at position $i , got ' $sign ' " )
347
352
}
348
353
}
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 " ) }
354
361
return UnboundedLocalDateTime (year, month, day, hour, minute, second, nanosecond).toInstant(offsetSeconds)
355
362
}
356
363
0 commit comments