@@ -100,20 +100,6 @@ object IntervalUtils {
100
100
Decimal (result, 18 , 6 )
101
101
}
102
102
103
- /**
104
- * Converts a string to [[CalendarInterval ]] case-insensitively.
105
- *
106
- * @throws IllegalArgumentException if the input string is not in valid interval format.
107
- */
108
- def fromString (str : String ): CalendarInterval = {
109
- if (str == null ) throw new IllegalArgumentException (" Interval string cannot be null" )
110
- val interval = stringToInterval(UTF8String .fromString(str))
111
- if (interval == null ) {
112
- throw new IllegalArgumentException (s " Invalid interval string: $str" )
113
- }
114
- interval
115
- }
116
-
117
103
private def toLongWithRange (
118
104
fieldName : IntervalUnit ,
119
105
s : String ,
@@ -250,30 +236,6 @@ object IntervalUtils {
250
236
}
251
237
}
252
238
253
- /**
254
- * Parse second_nano string in ss.nnnnnnnnn format to microseconds
255
- */
256
- private def parseSecondNano (secondNano : String ): Long = {
257
- def parseSeconds (secondsStr : String ): Long = {
258
- toLongWithRange(
259
- SECOND ,
260
- secondsStr,
261
- Long .MinValue / MICROS_PER_SECOND ,
262
- Long .MaxValue / MICROS_PER_SECOND ) * MICROS_PER_SECOND
263
- }
264
-
265
- secondNano.split(" \\ ." ) match {
266
- case Array (secondsStr) => parseSeconds(secondsStr)
267
- case Array (" " , nanosStr) => parseNanos(nanosStr, false )
268
- case Array (secondsStr, nanosStr) =>
269
- val seconds = parseSeconds(secondsStr)
270
- Math .addExact(seconds, parseNanos(nanosStr, seconds < 0 ))
271
- case _ =>
272
- throw new IllegalArgumentException (
273
- " Interval string does not match second-nano format of ss.nnnnnnnnn" )
274
- }
275
- }
276
-
277
239
/**
278
240
* Gets interval duration
279
241
*
@@ -397,20 +359,40 @@ object IntervalUtils {
397
359
private final val millisStr = unitToUtf8(MILLISECOND )
398
360
private final val microsStr = unitToUtf8(MICROSECOND )
399
361
362
+ /**
363
+ * A safe version of `stringToInterval`. It returns null for invalid input string.
364
+ */
365
+ def safeStringToInterval (input : UTF8String ): CalendarInterval = {
366
+ try {
367
+ stringToInterval(input)
368
+ } catch {
369
+ case _ : IllegalArgumentException => null
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Converts a string to [[CalendarInterval ]] case-insensitively.
375
+ *
376
+ * @throws IllegalArgumentException if the input string is not in valid interval format.
377
+ */
400
378
def stringToInterval (input : UTF8String ): CalendarInterval = {
401
379
import ParseState ._
380
+ var state = PREFIX
381
+ def exceptionWithState (msg : String , e : Exception = null ) = {
382
+ throw new IllegalArgumentException (s " Error parsing interval in state ' $state', $msg" , e)
383
+ }
402
384
403
385
if (input == null ) {
404
- return null
386
+ exceptionWithState( " interval string cannot be null" )
405
387
}
406
388
// scalastyle:off caselocale .toLowerCase
407
389
val s = input.trim.toLowerCase
408
390
// scalastyle:on
409
391
val bytes = s.getBytes
410
392
if (bytes.isEmpty) {
411
- return null
393
+ exceptionWithState( " interval string cannot be empty " )
412
394
}
413
- var state = PREFIX
395
+
414
396
var i = 0
415
397
var currentValue : Long = 0
416
398
var isNegative : Boolean = false
@@ -427,13 +409,17 @@ object IntervalUtils {
427
409
}
428
410
}
429
411
412
+ def nextWord : UTF8String = {
413
+ s.substring(i, s.numBytes()).subStringIndex(UTF8String .blankString(1 ), 1 )
414
+ }
415
+
430
416
while (i < bytes.length) {
431
417
val b = bytes(i)
432
418
state match {
433
419
case PREFIX =>
434
420
if (s.startsWith(intervalStr)) {
435
421
if (s.numBytes() == intervalStr.numBytes()) {
436
- return null
422
+ exceptionWithState( " interval string cannot be empty " )
437
423
} else {
438
424
i += intervalStr.numBytes()
439
425
}
@@ -450,7 +436,7 @@ object IntervalUtils {
450
436
i += 1
451
437
case _ if '0' <= b && b <= '9' =>
452
438
isNegative = false
453
- case _ => return null
439
+ case _ => exceptionWithState( s " Unrecognized sign ' $nextWord ' " )
454
440
}
455
441
currentValue = 0
456
442
fraction = 0
@@ -465,13 +451,14 @@ object IntervalUtils {
465
451
try {
466
452
currentValue = Math .addExact(Math .multiplyExact(10 , currentValue), (b - '0' ))
467
453
} catch {
468
- case _ : ArithmeticException => return null
454
+ case _ : ArithmeticException =>
455
+ exceptionWithState(s " ' $currentValue$nextWord' out of range " )
469
456
}
470
457
case ' ' => state = TRIM_BEFORE_UNIT
471
458
case '.' =>
472
459
fractionScale = (NANOS_PER_SECOND / 10 ).toInt
473
460
state = VALUE_FRACTIONAL_PART
474
- case _ => return null
461
+ case _ => exceptionWithState( s " invalid value ' $nextWord ' " )
475
462
}
476
463
i += 1
477
464
case VALUE_FRACTIONAL_PART =>
@@ -482,14 +469,14 @@ object IntervalUtils {
482
469
case ' ' =>
483
470
fraction /= NANOS_PER_MICROS .toInt
484
471
state = TRIM_BEFORE_UNIT
485
- case _ => return null
472
+ case _ => exceptionWithState( s " invalid value fractional part ' $fraction$nextWord ' " )
486
473
}
487
474
i += 1
488
475
case TRIM_BEFORE_UNIT => trimToNextState(b, UNIT_BEGIN )
489
476
case UNIT_BEGIN =>
490
477
// Checks that only seconds can have the fractional part
491
478
if (b != 's' && fractionScale >= 0 ) {
492
- return null
479
+ exceptionWithState( s " ' $nextWord ' with fractional part is unsupported " )
493
480
}
494
481
if (isNegative) {
495
482
currentValue = - currentValue
@@ -533,26 +520,26 @@ object IntervalUtils {
533
520
} else if (s.matchAt(microsStr, i)) {
534
521
microseconds = Math .addExact(microseconds, currentValue)
535
522
i += microsStr.numBytes()
536
- } else return null
537
- case _ => return null
523
+ } else exceptionWithState( s " invalid unit ' $nextWord ' " )
524
+ case _ => exceptionWithState( s " invalid unit ' $nextWord ' " )
538
525
}
539
526
} catch {
540
- case _ : ArithmeticException => return null
527
+ case e : ArithmeticException => exceptionWithState(e.getMessage, e)
541
528
}
542
529
state = UNIT_SUFFIX
543
530
case UNIT_SUFFIX =>
544
531
b match {
545
532
case 's' => state = UNIT_END
546
533
case ' ' => state = TRIM_BEFORE_SIGN
547
- case _ => return null
534
+ case _ => exceptionWithState( s " invalid unit suffix ' $nextWord ' " )
548
535
}
549
536
i += 1
550
537
case UNIT_END =>
551
538
b match {
552
539
case ' ' =>
553
540
i += 1
554
541
state = TRIM_BEFORE_SIGN
555
- case _ => return null
542
+ case _ => exceptionWithState( s " invalid unit suffix ' $nextWord ' " )
556
543
}
557
544
}
558
545
}
@@ -562,7 +549,6 @@ object IntervalUtils {
562
549
new CalendarInterval (months, days, microseconds)
563
550
case _ => null
564
551
}
565
-
566
552
result
567
553
}
568
554
0 commit comments