@@ -37,38 +37,74 @@ pub(crate) fn parse_interval_expression(
3737 Ok ( Box :: new ( IntervalExpr :: new ( interval) ) )
3838}
3939
40- /// Parse string intl Interval expression.
40+ /// Parse string into Interval expression.
41+ ///
42+ /// [@] <quantity> <unit> [quantity unit...] <direction>
43+ ///
44+ /// quantity: is a number (possibly signed)
45+ /// unit: is
46+ /// microsecond, millisecond, second,
47+ /// minute, hour, day, week, month, year,
48+ /// decade, century, millennium, or abbreviations or plurals of these units
49+ /// direction: can be ago or empty
50+ /// The at sign (@) is optional noise
51+ ///
4152/// Ref: https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-INPUT
4253fn parse_interval_literal (
4354 interval_str : & str ,
4455 location : SourceLocation ,
4556) -> Result < Interval , Box < Diagnostic > > {
46- if interval_str. is_empty ( ) {
47- return Err ( Diagnostic :: error ( "Invalid input syntax for type interval" )
57+ let tokens = interval_str. split_whitespace ( ) . collect :: < Vec < & str > > ( ) ;
58+ if tokens. is_empty ( ) {
59+ return Err ( Diagnostic :: error ( "Interval value can't be empty" )
60+ . add_help ( "Please check the documentation for help" )
4861 . with_location ( location)
4962 . as_boxed ( ) ) ;
5063 }
5164
5265 let mut position = 0 ;
5366 let mut interval = Interval :: default ( ) ;
54- let tokens = interval_str. split_whitespace ( ) . collect :: < Vec < & str > > ( ) ;
5567
68+ // Date part
69+ let mut has_millenniums = false ;
70+ let mut has_centuries = false ;
71+ let mut has_decades = false ;
5672 let mut has_years = false ;
5773 let mut has_months = false ;
74+ let mut has_week = false ;
5875 let mut has_days = false ;
76+
77+ // Time part
5978 let mut has_any_time_part = false ;
6079 let mut has_hours: bool = false ;
6180 let mut has_minutes = false ;
6281 let mut has_seconds = false ;
6382
83+ let mut has_direction_ago = false ;
84+
6485 while position < tokens. len ( ) {
6586 let token = tokens[ position] . trim ( ) ;
6687 if token. is_empty ( ) {
6788 position += 1 ;
6889 continue ;
6990 }
7091
71- // Parse Days, Months or Years
92+ if token == "ago" {
93+ if has_direction_ago {
94+ return Err (
95+ Diagnostic :: error ( "Interval can't contains more than one `ago`" )
96+ . add_help ( "Please keep at most only one `ago` direction" )
97+ . with_location ( location)
98+ . as_boxed ( ) ,
99+ ) ;
100+ }
101+
102+ has_direction_ago = true ;
103+ position += 1 ;
104+ continue ;
105+ }
106+
107+ // Parse Millienniums, Centuries, Decades, Years, Weeks, Months and Days
72108 if let Ok ( value) = token. parse :: < i64 > ( ) {
73109 // Consume value
74110 position += 1 ;
@@ -84,32 +120,101 @@ fn parse_interval_literal(
84120 // Parse the unit
85121 let mut maybe_unit = tokens[ position] ;
86122 let unit_lower = & maybe_unit. to_lowercase ( ) ;
87- maybe_unit = & unit_lower. as_str ( ) ;
123+ maybe_unit = unit_lower. as_str ( ) ;
124+
125+ if matches ! ( maybe_unit, "millennium" | "millenniums" ) {
126+ check_interval_value_and_unit ( & mut has_millenniums, value, maybe_unit, & location) ?;
127+ interval. years += value * 1000 ;
128+ position += 1 ;
129+ continue ;
130+ }
131+
132+ if matches ! ( maybe_unit, "century" | "centuries" ) {
133+ check_interval_value_and_unit ( & mut has_centuries, value, maybe_unit, & location) ?;
134+ interval. years += value * 100 ;
135+ position += 1 ;
136+ continue ;
137+ }
138+
139+ if matches ! ( maybe_unit, "decade" | "decades" ) {
140+ check_interval_value_and_unit ( & mut has_decades, value, maybe_unit, & location) ?;
141+ interval. years += value * 10 ;
142+ position += 1 ;
143+ continue ;
144+ }
88145
89146 if matches ! ( maybe_unit, "y" | "year" | "years" ) {
90- check_interval_value_and_unit ( & mut has_years, value, maybe_unit, location) ?;
91- interval. years = value;
147+ check_interval_value_and_unit ( & mut has_years, value, maybe_unit, & location) ?;
148+ interval. years + = value;
92149 position += 1 ;
93150 continue ;
94151 }
95152
96153 if matches ! ( maybe_unit, "m" | "mon" | "mons" | "months" ) {
97- check_interval_value_and_unit ( & mut has_months, value, maybe_unit, location) ?;
98- interval. months = value;
154+ check_interval_value_and_unit ( & mut has_months, value, maybe_unit, & location) ?;
155+ interval. months += value;
156+ position += 1 ;
157+ continue ;
158+ }
159+
160+ if matches ! ( maybe_unit, "w" | "week" | "weeks" ) {
161+ check_interval_value_and_unit ( & mut has_week, value, maybe_unit, & location) ?;
162+ interval. days += value * 7 ;
99163 position += 1 ;
100164 continue ;
101165 }
102166
103167 if matches ! ( maybe_unit, "d" | "day" | "days" ) {
104- check_interval_value_and_unit ( & mut has_days, value, maybe_unit, location) ?;
105- interval. days = value;
168+ check_interval_value_and_unit ( & mut has_days, value, maybe_unit, & location) ?;
169+ interval. days + = value;
106170 position += 1 ;
107171 continue ;
108172 }
109173
110174 if matches ! ( maybe_unit, "h" | "hour" | "hours" ) {
111- check_interval_value_and_unit ( & mut has_any_time_part, value, maybe_unit, location) ?;
112- interval. hours = value;
175+ check_interval_value_and_unit ( & mut has_hours, value, maybe_unit, & location) ?;
176+ has_any_time_part = true ;
177+ interval. hours += value;
178+ position += 1 ;
179+ continue ;
180+ }
181+
182+ if matches ! ( maybe_unit, "minute" | "minutes" ) {
183+ check_interval_value_and_unit ( & mut has_minutes, value, maybe_unit, & location) ?;
184+ has_any_time_part = true ;
185+ interval. minutes += value;
186+ position += 1 ;
187+ continue ;
188+ }
189+ }
190+
191+ // Parse Seconds
192+ if let Ok ( value) = token. parse :: < f64 > ( ) {
193+ // Consume value
194+ position += 1 ;
195+
196+ if position >= tokens. len ( ) {
197+ return Err ( Diagnostic :: error ( & format ! (
198+ "Missing interval unit after value {value}" ,
199+ ) )
200+ . with_location ( location)
201+ . as_boxed ( ) ) ;
202+ }
203+
204+ // Parse the unit
205+ let mut maybe_unit = tokens[ position] ;
206+ let unit_lower = & maybe_unit. to_lowercase ( ) ;
207+ maybe_unit = unit_lower. as_str ( ) ;
208+
209+ if matches ! ( maybe_unit, "second" | "seconds" ) {
210+ check_interval_value_and_unit (
211+ & mut has_seconds,
212+ value as i64 ,
213+ maybe_unit,
214+ & location,
215+ ) ?;
216+ has_any_time_part = true ;
217+ interval. seconds += value;
113218 position += 1 ;
114219 continue ;
115220 }
@@ -124,7 +229,7 @@ fn parse_interval_literal(
124229 . as_boxed ( ) ) ;
125230 }
126231
127- // Parse Seconds, Minutes or Hours
232+ // Parse the optional time part without explicit unit markings ( Seconds, Minutes or Hours)
128233 if token. contains ( ':' ) {
129234 if has_any_time_part {
130235 return Err (
@@ -135,15 +240,15 @@ fn parse_interval_literal(
135240 }
136241
137242 let time_parts: Vec < & str > = token. split ( ':' ) . collect ( ) ;
138- if time_parts. len ( ) != 3 && time_parts . len ( ) != 2 {
243+ if ! matches ! ( time_parts. len( ) , 2 | 3 ) {
139244 return Err ( Diagnostic :: error ( "Invalid input syntax for type interval" )
140245 . with_location ( location)
141246 . as_boxed ( ) ) ;
142247 }
143248
144249 match time_parts[ 0 ] . parse :: < i64 > ( ) {
145250 Ok ( hours) => {
146- check_interval_value_and_unit ( & mut has_hours, hours, time_parts[ 0 ] , location) ?;
251+ check_interval_value_and_unit ( & mut has_hours, hours, time_parts[ 0 ] , & location) ?;
147252 interval. hours = hours;
148253 }
149254 Err ( _) => {
@@ -159,7 +264,7 @@ fn parse_interval_literal(
159264 & mut has_minutes,
160265 minutes,
161266 time_parts[ 1 ] ,
162- location,
267+ & location,
163268 ) ?;
164269 interval. minutes = minutes;
165270 }
@@ -177,7 +282,7 @@ fn parse_interval_literal(
177282 & mut has_seconds,
178283 seconds as i64 ,
179284 time_parts[ 2 ] ,
180- location,
285+ & location,
181286 ) ?;
182287 interval. seconds = seconds;
183288 }
@@ -200,14 +305,19 @@ fn parse_interval_literal(
200305 . as_boxed ( ) ) ;
201306 }
202307
308+ // ago directon negates all the fields
309+ if has_direction_ago {
310+ interval = interval. mul ( -1 ) . unwrap_or ( interval) ;
311+ }
312+
203313 Ok ( interval)
204314}
205315
206316fn check_interval_value_and_unit (
207317 is_used_twice : & mut bool ,
208318 interval_value : i64 ,
209319 unit_name : & str ,
210- location : SourceLocation ,
320+ location : & SourceLocation ,
211321) -> Result < ( ) , Box < Diagnostic > > {
212322 if !* is_used_twice {
213323 * is_used_twice = true ;
@@ -219,14 +329,14 @@ fn check_interval_value_and_unit(
219329 "Interval value for unit `{unit_name}` is out of the range" ,
220330 ) )
221331 . add_help ( "Interval value must be in range from -170_000_000 to 170_000_000" )
222- . with_location ( location)
332+ . with_location ( * location)
223333 . as_boxed ( ) ) ;
224334 }
225335
226336 Err ( Diagnostic :: error ( & format ! (
227337 "Can't use the same interval unit `{unit_name}` twice" ,
228338 ) )
229- . with_location ( location)
339+ . with_location ( * location)
230340 . as_boxed ( ) )
231341}
232342
@@ -247,6 +357,56 @@ mod tests {
247357 }
248358 }
249359
360+ #[ test]
361+ fn valid_weeks ( ) {
362+ let inputs = [
363+ "2 w" ,
364+ "2 week" ,
365+ "2 weeks" ,
366+ "1 week 7 day" ,
367+ "1 w 7 d" ,
368+ "1 weeks 7 days" ,
369+ ] ;
370+
371+ for input in inputs {
372+ let parse_result = parse_interval_literal ( input, SourceLocation :: default ( ) ) ;
373+ assert ! ( parse_result. is_ok( ) ) ;
374+
375+ if let Ok ( interval) = parse_result {
376+ assert_eq ! ( interval. days, 14 ) ;
377+ }
378+ }
379+ }
380+
381+ #[ test]
382+ fn valid_seconds ( ) {
383+ let inputs = [ "10.1 second" ] ;
384+
385+ for input in inputs {
386+ let parse_result = parse_interval_literal ( input, SourceLocation :: default ( ) ) ;
387+ assert ! ( parse_result. is_ok( ) ) ;
388+
389+ if let Ok ( interval) = parse_result {
390+ assert_eq ! ( interval. seconds, 10.1 ) ;
391+ }
392+ }
393+ }
394+
395+ #[ test]
396+ fn ago_direction ( ) {
397+ let parse_result = parse_interval_literal ( "1 y 1 m 1 w 1 d" , SourceLocation :: default ( ) ) ;
398+ assert ! ( parse_result. is_ok( ) ) ;
399+
400+ let parse_result_with_ago =
401+ parse_interval_literal ( "1 y 1 m 1 w 1 d ago" , SourceLocation :: default ( ) ) ;
402+ assert ! ( parse_result_with_ago. is_ok( ) ) ;
403+
404+ assert_eq ! (
405+ parse_result. ok( ) . unwrap( ) . mul( -1 ) . unwrap( ) ,
406+ parse_result_with_ago. ok( ) . unwrap( )
407+ ) ;
408+ }
409+
250410 #[ test]
251411 fn invalid_time ( ) {
252412 let inputs = [ "1 h 1:00:00" , "1 h 1 h" ] ;
0 commit comments