Skip to content

Commit f627488

Browse files
committed
refactor(*): wrote a manual impl of from iso 8601
This removes the need for the regex and lazy static dependency. This follows the postgres impl pretty closely so there shouldn't be any unexpected behavior not found the postgres impl already
1 parent 1947e72 commit f627488

File tree

6 files changed

+339
-21
lines changed

6 files changed

+339
-21
lines changed

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,4 @@ default = ["postgres"]
1515

1616
[dependencies]
1717
postgres = { version = "^0.15", optional=true }
18-
byteorder = { version = "1.2.4" }
19-
regex = "1"
20-
lazy_static = "1.3"
18+
byteorder = { version = "1.2.4" }

src/interval_norm.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use pg_interval::Interval;
2+
use interval_parse::parse_error::ParseError;
23

34
pub struct IntervalNorm {
45
pub years: i32,
@@ -43,6 +44,33 @@ impl<'a> From<&'a Interval> for IntervalNorm {
4344
}
4445

4546
impl IntervalNorm {
47+
pub fn try_into_interval(self) -> Result<Interval, ParseError> {
48+
let months = self.years.checked_mul(12)
49+
.and_then(|years| self.months.checked_add(years));
50+
let microseconds = self.hours.checked_mul(60)
51+
.and_then(|minutes| self.minutes.checked_add(minutes))
52+
.and_then(|minutes| minutes.checked_mul(60))
53+
.and_then(|seconds| self.seconds.checked_add(seconds))
54+
.and_then(|seconds| seconds.checked_mul(1_000_000))
55+
.and_then(|microseconds| self.microseconds.checked_add(microseconds));
56+
Ok(Interval {
57+
months: months.ok_or(ParseError::from_year_month("Invalid year/month interval overflow detected."))?,
58+
days: self.days,
59+
microseconds: microseconds.ok_or(ParseError::from_time("Invalid time interval overflow detected."))?
60+
})
61+
}
62+
63+
pub fn default() -> IntervalNorm {
64+
IntervalNorm {
65+
years: 0,
66+
months: 0,
67+
days: 0,
68+
hours: 0,
69+
minutes: 0,
70+
seconds: 0,
71+
microseconds: 0,
72+
}
73+
}
4674
/// Is all the values in the interval set to 0?
4775
pub fn is_zeroed(&self) -> bool {
4876
self.years == 0

src/interval_parse/iso_8601.rs

Lines changed: 269 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,273 @@
1-
use pg_interval::PgInterval;
1+
use pg_interval::Interval;
2+
use super::parse_error::ParseError;
3+
use interval_norm::IntervalNorm;
24

3-
impl PgInterval {
4-
pub fn from_iso<T: Into<String>>(iso_str: T) -> PgInterval {
5-
lazy_static!{
6-
static ref RE: Regex = Regex::new("P(?:(-?\d+?Y)?(-?\d+?M)?(-?\d+?D)?(?:T(-?\d+?H)?(-?\d+?M)?(-?\d+(?:[\.,]\d{0,6})?S)?)?)$").unwrap();
7-
}
8-
for interval in RE.captures_iter(iso_str.into()) {
9-
let year = caps.get(1).unwrap_or("0").as_str();
10-
let month = caps.get(2).unwrap_or("0").as_str();
11-
let day = caps.get(3).unwrap_or("0").as_str();
12-
let hours = caps.get(4).unwrap_or("0").as_str();
13-
let minutes = caps.get(5).unwrap_or("0").as_str();
14-
let seconds = caps.get(6).unwrap_or("0.0").as_str();
5+
static DAYS_PER_MONTH: i32 = 30;
6+
static MONTHS_PER_YEAR: i32 = 12;
7+
static SECONDS_PER_MIN: i32 = 60;
8+
static HOURS_PER_DAY: i32 = 24;
9+
static MINUTES_PER_HOUR: i32 = 60;
10+
static MICROS_PER_SECOND: i32 = 1_000_000;
11+
12+
13+
enum ParserCode {
14+
BADFORMAT,
15+
GOOD,
16+
DELIMFOUND
17+
}
1518

19+
impl Interval {
20+
pub fn from_iso<'a>(iso_str: &'a str) -> Result<Interval,ParseError> {
21+
let mut date_part = true;
22+
let delim = vec!('Y', 'M', 'D', 'H', 'S');
23+
let mut number = "".to_owned();
24+
let mut interval_norm = IntervalNorm::default();
25+
if iso_str.rfind('P').map_or(false, |v| v == 1) {
26+
Err(ParseError::from_invalid_interval("Invalid format must start with P."))
27+
} else if iso_str.len() < 2 {
28+
Err(ParseError::from_invalid_interval("Invalid format length is less than 2."))
29+
} else {
30+
for x in iso_str.chars() {
31+
if x == 'P' {
32+
continue;
33+
}
34+
if x == 'T' {
35+
date_part = false;
36+
continue;
37+
}
38+
let code = consume_number(&x, &mut number, &delim);
39+
match code {
40+
ParserCode::BADFORMAT => {
41+
return Err(ParseError::from_invalid_interval("Invalid format."));
42+
},
43+
ParserCode::GOOD => {
44+
continue;
45+
},
46+
ParserCode::DELIMFOUND => {
47+
let val = parse_number(&mut number)?;
48+
match x {
49+
'Y' => {
50+
let (year, month) = scale_date(val, MONTHS_PER_YEAR);
51+
interval_norm.years += year;
52+
interval_norm.months += month;
53+
},
54+
'M' => {
55+
if date_part {
56+
let (month, day) = scale_date(val, DAYS_PER_MONTH);
57+
interval_norm.months += month;
58+
interval_norm.days += day;
59+
} else {
60+
let (minutes, seconds) = scale_time(val, SECONDS_PER_MIN);
61+
interval_norm.minutes += minutes;
62+
interval_norm.seconds += seconds;
63+
}
64+
},
65+
'D' => {
66+
let (days, hours) = scale_date(val, HOURS_PER_DAY);
67+
interval_norm.days += days;
68+
interval_norm.hours += hours as i64;
69+
},
70+
'H' => {
71+
let (hours, minutes) = scale_time(val, MINUTES_PER_HOUR);
72+
interval_norm.hours += hours;
73+
interval_norm.minutes += minutes;
74+
},
75+
'S' => {
76+
let(seconds, microseconds) = scale_time(val, MICROS_PER_SECOND);
77+
interval_norm.seconds += seconds;
78+
interval_norm.microseconds += microseconds;
79+
},
80+
_ => {
81+
return Err(ParseError::from_invalid_interval("Invalid format unknown delimiter."));
82+
}
83+
}
84+
}
85+
}
1686
}
87+
interval_norm.try_into_interval()
88+
}
89+
}
90+
}
91+
92+
fn consume_number<'a>(
93+
val: &'a char,
94+
number: &'a mut String,
95+
delim: &'a Vec<char>) -> ParserCode {
96+
if val.is_digit(10) {
97+
number.push(*val);
98+
ParserCode::GOOD
99+
} else if number.len() == 0 && *val == '-' {
100+
number.push(*val);
101+
ParserCode::GOOD
102+
} else if number.len() != 0 && *val == '.' {
103+
number.push(*val);
104+
ParserCode::GOOD
105+
} else if delim.contains(&val) {
106+
ParserCode::DELIMFOUND
107+
} else {
108+
ParserCode::BADFORMAT
109+
}
110+
}
111+
112+
fn parse_number<'a> (number: &'a mut String) -> Result<f64, ParseError> {
113+
let parse_num = number.parse::<f64>()?;
114+
if parse_num > i32::max_value() as f64 {
115+
Err(ParseError::from_invalid_interval("Exceeded max value"))
116+
} else {
117+
*number = "".to_owned();
118+
Ok(parse_num)
119+
}
120+
}
121+
122+
fn scale_date(val: f64, scale: i32) -> (i32, i32) {
123+
if val.fract() == 0.0 {
124+
return (val.trunc() as i32, 0)
125+
} else {
126+
// matches postgres implementation of just truncating.
127+
let sub_value = (val.fract() * scale as f64) as i32;
128+
(val.trunc() as i32, sub_value)
129+
}
130+
}
131+
132+
fn scale_time(val: f64, scale: i32) -> (i64, i64) {
133+
if val.fract() == 0.0 {
134+
return (val.trunc() as i64, 0)
135+
} else {
136+
// matches postgres implementation of just truncating.
137+
let sub_value = (val.fract() * scale as f64) as i64;
138+
(val.trunc() as i64, sub_value)
139+
}
140+
}
141+
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use pg_interval::Interval;
146+
147+
#[test]
148+
fn test_iso_1() {
149+
let interval = Interval::from_iso("P1Y").unwrap();
150+
let interval_exp = Interval::new(12, 0, 0);
151+
assert_eq!(interval, interval_exp);
152+
}
153+
154+
/*
155+
#[test]
156+
fn test_8601_2() {
157+
let interval = Interval::new(13, 0, 0);
158+
let output = interval.to_iso_8601();
159+
assert_eq!(String::from("P1Y1M"), output);
160+
}
161+
162+
#[test]
163+
fn test_8601_3() {
164+
let interval = Interval::new(13, 1, 0);
165+
let output = interval.to_iso_8601();
166+
assert_eq!(String::from("P1Y1M1D"), output);
167+
}
168+
169+
#[test]
170+
fn test_8601_4() {
171+
let interval = Interval::new(13, 1, 3600000000);
172+
let output = interval.to_iso_8601();
173+
assert_eq!(String::from("P1Y1M1DT1H"), output);
174+
}
175+
176+
#[test]
177+
fn test_8601_5() {
178+
let interval = Interval::new(13, 1, 4200000000);
179+
let output = interval.to_iso_8601();
180+
assert_eq!(String::from("P1Y1M1DT1H10M"), output);
181+
}
182+
183+
#[test]
184+
fn test_8601_6() {
185+
let interval = Interval::new(13, 1, 4215000000);
186+
let output = interval.to_iso_8601();
187+
assert_eq!(String::from("P1Y1M1DT1H10M15S"), output);
17188
}
18-
}
189+
190+
#[test]
191+
fn test_8601_7() {
192+
let interval = Interval::new(0, 0, 3600000000);
193+
let output = interval.to_iso_8601();
194+
assert_eq!(String::from("PT1H"), output);
195+
}
196+
197+
#[test]
198+
fn test_8601_8() {
199+
let interval = Interval::new(0, 0, 4200000000);
200+
let output = interval.to_iso_8601();
201+
assert_eq!(String::from("PT1H10M"), output);
202+
}
203+
204+
#[test]
205+
fn test_8601_9() {
206+
let interval = Interval::new(0, 0, 4215000000);
207+
let output = interval.to_iso_8601();
208+
assert_eq!(String::from("PT1H10M15S"), output);
209+
}
210+
211+
#[test]
212+
fn test_8601_10() {
213+
let interval = Interval::new(-12, 0, 0);
214+
let output = interval.to_iso_8601();
215+
assert_eq!(String::from("P-1Y"), output);
216+
}
217+
218+
#[test]
219+
fn test_8601_11() {
220+
let interval = Interval::new(-13, 0, 0);
221+
let output = interval.to_iso_8601();
222+
assert_eq!(String::from("P-1Y-1M"), output);
223+
}
224+
225+
#[test]
226+
fn test_8601_12() {
227+
let interval = Interval::new(-13, -1, 0);
228+
let output = interval.to_iso_8601();
229+
assert_eq!(String::from("P-1Y-1M-1D"), output);
230+
}
231+
232+
#[test]
233+
fn test_8601_13() {
234+
let interval = Interval::new(-13, -1, -3600000000);
235+
let output = interval.to_iso_8601();
236+
assert_eq!(String::from("P-1Y-1M-1DT-1H"), output);
237+
}
238+
239+
#[test]
240+
fn test_8601_14() {
241+
let interval = Interval::new(-13, -1, -4200000000);
242+
let output = interval.to_iso_8601();
243+
assert_eq!(String::from("P-1Y-1M-1DT-1H-10M"), output);
244+
}
245+
246+
#[test]
247+
fn test_8601_15() {
248+
let interval = Interval::new(-13, -1, -4215000000);
249+
let output = interval.to_iso_8601();
250+
assert_eq!(String::from("P-1Y-1M-1DT-1H-10M-15S"), output);
251+
}
252+
253+
#[test]
254+
fn test_8601_16() {
255+
let interval = Interval::new(0, 0, -3600000000);
256+
let output = interval.to_iso_8601();
257+
assert_eq!(String::from("PT-1H"), output);
258+
}
259+
260+
#[test]
261+
fn test_8601_17() {
262+
let interval = Interval::new(0, 0, -4200000000);
263+
let output = interval.to_iso_8601();
264+
assert_eq!(String::from("PT-1H-10M"), output);
265+
}
266+
267+
#[test]
268+
fn test_8601_18() {
269+
let interval = Interval::new(0, 0, -4215000000);
270+
let output = interval.to_iso_8601();
271+
assert_eq!(String::from("PT-1H-10M-15S"), output);
272+
} */
273+
}

src/interval_parse/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
mod iso_8601;
1+
mod iso_8601;
2+
pub mod parse_error;

src/interval_parse/parse_error.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::num::{ParseIntError, ParseFloatError};
2+
3+
#[derive(Debug)]
4+
pub enum ParseError {
5+
ParseIntErr(ParseIntError),
6+
ParseFloatErr(ParseFloatError),
7+
InvalidYearMonth(String),
8+
InvalidTime(String),
9+
InvalidInterval(String)
10+
}
11+
12+
13+
impl<'a> ParseError {
14+
pub fn from_year_month(message: &'a str) -> ParseError {
15+
ParseError::InvalidYearMonth(String::from(message))
16+
}
17+
18+
pub fn from_time(message: &'a str) -> ParseError {
19+
ParseError::InvalidTime(String::from(message))
20+
}
21+
22+
pub fn from_invalid_interval(message: &'a str) -> ParseError {
23+
ParseError::InvalidInterval(String::from(message))
24+
}
25+
}
26+
27+
impl From<ParseIntError> for ParseError {
28+
fn from(error: ParseIntError) -> ParseError {
29+
ParseError::ParseIntErr(error)
30+
}
31+
}
32+
33+
impl From<ParseFloatError> for ParseError {
34+
fn from(error: ParseFloatError) -> ParseError {
35+
ParseError::ParseFloatErr(error)
36+
}
37+
}

0 commit comments

Comments
 (0)