Skip to content

Commit 767a17a

Browse files
committed
feat(interval_parse/postgres.rs): allow users to parse interval from a postgres interval string
1 parent caa9e42 commit 767a17a

File tree

1 file changed

+371
-0
lines changed

1 file changed

+371
-0
lines changed

src/interval_parse/postgres.rs

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
use pg_interval::Interval;
2+
use super::parse_error::ParseError;
3+
use interval_norm::IntervalNorm;
4+
5+
use super::{
6+
scale_date,
7+
scale_time,
8+
DAYS_PER_MONTH,
9+
MONTHS_PER_YEAR,
10+
SECONDS_PER_MIN,
11+
HOURS_PER_DAY,
12+
MINUTES_PER_HOUR,
13+
MICROS_PER_SECOND
14+
};
15+
16+
impl Interval {
17+
pub fn from_postgres<'a>(iso_str: &'a str) -> Result<Interval,ParseError> {
18+
let mut delim = vec!("years", "months", "mons", "days", "hours", "minutes", "seconds");
19+
let mut time_tokens = iso_str.split(" ").collect::<Vec<&str>>();
20+
// clean up empty values caused by n spaces between values.
21+
time_tokens.retain(|&token| token != "");
22+
// since there might not be space between the delim and the
23+
// value we need to scan each token.
24+
let mut final_tokens = Vec::with_capacity(time_tokens.len());
25+
for token in time_tokens {
26+
if is_token_alphanumeric(token)? {
27+
let (val, unit) = split_token(token)?;
28+
final_tokens.push(val);
29+
final_tokens.push(unit);
30+
} else {
31+
final_tokens.push(token.to_owned());
32+
}
33+
}
34+
if final_tokens.len() % 2 != 0 {
35+
return Err(ParseError::from_invalid_interval("Invalid amount tokens were found."));
36+
}
37+
// Consume our tokens and build up the
38+
// normalized interval.
39+
let mut val = 0.0;
40+
let mut is_numeric = true;
41+
let mut interval = IntervalNorm::default();
42+
for token in final_tokens {
43+
if is_numeric {
44+
val = token.parse::<f64>()?;
45+
is_numeric = false;
46+
} else {
47+
consume_token(&mut interval, val, token, &mut delim)?;
48+
is_numeric = true;
49+
}
50+
}
51+
interval.try_into_interval()
52+
}
53+
}
54+
55+
/// Does the token contain both alphabetic and numeric characters?
56+
fn is_token_alphanumeric<'a>(val: &'a str) -> Result<bool, ParseError> {
57+
let mut has_numeric = false;
58+
let mut has_alpha = false;
59+
for character in val.chars() {
60+
if character.is_numeric() || character == '-' || character == '.' {
61+
has_numeric = true;
62+
} else if character.is_alphabetic() {
63+
has_alpha = true;
64+
} else {
65+
return Err(
66+
ParseError::from_invalid_interval("String can only contain alpha numeric characters.")
67+
);
68+
}
69+
}
70+
Ok(has_numeric && has_alpha)
71+
}
72+
73+
/// Split the token into two tokens as they might of not been
74+
/// seperated by a space.
75+
fn split_token<'a>(val: &'a str) -> Result<(String, String), ParseError> {
76+
let mut is_numeric_done = false;
77+
let mut value = String::new();
78+
let mut delim = String::new();
79+
for character in val.chars() {
80+
if (character.is_numeric() || character == '-' || character == '.') && !is_numeric_done {
81+
value.push(character);
82+
} else if character.is_alphabetic() {
83+
is_numeric_done = true;
84+
delim.push(character)
85+
} else {
86+
return Err(
87+
ParseError::from_invalid_interval("String can only contain alpha numeric characters.")
88+
);
89+
}
90+
}
91+
Ok((value, delim))
92+
}
93+
94+
/// Consume the token parts and add to the normalized interval.
95+
fn consume_token<'a>(
96+
interval: &mut IntervalNorm,
97+
val: f64,
98+
delim: String,
99+
delim_list: &mut Vec<&'a str>
100+
) -> Result<(), ParseError> {
101+
// Unlike iso8601 the delimiter can only appear once
102+
// so we need to check if the token can be found in
103+
// the deliminator list.
104+
if delim_list.contains(&&*delim) {
105+
match &*delim {
106+
"years" => {
107+
let (year, month) = scale_date(val, MONTHS_PER_YEAR);
108+
interval.years += year;
109+
interval.months += month;
110+
delim_list.retain(|x| *x != "years");
111+
Ok(())
112+
},
113+
"months" | "mons" => {
114+
let (month, day) = scale_date(val, DAYS_PER_MONTH);
115+
interval.months += month;
116+
interval.days += day;
117+
delim_list.retain(|x| *x != "months" && *x != "mons");
118+
Ok(())
119+
},
120+
"days" => {
121+
let (days, hours) = scale_date(val, HOURS_PER_DAY);
122+
interval.days += days;
123+
interval.hours += hours as i64;
124+
delim_list.retain(|x| *x != "days");
125+
Ok(())
126+
},
127+
"hours" => {
128+
let (hours, minutes) = scale_time(val, MINUTES_PER_HOUR);
129+
interval.hours += hours;
130+
interval.minutes += minutes;
131+
delim_list.retain(|x| *x != "hours");
132+
Ok(())
133+
},
134+
"minutes" => {
135+
let (minutes, seconds) = scale_time(val, SECONDS_PER_MIN);
136+
interval.minutes += minutes;
137+
interval.seconds += seconds;
138+
delim_list.retain(|x| *x != "minutes");
139+
Ok(())
140+
},
141+
"seconds" => {
142+
let(seconds, microseconds) = scale_time(val, MICROS_PER_SECOND);
143+
interval.seconds += seconds;
144+
interval.microseconds += microseconds;
145+
delim_list.retain(|x| *x != "seconds");
146+
Ok(())
147+
}
148+
_ => {
149+
Err(
150+
ParseError::from_invalid_interval("Invalid deliminator.")
151+
)
152+
}
153+
}
154+
} else {
155+
Err(
156+
ParseError::from_invalid_interval("Invalid deliminator2")
157+
)
158+
}
159+
}
160+
161+
#[cfg(test)]
162+
mod tests {
163+
use pg_interval::Interval;
164+
165+
#[test]
166+
fn test_from_postgres_1() {
167+
let interval = Interval::from_postgres("1 years").unwrap();
168+
let interval_exp = Interval::new(12, 0, 0);
169+
assert_eq!(interval, interval_exp);
170+
}
171+
172+
#[test]
173+
fn test_from_postgres_2() {
174+
let interval = Interval::from_postgres("1years").unwrap();
175+
let interval_exp = Interval::new(12, 0, 0);
176+
assert_eq!(interval, interval_exp);
177+
}
178+
179+
#[test]
180+
fn test_from_postgres_3() {
181+
let interval = Interval::from_postgres("1 years 1 months").unwrap();
182+
let interval_exp = Interval::new(13, 0, 0);
183+
assert_eq!(interval, interval_exp);
184+
}
185+
186+
#[test]
187+
fn test_from_postgres_4() {
188+
let interval = Interval::from_postgres("1years 1months").unwrap();
189+
let interval_exp = Interval::new(13, 0, 0);
190+
assert_eq!(interval, interval_exp);
191+
}
192+
193+
194+
#[test]
195+
fn test_from_postgres_5() {
196+
let interval = Interval::from_postgres("1 years 1 mons 1 days").unwrap();
197+
let interval_exp = Interval::new(13, 1, 0);
198+
assert_eq!(interval, interval_exp);
199+
}
200+
201+
#[test]
202+
fn test_from_postgres_6() {
203+
let interval = Interval::from_postgres("1years 1mons 1days").unwrap();
204+
let interval_exp = Interval::new(13, 1, 0);
205+
assert_eq!(interval, interval_exp);
206+
}
207+
208+
#[test]
209+
fn test_from_postgres_7() {
210+
let interval = Interval::from_postgres("1 years 1 months 1 days 1 hours").unwrap();
211+
let interval_exp = Interval::new(13, 1, 3600000000);
212+
assert_eq!(interval, interval_exp);
213+
}
214+
215+
#[test]
216+
fn test_from_postgres_8() {
217+
let interval = Interval::from_postgres("1years 1months 1days 1hours").unwrap();
218+
let interval_exp = Interval::new(13, 1, 3600000000);
219+
assert_eq!(interval, interval_exp);
220+
}
221+
222+
223+
#[test]
224+
fn test_from_postgres_9() {
225+
let interval = Interval::from_postgres("1 years 1 months 1 days 1 hours 10 minutes").unwrap();
226+
let interval_exp = Interval::new(13, 1, 4200000000);
227+
assert_eq!(interval, interval_exp);
228+
}
229+
230+
231+
#[test]
232+
fn test_from_postgres_10() {
233+
let interval = Interval::from_postgres("1 years 1 months 1 days 1 hours 10 minutes 15 seconds").unwrap();
234+
let interval_exp = Interval::new(13, 1, 4215000000);
235+
assert_eq!(interval, interval_exp);
236+
}
237+
238+
#[test]
239+
fn test_from_postgres_11() {
240+
let interval = Interval::from_postgres("1 hours").unwrap();
241+
let interval_exp = Interval::new(0, 0, 3600000000);
242+
assert_eq!(interval, interval_exp);
243+
}
244+
245+
#[test]
246+
fn test_from_postgres_12() {
247+
let interval = Interval::from_postgres("1 hours 10 minutes").unwrap();
248+
let interval_exp = Interval::new(0, 0, 4200000000);
249+
assert_eq!(interval, interval_exp);
250+
}
251+
252+
253+
#[test]
254+
fn test_from_postgres_13() {
255+
let interval = Interval::from_postgres("1 hours 10 minutes 15 seconds").unwrap();
256+
let interval_exp = Interval::new(0, 0, 4215000000);
257+
assert_eq!(interval, interval_exp);
258+
}
259+
260+
#[test]
261+
fn test_from_postgres_14() {
262+
let interval = Interval::from_postgres("-1 years").unwrap();
263+
let interval_exp = Interval::new(-12, 0, 0);
264+
assert_eq!(interval, interval_exp);
265+
}
266+
267+
#[test]
268+
fn test_from_postgres_15() {
269+
let interval = Interval::from_postgres("-1years").unwrap();
270+
let interval_exp = Interval::new(-12, 0, 0);
271+
assert_eq!(interval, interval_exp);
272+
}
273+
274+
275+
#[test]
276+
fn test_from_postgres_16() {
277+
let interval = Interval::from_postgres("-1 years -1 months").unwrap();
278+
let interval_exp = Interval::new(-13, 0, 0);
279+
assert_eq!(interval, interval_exp);
280+
}
281+
282+
#[test]
283+
fn test_from_postgres_17() {
284+
let interval = Interval::from_postgres("-1 years -1months -1 days").unwrap();
285+
let interval_exp = Interval::new(-13, -1, 0);
286+
assert_eq!(interval, interval_exp);
287+
}
288+
289+
#[test]
290+
fn test_from_postgres_18() {
291+
let interval = Interval::from_postgres("-1 years -1 months -1 days -1 hours").unwrap();
292+
let interval_exp = Interval::new(-13, -1, -3600000000);
293+
assert_eq!(interval, interval_exp);
294+
}
295+
296+
#[test]
297+
fn test_from_postgres_19() {
298+
let interval = Interval::from_postgres("-1 years -1 months -1days -1hours -10minutes").unwrap();
299+
let interval_exp = Interval::new(-13, -1, -4200000000);
300+
assert_eq!(interval, interval_exp);
301+
}
302+
303+
#[test]
304+
fn test_from_postgres_20() {
305+
let interval = Interval::from_postgres("-1years -1 mons -1 days -1hours -10minutes -15seconds").unwrap();
306+
let interval_exp = Interval::new(-13, -1, -4215000000);
307+
assert_eq!(interval, interval_exp);
308+
}
309+
310+
#[test]
311+
fn test_from_postgres_21() {
312+
let interval = Interval::from_postgres("-1 hours").unwrap();
313+
let interval_exp = Interval::new(0, 0, -3600000000);
314+
assert_eq!(interval, interval_exp);
315+
}
316+
317+
#[test]
318+
fn test_from_postgres_22() {
319+
let interval = Interval::from_postgres("-1 hours -10minutes").unwrap();
320+
let interval_exp = Interval::new(0, 0, -4200000000);
321+
assert_eq!(interval, interval_exp);
322+
}
323+
324+
#[test]
325+
fn test_from_postgres_23() {
326+
let interval = Interval::from_postgres("-1 hours -10minutes -15 seconds").unwrap();
327+
let interval_exp = Interval::new(0, 0, -4215000000);
328+
assert_eq!(interval, interval_exp);
329+
}
330+
331+
332+
#[test]
333+
fn test_from_postgres_24() {
334+
let interval = Interval::from_postgres("years 1");
335+
assert_eq!(interval.is_err(), true);
336+
}
337+
338+
#[test]
339+
fn test_from_postgres_25() {
340+
let interval = Interval::from_postgres("- years");
341+
assert_eq!(interval.is_err(), true);
342+
}
343+
344+
#[test]
345+
fn test_from_postgres_26() {
346+
let interval = Interval::from_postgres("10");
347+
assert_eq!(interval.is_err(), true);
348+
}
349+
350+
#[test]
351+
fn test_from_postgres_27() {
352+
let interval = Interval::from_postgres("1.2 years").unwrap();
353+
let interval_exp = Interval::new(14, 0, 0);
354+
assert_eq!(interval, interval_exp);
355+
}
356+
357+
#[test]
358+
fn test_from_postgres_28() {
359+
let interval = Interval::from_postgres("1.2 months").unwrap();
360+
let interval_exp = Interval::new(1, 6, 0);
361+
assert_eq!(interval, interval_exp);
362+
}
363+
364+
#[test]
365+
fn test_from_postgres_29() {
366+
let interval = Interval::from_postgres("1.2 seconds").unwrap();
367+
let interval_exp = Interval::new(0, 0, 1_200_000);
368+
assert_eq!(interval, interval_exp);
369+
}
370+
371+
}

0 commit comments

Comments
 (0)