Parse actual dates and make chrono datetimes

This commit is contained in:
Tiffany Bennett 2016-08-10 23:25:47 -04:00
parent 98dfcc7b2d
commit 05f87a3a74
5 changed files with 204 additions and 16 deletions

View file

@ -15,6 +15,7 @@ ircbot = ["irc", "glob"]
[dependencies]
rust-gmp = "0.3.2"
chrono = "0.2.25"
rustyline = { version = "0.2.3", optional = true }
irc = { version = "0.11.3", optional = true }
glob = { version = "0.2.11", optional = true }

View file

@ -20,6 +20,7 @@ pub struct Context {
units: HashMap<String, Value>,
aliases: HashMap<Unit, String>,
prefixes: Vec<(String, Value)>,
datepatterns: Vec<Vec<::unit_defs::DatePattern>>,
pub short_output: bool,
}
@ -118,6 +119,163 @@ fn to_string(rational: &Mpq) -> (bool, String) {
}
}
fn parse_date<I>(out: &mut ::chrono::format::parsed::Parsed,
date: &mut ::std::iter::Peekable<I>,
pat: &[::unit_defs::DatePattern]) -> Result<(), String>
where I: Iterator<Item=::unit_defs::Token>+Clone {
use unit_defs::{DatePattern, Token};
use chrono::Weekday;
let tok = date.peek().cloned();
macro_rules! numeric_match {
($name:expr, $digits:expr, $field:ident) => {
match tok {
Some(Token::Number(ref s, None, None)) if $digits == 0 || s.len() == $digits => {
let value = i32::from_str_radix(&**s, 10).unwrap();
out.$field = Some(value as _);
Ok(())
},
x => Err(format!("Expected {}-digit {}, got {:?}", $digits, $name, x))
}
}
}
let res = match pat.first() {
None if tok.is_some() => Err(format!("Expected EOF, got {:?}", tok.unwrap())),
None => return Ok(()),
Some(&DatePattern::Literal(ref l)) => match tok {
Some(Token::Ident(ref s)) if s == l => Ok(()),
x => Err(format!("Expected `{}`, got {:?}", l, x)),
},
Some(&DatePattern::Match(ref what)) => match &**what {
"fullyear" => numeric_match!("fullyear", 4, year),
"shortyear" => numeric_match!("shortyear", 2, year_mod_100),
"century" => numeric_match!("century", 2, year_div_100),
"monthnum" => numeric_match!("monthnum", 2, month),
"day" => numeric_match!("day", 2, day),
"hour12" => numeric_match!("hour12", 2, hour_mod_12),
"min" => numeric_match!("min", 2, minute),
"ordinal" => numeric_match!("ordinal", 3, ordinal),
"isoyear" => numeric_match!("isoyear", 4, isoyear),
"isoweek" => numeric_match!("isoweek", 2, isoweek),
"unix" => numeric_match!("unix", 0, timestamp),
"hour24" => match tok {
Some(Token::Number(ref s, None, None)) if s.len() == 2 => {
let value = u32::from_str_radix(&**s, 10).unwrap();
out.hour_div_12 = Some(value / 12);
out.hour_mod_12 = Some(value % 12);
Ok(())
},
x => Err(format!("Expected 2-digit hour24, got {:?}", x))
},
"sec" => match tok {
Some(Token::Number(ref s, None, None)) if s.len() == 2 => {
let value = u32::from_str_radix(&**s, 10).unwrap();
out.second = Some(value);
Ok(())
},
Some(Token::Number(ref s, Some(ref f), None)) if s.len() == 2 => {
let secs = u32::from_str_radix(&**s, 10).unwrap();
let nsecs = u32::from_str_radix(&**f, 10).unwrap() * 10u32.pow(9 - f.len() as u32);
out.second = Some(secs);
out.nanosecond = Some(nsecs);
Ok(())
},
x => Err(format!("Expected 2-digit sec, got {:?}", x))
},
"offset" => {
macro_rules! take {
($($pat: pat)|+) => {
match date.peek().cloned() {
$(Some($pat))|+ => date.next().unwrap(),
x => return Err(format!("Expected {}, got {:?}", stringify!($($pat)|+), x))
}
};
($pat:pat, $var:ident) => {
match date.peek().cloned() {
Some($pat) => {date.next(); $var},
x => return Err(format!("Expected {}, got {:?}", stringify!($pat), x))
}
}
}
let s = match take!(Token::Plus | Token::Minus) {
Token::Plus => 1, Token::Minus => -1, _ => panic!()
};
let h = take!(Token::Number(s, None, None), s);
let h = i32::from_str_radix(&*h, 10).unwrap();
take!(Token::Colon);
let m = take!(Token::Number(s, None, None), s);
let m = i32::from_str_radix(&*m, 10).unwrap();
out.offset = Some(s * (h*3600 + m*60));
Ok(())
},
"monthname" => match tok {
Some(Token::Ident(ref s)) => {
let res = match &*s.to_lowercase() {
"jan" | "january" => 1,
"feb" | "february" => 2,
"mar" | "march" => 3,
"apr" | "april" => 4,
"may" => 5,
"jun" | "june" => 6,
"jul" | "july" => 7,
"aug" | "august" => 8,
"sep" | "september" => 9,
"oct" | "october" => 10,
"nov" | "november" => 11,
"dec" | "december" => 12,
x => return Err(format!("Unknown month name: {}", x))
};
out.month = Some(res);
Ok(())
},
x => Err(format!("Expected month name, got {:?}", x))
},
"weekday" => match tok {
Some(Token::Ident(ref s)) => {
let res = match &*s.to_lowercase() {
"mon" | "monday" => Weekday::Mon,
"tue" | "tuesday" => Weekday::Tue,
"wed" | "wednesday" => Weekday::Wed,
"thu" | "thursday" => Weekday::Thu,
"fri" | "friday" => Weekday::Fri,
"sat" | "saturday" => Weekday::Sat,
"sun" | "sunday" => Weekday::Sun,
x => return Err(format!("Unknown weekday: {}", x))
};
out.weekday = Some(res);
Ok(())
},
x => Err(format!("Expected weekday, got {:?}", x))
},
x => Err(format!("Unknown match pattern `{}`", x))
},
Some(&DatePattern::Optional(ref pats)) => {
let mut iter = date.clone();
match parse_date(out, &mut iter, &pats[..]) {
Ok(()) => *date = iter,
Err(_) => ()
};
Ok(())
}
Some(&DatePattern::Dash) => match tok {
Some(Token::Minus) => Ok(()),
x => Err(format!("Expected `-`, got {:?}", x))
},
Some(&DatePattern::Colon) => match tok {
Some(Token::Colon) => Ok(()),
x => Err(format!("Expected `:`, got {:?}", x))
},
Some(&DatePattern::Error(ref err)) => Err(err.clone())
};
date.next();
match res {
Ok(()) => parse_date(out, date, &pat[1..]),
Err(e) => Err(e)
}
}
fn btree_merge<K: ::std::cmp::Ord+Clone, V:Clone, F:Fn(&V, &V) -> Option<V>>(
left: &BTreeMap<K, V>, right: &BTreeMap<K, V>, merge_func: F
) -> BTreeMap<K, V> {
@ -408,7 +566,32 @@ impl Context {
let num = &num * &exp;
Ok(Value::new(num))
},
Expr::Date(_) => unimplemented!(),
Expr::Date(ref date) => {
use chrono::format::Parsed;
use chrono::{DateTime, UTC, FixedOffset};
for pat in &self.datepatterns {
let attempt = || {
let mut parsed = Parsed::new();
try!(parse_date(&mut parsed, &mut date.iter().cloned().peekable(), &pat[..]));
let offset = parsed.to_fixed_offset().unwrap_or(FixedOffset::east(0));
let time = parsed.to_naive_time();
let date = parsed.to_naive_date();
match (time, date) {
(Ok(time), Ok(date)) =>
Ok(DateTime::<FixedOffset>::from_utc(date.and_time(time), offset)),
(Ok(time), Err(_)) =>
Ok(UTC::now().with_timezone(&offset).date().and_time(time).unwrap()),
_ => Err(format!("Failed to construct a useful datetime"))
}
};
match attempt() {
Ok(_datetime) => unimplemented!(),
Err(_) => ()
}
}
unimplemented!()
},
Expr::Neg(ref expr) => self.eval(&**expr).and_then(|mut v| {
v.0 = -v.0;
Ok(v)
@ -648,6 +831,7 @@ impl Context {
units: HashMap::new(),
aliases: HashMap::new(),
prefixes: Vec::new(),
datepatterns: Vec::new(),
short_output: false,
};
@ -681,7 +865,7 @@ impl Context {
},
Err(e) => println!("Prefix {} is malformed: {}", name, e)
},
Def::DatePattern(_) => unimplemented!(),
Def::DatePattern(ref pat) => ctx.datepatterns.push(pat.clone()),
Def::Error(ref err) => println!("Def {}: {}", name, err),
};
}

