aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2016-05-12 14:07:24 -0700
committerAlex Crichton <alex@alexcrichton.com>2016-05-12 14:07:34 -0700
commitc7770015987e915b08604dd41c2188c2f3f5b644 (patch)
tree462d8a1ec82e166df64b395363fe71f0d390709f
parenta8223490c2e1563c90616ea6edbcadab5405dd51 (diff)
downloadmilf-rs-c7770015987e915b08604dd41c2188c2f3f5b644.tar.gz
milf-rs-c7770015987e915b08604dd41c2188c2f3f5b644.zip
Accept fractional seconds and timezones in datetime parsing
Closes #96
-rw-r--r--src/parser.rs140
1 files changed, 99 insertions, 41 deletions
diff --git a/src/parser.rs b/src/parser.rs
index 60767eb..265d7b5 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -631,9 +631,13 @@ impl<'a> Parser<'a> {
};
let end = self.next_pos();
let input = &self.input[start..end];
- let ret = if !is_float && !input.starts_with("+") &&
- !input.starts_with("-") && self.eat('-') {
- self.datetime(start, end + 1)
+ let ret = if decimal.is_none() &&
+ exponent.is_none() &&
+ !input.starts_with("+") &&
+ !input.starts_with("-") &&
+ start + 4 == end &&
+ self.eat('-') {
+ self.datetime(start)
} else {
let input = match (decimal, exponent) {
(None, None) => prefix,
@@ -658,7 +662,9 @@ impl<'a> Parser<'a> {
ret
}
- fn integer(&mut self, start: usize, allow_leading_zeros: bool,
+ fn integer(&mut self,
+ start: usize,
+ allow_leading_zeros: bool,
allow_sign: bool) -> Option<String> {
let mut s = String::new();
if allow_sign {
@@ -745,52 +751,81 @@ impl<'a> Parser<'a> {
}
}
- fn datetime(&mut self, start: usize, end_so_far: usize) -> Option<Value> {
- let mut date = format!("{}", &self.input[start..end_so_far]);
- for _ in 0..15 {
- match self.cur.next() {
- Some((_, ch)) => date.push(ch),
- None => {
- self.errors.push(ParserError {
- lo: start,
- hi: end_so_far,
- desc: format!("malformed date literal"),
- });
- return None
+ fn datetime(&mut self, start: usize) -> Option<Value> {
+ // Up to `start` already contains the year, and we've eaten the next
+ // `-`, so we just resume parsing from there.
+
+ let mut valid = true;
+
+ // month
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+
+ // day
+ valid = valid && self.cur.next().map(|c| c.1) == Some('-');
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+
+ valid = valid && self.cur.next().map(|c| c.1) == Some('T');
+
+ // hour
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+
+ // minute
+ valid = valid && self.cur.next().map(|c| c.1) == Some(':');
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+
+ // second
+ valid = valid && self.cur.next().map(|c| c.1) == Some(':');
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+
+ // fractional seconds
+ if self.eat('.') {
+ valid = valid && digit(self.cur.next());
+ loop {
+ match self.cur.clone().next() {
+ Some((_, c)) if is_digit(c) => {
+ self.cur.next();
+ }
+ _ => break,
}
}
}
- let mut it = date.chars();
- let mut valid = true;
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(|c| c == '-').unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(|c| c == '-').unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(|c| c == 'T').unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(|c| c == ':').unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(|c| c == ':').unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(is_digit).unwrap_or(false);
- valid = valid && it.next().map(|c| c == 'Z').unwrap_or(false);
- if valid {
- Some(Value::Datetime(date.clone()))
+
+ // time zone
+ if !self.eat('Z') {
+ valid = valid && (self.eat('+') || self.eat('-'));
+
+ // hour
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+
+ // minute
+ valid = valid && self.cur.next().map(|c| c.1) == Some(':');
+ valid = valid && digit(self.cur.next());
+ valid = valid && digit(self.cur.next());
+ }
+
+ return if valid {
+ Some(Value::Datetime(self.input[start..self.next_pos()].to_string()))
} else {
+ let next = self.next_pos();
self.errors.push(ParserError {
lo: start,
- hi: start + date.len(),
+ hi: start + next,
desc: format!("malformed date literal"),
});
None
+ };
+
+ fn digit(val: Option<(usize, char)>) -> bool {
+ match val {
+ Some((_, c)) => is_digit(c),
+ None => false,
+ }
}
}
@@ -1521,4 +1556,27 @@ trimmed in raw strings.
[a]
", "redefinition of table `a`");
}
+
+ #[test]
+ fn datetimes() {
+ macro_rules! t {
+ ($actual:expr) => ({
+ let f = format!("foo = {}", $actual);
+ let mut p = Parser::new(&f);
+ let table = Table(p.parse().unwrap());
+ assert_eq!(table.lookup("foo").and_then(|k| k.as_datetime()),
+ Some($actual));
+ })
+ }
+
+ t!("2016-09-09T09:09:09Z");
+ t!("2016-09-09T09:09:09.0Z");
+ t!("2016-09-09T09:09:09.0+10:00");
+ t!("2016-09-09T09:09:09.01234567890-02:00");
+ bad!("foo = 2016-09-09T09:09:09.Z", "malformed date literal");
+ bad!("foo = 2016-9-09T09:09:09Z", "malformed date literal");
+ bad!("foo = 2016-09-09T09:09:09+2:00", "malformed date literal");
+ bad!("foo = 2016-09-09T09:09:09-2:00", "malformed date literal");
+ bad!("foo = 2016-09-09T09:09:09Z-2:00", "expected");
+ }
}