blob: 6d306ecf2480f491d35079837d8e1719be4a3e88 [file] [log] [blame]
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})
}
}