View file

@ -27,6 +27,7 @@ println!("{}", one_line(&mut ctx, "kWh / year -> W").unwrap());
*/
extern crate gmp;
extern crate chrono;
pub mod unit_defs;
pub mod eval;

View file

@ -8,7 +8,6 @@ pub enum Token {
Comment(usize),
Ident(String),
Number(String, Option<String>, Option<String>),
Date(String),
Quote(String),
Slash,
Colon,
@ -29,6 +28,7 @@ pub enum Token {
Minus,
Asterisk,
DashArrow,
Hash,
ImaginaryUnit,
Error(String),
}
@ -233,17 +233,7 @@ impl<'a> Iterator for TokenIterator<'a> {
}
Token::Quote(buf)
},
'#' => {
let mut buf = String::new();
loop {
match self.0.next() {
None | Some('\n') => return Some(Token::Error(format!("Unexpected newline or EOF"))),
Some('#') => break,
Some(c) => buf.push(c),
}
}
Token::Date(buf)
},
'#' => Token::Hash,
x => {
let mut buf = String::new();
buf.push(x);
@ -267,7 +257,7 @@ pub type Iter<'a> = Peekable<TokenIterator<'a>>;
pub enum Expr {
Unit(String),
Const(String, Option<String>, Option<String>),
Date(String),
Date(Vec<Token>),
Frac(Box<Expr>, Box<Expr>),
Mul(Vec<Expr>),
Pow(Box<Expr>, Box<Expr>),
@ -320,7 +310,16 @@ fn parse_term(mut iter: &mut Iter) -> Expr {
x => Expr::Error(format!("Expected ), got {:?}", x))
}
},
Token::Date(date) => Expr::Date(date),
Token::Hash => {
let mut out = vec![];
loop {
match iter.next().unwrap() {
Token::Hash => break,
x => out.push(x),
}
}
Expr::Date(out)
},
x => Expr::Error(format!("Expected term, got {:?}", x))
}
}

View file

@ -4703,3 +4703,6 @@ HMS[time] :=
}
datepattern fullyear-monthnum-day'T'hour24:min:sec
datepattern fullyear-monthnum-day
datepattern hour24:min:sec offset