| use scanlex::{Scanner,Token}; |
| use errors::*; |
| use types::*; |
| |
| // when we parse dates, there's often a bit of time parsed.. |
| #[derive(Clone,Copy,Debug)] |
| enum TimeKind { |
| Formal, |
| Informal, |
| AmPm(bool), |
| Unknown, |
| } |
| |
| pub struct DateParser<'a> { |
| s: Scanner<'a>, |
| direct: Direction, |
| maybe_time: Option<(u32,TimeKind)>, |
| pub american: bool, // 9/11, not 20/03 |
| } |
| |
| impl <'a> DateParser<'a> { |
| |
| pub fn new(text: &'a str) -> DateParser<'a> { |
| DateParser{ |
| s: Scanner::new(text).no_float(), |
| direct: Direction::Here, |
| maybe_time: None, |
| american: false |
| } |
| } |
| |
| pub fn american_date(mut self) -> DateParser<'a> { |
| self.american = true; |
| self |
| } |
| |
| fn iso_date(&mut self, y: u32) -> DateResult<DateSpec> { |
| let month = self.s.get_int::<u32>()?; |
| self.s.get_ch_matching(&['-'])?; |
| let day = self.s.get_int::<u32>()?; |
| Ok(DateSpec::absolute(y,month,day)) |
| } |
| |
| fn informal_date(&mut self, day_or_month: u32) -> DateResult<DateSpec> { |
| let month_or_day = self.s.get_int::<u32>()?; |
| let (d,m) = if self.american { |
| (month_or_day, day_or_month) |
| } else { |
| (day_or_month, month_or_day) |
| }; |
| Ok(if self.s.peek() == '/' { |
| self.s.get(); |
| let y = self.s.get_int::<u32>()?; |
| let y = if y < 100 { // pivot (1940, 2040) |
| if y > 40 { |
| 1900 + y |
| } else { |
| 2000 + y |
| } |
| } else { |
| y |
| }; |
| DateSpec::absolute(y,m,d) |
| } else { |
| DateSpec::FromName(ByName::from_day_month(d,m,self.direct)) |
| }) |
| } |
| |
| fn parse_date(&mut self) -> DateResult<Option<DateSpec>> { |
| let mut t = self.s.next().or_err("empty date string")?; |
| |
| let sign = if t.is_char() && t.as_char().unwrap() == '-' { |
| true |
| } else { |
| false |
| }; |
| if sign { |
| t = self.s.next().or_err("nothing after '-'")?; |
| } |
| if let Some(name) = t.as_iden() { |
| let shortcut = match name { |
| "now" => Some(0), |
| "yesterday" => Some(-1), |
| "tomorrow" => Some(1), |
| _ => None |
| }; |
| if let Some(skip) = shortcut { |
| return Ok(Some( |
| DateSpec::skip(time_unit("day").unwrap(), skip) |
| )); |
| } else // maybe next or last? |
| if let Some(d) = Direction::from_name(&name) { |
| self.direct = d; |
| } |
| } |
| if self.direct != Direction::Here { |
| t = self.s.next().or_err("nothing after last/next")?; |
| } |
| Ok(match t { |
| Token::Iden(ref name) => { |
| let name = name.to_lowercase(); |
| // maybe weekday or month name? |
| if let Some(by_name) = ByName::from_name(&name,self.direct) { |
| // however, MONTH _might_ be followed by DAY, YEAR |
| if let Some(month) = by_name.as_month() { |
| let t = self.s.get(); |
| if t.is_integer() { |
| let day = t.to_int_result::<u32>()?; |
| return Ok(Some(if self.s.peek() == ',' { |
| self.s.get_char()?; // eat ',' |
| let year = self.s.get_int::<u32>()?; |
| DateSpec::absolute(year,month,day) |
| } else { // MONTH DAY is like DAY MONTH (tho no time!) |
| DateSpec::from_day_month(day, month, self.direct) |
| })); |
| } |
| } |
| Some(DateSpec::FromName(by_name)) |
| } else { |
| return date_result("expected week day or month name"); |
| } |
| }, |
| Token::Int(_) => { |
| let n = t.to_int_result::<u32>()?; |
| let t = self.s.get(); |
| if t.finished() { // must be a year... |
| return Ok(Some(DateSpec::absolute(n,1,1))); |
| } |
| match t { |
| Token::Iden(ref name) => { |
| let day = n; |
| let name = name.to_lowercase(); |
| if let Some(month) = month_name(&name) { |
| if let Ok(year) = self.s.get_int::<u32>() { |
| // 4 July 2017 |
| Some(DateSpec::absolute(year,month,day)) |
| } else { |
| // 4 July |
| Some(DateSpec::from_day_month(day, month, self.direct)) |
| } |
| } else |
| if let Some(u) = time_unit(&name) { // '2 days' |
| let mut n = n as i32; |
| if sign { |
| n = -n; |
| } else { |
| let t = self.s.get(); |
| let got_ago = if let Some(name) = t.as_iden() { |
| if name == "ago" { |
| n = -n; |
| true |
| } else { |
| return date_result("only expected 'ago'"); |
| } |
| } else { |
| false |
| }; |
| if ! got_ago { |
| if let Some(h) = t.to_integer() { |
| self.maybe_time = Some((h as u32, TimeKind::Unknown)); |
| } |
| } |
| } |
| Some(DateSpec::skip(u, n)) |
| } else |
| if name == "am" || name == "pm" { |
| self.maybe_time = Some((n, TimeKind::AmPm(name == "pm"))); |
| None |
| } else { |
| return date_result("expected month or time unit"); |
| } |
| }, |
| Token::Char(ch) => { |
| match ch { |
| '-' => Some(self.iso_date(n)?), |
| '/' => Some(self.informal_date(n)?), |
| ':' | '.' => { |
| let kind = if ch == ':' { |
| TimeKind::Formal |
| } else { |
| TimeKind::Informal |
| }; |
| self.maybe_time = Some((n,kind)); |
| None |
| } |
| _ => return date_result(&format!("unexpected char {:?}",ch)), |
| } |
| }, |
| _ => return date_result(&format!("unexpected token {:?}",t)), |
| |
| } |
| }, |
| _ => return date_result(&format!("not expected token {:?}",t)), |
| }) |
| |
| } |
| |
| fn formal_time(&mut self, hour: u32) -> DateResult<TimeSpec> { |
| let min = self.s.get_int::<u32>()?; |
| // minute may be followed by [:secs][am|pm] |
| let mut tnext = None; |
| let sec = if let Some(t) = self.s.next() { |
| if let Some(ch) = t.as_char() { |
| if ch != ':' { |
| return date_result("expecting ':'"); |
| } |
| self.s.get_int::<u32>()? |
| } else { |
| tnext = Some(t); |
| 0 |
| } |
| } else { |
| 0 |
| }; |
| // we found seconds, look ahead |
| if tnext.is_none() { |
| tnext = self.s.next(); |
| } |
| //println!("token {:?}", tnext); |
| if tnext.is_none() { |
| Ok(TimeSpec::new(hour,min,sec)) |
| } else |
| if let Some(ch) = tnext.as_ref().unwrap().as_char() { |
| let expecting_offset = match ch { |
| '+' | '-' => true, |
| 'Z' => false, |
| _ => return date_result("expected +/- or Z") |
| }; |
| let offset = if expecting_offset { |
| let h = self.s.get_int::<u32>()?; |
| let m = if self.s.peek() == ':' { |
| self.s.nextch(); |
| self.s.get_int::<u32>()? |
| } else { // but what about 0030 etc? |
| 0 |
| }; |
| let res = 60*(m + 60*h); |
| (res as i64)*if ch == '-' {-1} else {1} |
| } else { |
| 0 |
| }; |
| Ok(TimeSpec::new_with_offset(hour,min,sec,offset)) |
| } else { |
| // can only be am/pm |
| let hour = if let Some(t) = tnext { |
| let name = t.to_iden_result()?; |
| DateParser::am_pm(&name,hour)? |
| } else { |
| hour |
| }; |
| Ok(TimeSpec::new(hour,min,sec)) |
| } |
| } |
| |
| fn informal_time(&mut self, hour: u32) -> DateResult<TimeSpec> { |
| let min = self.s.get_int::<u32>()?; |
| let hour = if let Some(t) = self.s.next() { |
| let name = t.to_iden_result()?; |
| DateParser::am_pm(&name,hour)? |
| } else { |
| hour |
| }; |
| Ok(TimeSpec::new(hour,min,0)) |
| } |
| |
| fn am_pm(name: &str, mut hour: u32) -> DateResult<u32> { |
| if name == "pm" { |
| hour += 12; |
| } else |
| if name != "am" { |
| return date_result("expected am or pm"); |
| } |
| Ok(hour) |
| } |
| |
| fn hour_time(name: &str, hour: u32) -> DateResult<TimeSpec> { |
| Ok(TimeSpec::new (DateParser::am_pm(name,hour)?,0,0)) |
| } |
| |
| fn parse_time(&mut self) -> DateResult<Option<TimeSpec>> { |
| // here the date parser looked ahead and saw an hour followed by some separator |
| if let Some(hour_sep) = self.maybe_time { // didn't see a separator, so look... |
| let (h,mut kind) = hour_sep; |
| if let TimeKind::Unknown = kind { |
| kind = match self.s.get_char()? { |
| ':' => TimeKind::Formal, |
| '.' => TimeKind::Informal, |
| ch => return date_result(&format!("expected : or ., not {}", ch)), |
| }; |
| } |
| Ok(Some( |
| match kind { |
| TimeKind::Formal => self.formal_time(h)?, |
| TimeKind::Informal => self.informal_time(h)?, |
| TimeKind::AmPm(is_pm) => |
| DateParser::hour_time(if is_pm {"pm"} else {"am"},h)?, |
| TimeKind::Unknown => unreachable!(), |
| } |
| )) |
| } else { // no lookahead... |
| if self.s.peek() == 'T' { |
| self.s.nextch(); |
| } |
| let t = self.s.get(); |
| if t.finished() { |
| return Ok(None); |
| } |
| |
| let mut hour = t.to_int_result::<u32>()?; |
| Ok(Some(match self.s.get() { |
| Token::Char(ch) => match ch { |
| ':' => self.formal_time(hour)?, |
| '.' => self.informal_time(hour)?, |
| ch => return date_result(&format!("unexpected char {:?}",ch)) |
| }, |
| Token::Iden(name) => { |
| DateParser::hour_time(&name,hour)? |
| } |
| t => return date_result(&format!("unexpected token {:?}",t)) |
| })) |
| } |
| } |
| |
| pub fn parse(&mut self) -> DateResult<DateTimeSpec> { |
| let date = self.parse_date()?; |
| let time = self.parse_time()?; |
| Ok(DateTimeSpec{date: date, time: time}) |
| } |
| |
| } |