1717
1818package org .apache .spark .sql .catalyst .util
1919
20- import java .util .Locale
2120import java .util .concurrent .TimeUnit
2221
2322import scala .util .control .NonFatal
2423
25- import org .apache .spark .sql .catalyst .parser .{CatalystSqlParser , ParseException }
2624import org .apache .spark .sql .catalyst .util .DateTimeConstants ._
2725import org .apache .spark .sql .types .Decimal
2826import org .apache .spark .unsafe .types .{CalendarInterval , UTF8String }
2927
3028object IntervalUtils {
31- import IntervalUnit ._
3229
3330 def getYears (interval : CalendarInterval ): Int = {
3431 interval.months / MONTHS_PER_YEAR
@@ -87,42 +84,14 @@ object IntervalUtils {
8784 Decimal (result, 18 , 6 )
8885 }
8986
90- /**
91- * Converts a string to [[CalendarInterval ]] case-insensitively.
92- *
93- * @throws IllegalArgumentException if the input string is not in valid interval format.
94- */
95- def fromString (str : String ): CalendarInterval = {
96- if (str == null ) throw new IllegalArgumentException (" Interval string cannot be null" )
97- try {
98- CatalystSqlParser .parseInterval(str)
99- } catch {
100- case e : ParseException =>
101- val ex = new IllegalArgumentException (s " Invalid interval string: $str\n " + e.message)
102- ex.setStackTrace(e.getStackTrace)
103- throw ex
104- }
105- }
106-
107- /**
108- * A safe version of `fromString`. It returns null for invalid input string.
109- */
110- def safeFromString (str : String ): CalendarInterval = {
111- try {
112- fromString(str)
113- } catch {
114- case _ : IllegalArgumentException => null
115- }
116- }
117-
11887 private def toLongWithRange (
119- fieldName : IntervalUnit ,
88+ fieldName : String ,
12089 s : String ,
12190 minValue : Long ,
12291 maxValue : Long ): Long = {
12392 val result = if (s == null ) 0L else s.toLong
12493 require(minValue <= result && result <= maxValue,
125- s " ${ fieldName.toString} $result outside range [ $minValue, $maxValue] " )
94+ s " $fieldName $result outside range [ $minValue, $maxValue] " )
12695
12796 result
12897 }
@@ -138,8 +107,8 @@ object IntervalUtils {
138107 require(input != null , " Interval year-month string must be not null" )
139108 def toInterval (yearStr : String , monthStr : String ): CalendarInterval = {
140109 try {
141- val years = toLongWithRange(YEAR , yearStr, 0 , Integer .MAX_VALUE ).toInt
142- val months = toLongWithRange(MONTH , monthStr, 0 , 11 ).toInt
110+ val years = toLongWithRange(" year " , yearStr, 0 , Integer .MAX_VALUE ).toInt
111+ val months = toLongWithRange(" month " , monthStr, 0 , 11 ).toInt
143112 val totalMonths = Math .addExact(Math .multiplyExact(years, 12 ), months)
144113 new CalendarInterval (totalMonths, 0 , 0 )
145114 } catch {
@@ -166,7 +135,7 @@ object IntervalUtils {
166135 * adapted from HiveIntervalDayTime.valueOf
167136 */
168137 def fromDayTimeString (s : String ): CalendarInterval = {
169- fromDayTimeString(s, DAY , SECOND )
138+ fromDayTimeString(s, " day " , " second " )
170139 }
171140
172141 private val dayTimePattern =
@@ -181,7 +150,7 @@ object IntervalUtils {
181150 * - HOUR TO (MINUTE|SECOND)
182151 * - MINUTE TO SECOND
183152 */
184- def fromDayTimeString (input : String , from : IntervalUnit , to : IntervalUnit ): CalendarInterval = {
153+ def fromDayTimeString (input : String , from : String , to : String ): CalendarInterval = {
185154 require(input != null , " Interval day-time string must be not null" )
186155 assert(input.length == input.trim.length)
187156 val m = dayTimePattern.pattern.matcher(input)
@@ -192,33 +161,33 @@ object IntervalUtils {
192161 val days = if (m.group(2 ) == null ) {
193162 0
194163 } else {
195- toLongWithRange(DAY , m.group(3 ), 0 , Integer .MAX_VALUE ).toInt
164+ toLongWithRange(" day " , m.group(3 ), 0 , Integer .MAX_VALUE ).toInt
196165 }
197166 var hours : Long = 0L
198167 var minutes : Long = 0L
199168 var seconds : Long = 0L
200- if (m.group(5 ) != null || from == MINUTE ) { // 'HH:mm:ss' or 'mm:ss minute'
201- hours = toLongWithRange(HOUR , m.group(5 ), 0 , 23 )
202- minutes = toLongWithRange(MINUTE , m.group(6 ), 0 , 59 )
203- seconds = toLongWithRange(SECOND , m.group(7 ), 0 , 59 )
169+ if (m.group(5 ) != null || from == " minute " ) { // 'HH:mm:ss' or 'mm:ss minute'
170+ hours = toLongWithRange(" hour " , m.group(5 ), 0 , 23 )
171+ minutes = toLongWithRange(" minute " , m.group(6 ), 0 , 59 )
172+ seconds = toLongWithRange(" second " , m.group(7 ), 0 , 59 )
204173 } else if (m.group(8 ) != null ) { // 'mm:ss.nn'
205- minutes = toLongWithRange(MINUTE , m.group(6 ), 0 , 59 )
206- seconds = toLongWithRange(SECOND , m.group(7 ), 0 , 59 )
174+ minutes = toLongWithRange(" minute " , m.group(6 ), 0 , 59 )
175+ seconds = toLongWithRange(" second " , m.group(7 ), 0 , 59 )
207176 } else { // 'HH:mm'
208- hours = toLongWithRange(HOUR , m.group(6 ), 0 , 23 )
209- minutes = toLongWithRange(SECOND , m.group(7 ), 0 , 59 )
177+ hours = toLongWithRange(" hour " , m.group(6 ), 0 , 23 )
178+ minutes = toLongWithRange(" second " , m.group(7 ), 0 , 59 )
210179 }
211180 // Hive allow nanosecond precision interval
212181 var secondsFraction = parseNanos(m.group(9 ), seconds < 0 )
213182 to match {
214- case HOUR =>
183+ case " hour " =>
215184 minutes = 0
216185 seconds = 0
217186 secondsFraction = 0
218- case MINUTE =>
187+ case " minute " =>
219188 seconds = 0
220189 secondsFraction = 0
221- case SECOND =>
190+ case " second " =>
222191 // No-op
223192 case _ =>
224193 throw new IllegalArgumentException (
@@ -236,21 +205,40 @@ object IntervalUtils {
236205 }
237206 }
238207
208+ private val isYear : String => Boolean =
209+ y => """ y((r)|(rs)|(ear)|(ears))?""" .r.pattern.matcher(y).matches()
210+ private val isMonth : String => Boolean =
211+ mon => """ mon((s)|(th)|(ths))?""" .r.pattern.matcher(mon).matches()
212+ private val isWeek : String => Boolean =
213+ w => """ w((eek)|(eeks))?""" .r.pattern.matcher(w).matches()
214+ private val isDay : String => Boolean =
215+ d => """ d((ay)|(ays))?""" .r.pattern.matcher(d).matches()
216+ private val isHour : String => Boolean =
217+ h => """ h((r)|(rs)|(our)|(ours))?""" .r.pattern.matcher(h).matches()
218+ private val isMinute : String => Boolean =
219+ m => """ m((in)|(ins)|(inute)|(inutes))?""" .r.pattern.matcher(m).matches()
220+ private val isSecond : String => Boolean =
221+ s => """ s((ec)|(ecs)|(econd)|(econds))?""" .r.pattern.matcher(s).matches()
222+ private val isMs : String => Boolean =
223+ ms => """ (ms((ec)|(ecs)|(econds))?|(millisecond)[s]?)""" .r.pattern.matcher(ms).matches()
224+ private val isUs : String => Boolean =
225+ us => """ (us((ec)|(ecs)|(econds))?|(microsecond)[s]?)""" .r.pattern.matcher(us).matches()
226+
239227 /**
240228 * Converts a string with multiple value unit pairs to [[CalendarInterval ]] case-insensitively.
241229 *
242230 * @throws IllegalArgumentException if the input string is not in valid interval format.
243231 */
244232 def fromMultiUnitsString (str : String ): CalendarInterval = {
245233 if (str == null ) throw new IllegalArgumentException (" Interval multi unit string cannot be null" )
234+ var months : Int = 0
235+ var days : Int = 0
236+ var us : Long = 0L
237+ var array = """ -\s+""" .r.replaceAllIn(str.stripPrefix(" interval " ), " -" )
238+ .split(" \\ s+" ).filter(_ != " +" ).toList
239+ require(array.length % 2 == 0 , " Interval string should be value and unit pairs" )
246240
247241 try {
248- var months : Int = 0
249- var days : Int = 0
250- var us : Long = 0L
251- var array = """ -\s+""" .r.replaceAllIn(str.stripPrefix(" interval " ), " -" )
252- .split(" \\ s+" ).filter(_ != " +" ).toList
253- require(array.length % 2 == 0 , " Interval string should be value and unit pairs" )
254242 while (array.nonEmpty) {
255243 array match {
256244 case valueStr :: unit :: tail =>
@@ -287,7 +275,7 @@ object IntervalUtils {
287275 }
288276 }
289277
290- def fromUnitStrings (units : Array [IntervalUnit ], fields : Array [String ]): CalendarInterval = {
278+ def fromUnitStrings (units : Array [String ], fields : Array [String ]): CalendarInterval = {
291279 assert(units.length == fields.length)
292280 var months : Int = 0
293281 var days : Int = 0
@@ -296,26 +284,26 @@ object IntervalUtils {
296284 while (i < units.length) {
297285 try {
298286 units(i) match {
299- case YEAR =>
287+ case " year " =>
300288 months = Math .addExact(months, Math .multiplyExact(fields(i).toInt, 12 ))
301- case MONTH =>
289+ case " month " =>
302290 months = Math .addExact(months, fields(i).toInt)
303- case WEEK =>
291+ case " week " =>
304292 days = Math .addExact(days, Math .multiplyExact(fields(i).toInt, 7 ))
305- case DAY =>
293+ case " day " =>
306294 days = Math .addExact(days, fields(i).toInt)
307- case HOUR =>
295+ case " hour " =>
308296 val hoursUs = Math .multiplyExact(fields(i).toLong, MICROS_PER_HOUR )
309297 microseconds = Math .addExact(microseconds, hoursUs)
310- case MINUTE =>
298+ case " minute " =>
311299 val minutesUs = Math .multiplyExact(fields(i).toLong, MICROS_PER_MINUTE )
312300 microseconds = Math .addExact(microseconds, minutesUs)
313- case SECOND =>
301+ case " second " =>
314302 microseconds = Math .addExact(microseconds, parseSecondNano(fields(i)))
315- case MILLISECOND =>
303+ case " millisecond " =>
316304 val millisUs = Math .multiplyExact(fields(i).toLong, MICROS_PER_MILLIS )
317305 microseconds = Math .addExact(microseconds, millisUs)
318- case MICROSECOND =>
306+ case " microsecond " =>
319307 microseconds = Math .addExact(microseconds, fields(i).toLong)
320308 }
321309 } catch {
@@ -334,7 +322,7 @@ object IntervalUtils {
334322 val alignedStr = if (nanosStr.length < maxNanosLen) {
335323 (nanosStr + " 000000000" ).substring(0 , maxNanosLen)
336324 } else nanosStr
337- val nanos = toLongWithRange(NANOSECOND , alignedStr, 0L , 999999999L )
325+ val nanos = toLongWithRange(" nanosecond " , alignedStr, 0L , 999999999L )
338326 val micros = nanos / NANOS_PER_MICROS
339327 if (isNegative) - micros else micros
340328 } else {
@@ -348,7 +336,7 @@ object IntervalUtils {
348336 private def parseSecondNano (secondNano : String ): Long = {
349337 def parseSeconds (secondsStr : String ): Long = {
350338 toLongWithRange(
351- SECOND ,
339+ " second " ,
352340 secondsStr,
353341 Long .MinValue / MICROS_PER_SECOND ,
354342 Long .MaxValue / MICROS_PER_SECOND ) * MICROS_PER_SECOND
@@ -651,39 +639,3 @@ object IntervalUtils {
651639 result
652640 }
653641}
654-
655- object IntervalUnit extends Enumeration {
656- type IntervalUnit = Value
657-
658- val YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND = Value
659-
660- def fromString (unit : String ): IntervalUnit = unit.toLowerCase(Locale .ROOT ) match {
661- case " year" | " years" => YEAR
662- case " month" | " months" => MONTH
663- case " week" | " weeks" => WEEK
664- case " day" | " days" => DAY
665- case " hour" | " hours" => HOUR
666- case " minute" | " minutes" => MINUTE
667- case " second" | " seconds" => SECOND
668- case " millisecond" | " milliseconds" => MILLISECOND
669- case " microsecond" | " microseconds" => MICROSECOND
670- case u => throw new IllegalArgumentException (s " Invalid interval unit: $u" )
671- }
672-
673- val isYear : String => Boolean =
674- y => """ y((r)|(rs)|(ear)|(ears))?""" .r.pattern.matcher(y).matches()
675- val isMonth : String => Boolean =
676- mon => """ mon((s)|(th)|(ths))?""" .r.pattern.matcher(mon).matches()
677- val isWeek : String => Boolean = w => """ w((eek)|(eeks))?""" .r.pattern.matcher(w).matches()
678- val isDay : String => Boolean = d => """ d((ay)|(ays))?""" .r.pattern.matcher(d).matches()
679- val isHour : String => Boolean =
680- h => """ h((r)|(rs)|(our)|(ours))?""" .r.pattern.matcher(h).matches()
681- val isMinute : String => Boolean =
682- m => """ m((in)|(ins)|(inute)|(inutes))?""" .r.pattern.matcher(m).matches()
683- val isSecond : String => Boolean =
684- s => """ s((ec)|(ecs)|(econd)|(econds))?""" .r.pattern.matcher(s).matches()
685- val isMs : String => Boolean =
686- ms => """ (ms((ec)|(ecs)|(econds))?|(millisecond)[s]?)""" .r.pattern.matcher(ms).matches()
687- val isUs : String => Boolean =
688- us => """ (us((ec)|(ecs)|(econds))?|(microsecond)[s]?)""" .r.pattern.matcher(us).matches()
689- }
0 commit comments