1use {
2 super::{Interval, IntervalError, DAY, HOUR, MINUTE, SECOND},
3 crate::{
4 ast::{Expr, ToSql},
5 parse_sql::parse_interval,
6 result::Result,
7 translate::translate_expr,
8 },
9};
10
11impl Interval {
12 pub fn parse(s: &str) -> Result<Self> {
13 let parsed = parse_interval(s)?;
14
15 match translate_expr(&parsed)? {
16 Expr::Interval {
17 expr,
18 leading_field,
19 last_field,
20 } => {
21 let literal = match expr.as_ref() {
22 Expr::Literal(literal) => literal.to_sql(),
23 _ => {
24 return Err(IntervalError::ParseSupportedOnlyLiteral { expr: *expr }.into())
25 }
26 };
27
28 Interval::try_from_str(&literal, leading_field, last_field)
29 }
30 _ => Err(IntervalError::Unreachable.into()),
31 }
32 }
33
34 pub fn to_sql_str(&self) -> String {
35 match self {
36 Interval::Month(v) => {
37 let v = *v;
38 let (sign, v) = if v < 0 { ("-", -v) } else { ("", v) };
39
40 let year = v / 12;
41 let month = v % 12;
42
43 match (year, month) {
44 (_, 0) if year != 0 => format!("'{}{}' YEAR", sign, year),
45 (0, _) => format!("'{}{}' MONTH", sign, month),
46 _ => format!("'{}{}-{}' YEAR TO MONTH", sign, year, month),
47 }
48 }
49 Interval::Microsecond(v) => {
50 let v = *v;
51 let (sign, v) = if v < 0 { ("-", -v) } else { ("", v) };
52
53 let day = v / DAY;
54 let hour = (v % DAY) / HOUR;
55 let minute = (v % HOUR) / MINUTE;
56 let second = (v % MINUTE) / SECOND;
57 let microsecond = v % SECOND;
58
59 macro_rules! micro {
60 () => {
61 format!("{:06}", microsecond).trim_end_matches('0')
62 };
63 }
64
65 macro_rules! f {
66 ($template: literal; $( $value: expr )*; $from_to: literal) => {
67 format!("'{}{}' {}", sign, format!($template, $( $value ),*), $from_to)
68 };
69
70 (DAY $template: literal) => {
71 f!($template; day; "DAY")
72 };
73 (HOUR $template: literal) => {
74 f!($template; hour; "HOUR")
75 };
76 (MINUTE $template: literal) => {
77 f!($template; minute; "MINUTE")
78 };
79 (SECOND $template: literal) => {
80 f!($template; second; "SECOND")
81 };
82 (MICRO $template: literal) => {
83 f!($template; second micro!(); "SECOND")
84 };
85
86 (MINUTE TO SECOND $template: literal) => {
88 f!($template; minute second; "MINUTE TO SECOND")
89 };
90 (MINUTE TO MICRO $template: literal) => {
91 f!($template; minute second micro!(); "MINUTE TO SECOND")
92 };
93
94 (HOUR TO MINUTE $template: literal) => {
96 f!($template; hour minute; "HOUR TO MINUTE")
97 };
98 (HOUR TO SECOND $template: literal) => {
99 f!($template; hour minute second; "HOUR TO SECOND")
100 };
101 (HOUR TO MICRO $template: literal) => {
102 f!($template; hour minute second micro!(); "HOUR TO SECOND")
103 };
104
105 (DAY TO HOUR $template: literal) => {
107 f!($template; day hour; "DAY TO HOUR")
108 };
109 (DAY TO MINUTE $template: literal) => {
110 f!($template; day hour minute; "DAY TO MINUTE")
111 };
112 (DAY TO SECOND $template: literal) => {
113 f!($template; day hour minute second; "DAY TO SECOND")
114 };
115 (DAY TO MICRO $template: literal) => {
116 f!($template; day hour minute second micro!(); "DAY TO SECOND")
117 };
118 }
119
120 match (day, hour, minute, second, microsecond) {
121 (_, 0, 0, 0, 0) if day != 0 => f!(DAY "{}"),
122 (0, _, 0, 0, 0) if hour != 0 => f!(HOUR "{}"),
123 (0, 0, _, 0, 0) if minute != 0 => f!(MINUTE "{}"),
124 (0, 0, 0, _, 0) if second != 0 => f!(SECOND "{}"),
125 (0, 0, 0, _, _) if microsecond != 0 => f!(MICRO "{}.{}"),
126
127 (0, 0, _, _, 0) => f!(MINUTE TO SECOND "{:02}:{:02}"),
129 (0, 0, _, _, _) => f!(MINUTE TO MICRO "{:02}:{:02}.{}"),
130
131 (0, _, _, 0, 0) => f!(HOUR TO MINUTE "{:02}:{:02}"),
133 (0, _, _, _, 0) => f!(HOUR TO SECOND "{:02}:{:02}:{:02}"),
134 (0, _, _, _, _) => f!(HOUR TO MICRO "{:02}:{:02}:{:02}.{}"),
135
136 (_, _, 0, 0, 0) => f!(DAY TO HOUR "{} {}"),
138 (_, _, _, 0, 0) => f!(DAY TO MINUTE "{} {:02}:{:02}"),
139 (_, _, _, _, 0) => f!(DAY TO SECOND "{} {:02}:{:02}:{:02}"),
140 (_, _, _, _, _) => f!(DAY TO MICRO "{} {:02}:{:02}:{:02}.{}"),
141 }
142 }
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::Interval;
150
151 #[test]
152 fn parse() {
153 macro_rules! test {
154 ($( $value: literal $duration: ident ),* => $result: literal $from_to: tt) => {
155 let interval = interval!($( $value $duration ),*);
156 let interval_str = format!("'{}' {}", $result, stringify!($from_to));
157
158 let expected = Interval::parse(interval_str.as_str());
159 assert_eq!(Ok(interval), expected);
160 assert_eq!(interval.to_sql_str(), interval_str);
161 };
162 ($( $value: literal $duration: ident ),* => $result: literal $from: tt TO $to: tt) => {
163 let interval = interval!($( $value $duration ),*);
164 let interval_str = format!(
165 "'{}' {} TO {}",
166 $result,
167 stringify!($from),
168 stringify!($to),
169 );
170
171 let expected = Interval::parse(interval_str.as_str());
172 assert_eq!(Ok(interval), expected);
173 assert_eq!(interval.to_sql_str(), interval_str);
174 };
175 }
176
177 macro_rules! interval {
178 ($value: literal $duration:ident) => {
179 Interval::$duration($value)
180 };
181 ($interval: expr ; $value: literal $duration:ident) => {
182 $interval.add(&Interval::$duration($value)).unwrap()
183 };
184 ($v0: literal $d0: ident, $( $v1: literal $d1: ident ),*) => {
185 interval!(
186 Interval::$d0($v0) ;
187 $( $v1 $d1 ),*
188 )
189 };
190 ($interval: expr ; $v0: literal $d0: ident, $( $v1: literal $d1: ident ),*) => {
191 interval!(
192 $interval.add(&Interval::$d0($v0)).unwrap() ;
193 $( $v1 $d1 ),*
194 )
195 };
196 }
197
198 test!(33 years => "33" YEAR);
200 test!(-33 years => "-33" YEAR);
201 test!(14 months => "1-2" YEAR TO MONTH);
202 test!(-33 months => "-2-9" YEAR TO MONTH);
203 test!(12 months => "1" YEAR);
204 test!(11 months => "11" MONTH);
205 test!(-3 months => "-3" MONTH);
206 test!(33 years, 11 months => "33-11" YEAR TO MONTH);
207 test!(1 years, 100 months => "9-4" YEAR TO MONTH);
208
209 test!(102 days => "102" DAY);
211 test!(-20 days => "-20" DAY);
212 test!(30 hours => "1 6" DAY TO HOUR);
213 test!(24 hours => "1" DAY);
214 test!(3 hours => "3" HOUR);
215 test!(-5 hours => "-5" HOUR);
216 test!(350 minutes => "05:50" HOUR TO MINUTE);
217 test!(600 minutes => "10" HOUR);
218 test!(59 minutes => "59" MINUTE);
219 test!(3 minutes => "3" MINUTE);
220 test!(-9 minutes => "-9" MINUTE);
221 test!(7298 seconds => "02:01:38" HOUR TO SECOND);
222 test!(98 seconds => "01:38" MINUTE TO SECOND);
223 test!(120 seconds => "2" MINUTE);
224 test!(30 seconds => "30" SECOND);
225 test!(3 seconds => "3" SECOND);
226 test!(-37 seconds => "-37" SECOND);
227 test!(1234 milliseconds => "1.234" SECOND);
228 test!(-234 milliseconds => "-0.234" SECOND);
229 test!(1234 microseconds => "0.001234" SECOND);
230
231 test!(11 minutes, 30 seconds => "11:30" MINUTE TO SECOND);
233 test!(-11 minutes, -39 seconds => "-11:39" MINUTE TO SECOND);
234 test!(11 minutes, 36540 microseconds => "11:00.03654" MINUTE TO SECOND);
235 test!(-30 minutes, -9876 microseconds => "-30:00.009876" MINUTE TO SECOND);
236 test!(11 minutes, 30 seconds, 36540 microseconds => "11:30.03654" MINUTE TO SECOND);
237
238 test!(11 hours, 50 minutes => "11:50" HOUR TO MINUTE);
240 test!(-20 hours, -39 minutes => "-20:39" HOUR TO MINUTE);
241 test!(23 hours, 59 seconds => "23:00:59" HOUR TO SECOND);
242 test!(-23 hours, -59 seconds => "-23:00:59" HOUR TO SECOND);
243 test!(7 hours, 30040 microseconds => "07:00:00.03004" HOUR TO SECOND);
244 test!(-7 hours, -30040 microseconds => "-07:00:00.03004" HOUR TO SECOND);
245 test!(23 hours, 12 minutes, 59 seconds => "23:12:59" HOUR TO SECOND);
246 test!(23 hours, 12 minutes, 300 microseconds => "23:12:00.0003" HOUR TO SECOND);
247 test!(
248 23 hours, 12 minutes, 9 seconds, 300 microseconds =>
249 "23:12:09.0003" HOUR TO SECOND
250 );
251
252 test!(1 days, 1 hours => "1 1" DAY TO HOUR);
254 test!(-1 days, -7 hours => "-1 7" DAY TO HOUR);
255 test!(30 days, 5 minutes => "30 00:05" DAY TO MINUTE);
256 test!(-1 days, -130 minutes => "-1 02:10" DAY TO MINUTE);
257 test!(30 days, 100 seconds => "30 00:01:40" DAY TO SECOND);
258 test!(-5 days, -3800 seconds => "-5 01:03:20" DAY TO SECOND);
259 test!(1 days, 1 microseconds => "1 00:00:00.000001" DAY TO SECOND);
260 test!(-1 days, -1 microseconds => "-1 00:00:00.000001" DAY TO SECOND);
261 test!(100 days, 2 hours, 3 minutes => "100 02:03" DAY TO MINUTE);
262 test!(2 days, 20 hours, 3 seconds => "2 20:00:03" DAY TO SECOND);
263 test!(2 days, 20 hours, 234 milliseconds => "2 20:00:00.234" DAY TO SECOND);
264 test!(
265 30 days, 30 hours, 100 seconds, 3 microseconds =>
266 "31 06:01:40.000003" DAY TO SECOND
267 );
268 }
269}