1 use scanlex::{Scanner,Token};
2 use errors::*;
3 use types::*;
4 
5 // when we parse dates, there's often a bit of time parsed..
6 #[derive(Clone,Copy,Debug)]
7 enum TimeKind {
8     Formal,
9     Informal,
10     AmPm(bool),
11     Unknown,
12 }
13 
14 pub struct DateParser<'a> {
15     s: Scanner<'a>,
16     direct: Direction,
17     maybe_time: Option<(u32,TimeKind)>,
18     pub american: bool, // 9/11, not 20/03
19 }
20 
21 impl <'a> DateParser<'a> {
22 
new(text: &'a str) -> DateParser<'a>23     pub fn new(text: &'a str) -> DateParser<'a> {
24         DateParser{
25             s: Scanner::new(text).no_float(),
26             direct: Direction::Here,
27             maybe_time: None,
28             american: false
29         }
30     }
31 
american_date(mut self) -> DateParser<'a>32     pub fn american_date(mut self) -> DateParser<'a> {
33         self.american = true;
34         self
35     }
36 
iso_date(&mut self, y: u32) -> DateResult<DateSpec>37     fn iso_date(&mut self, y: u32) -> DateResult<DateSpec> {
38         let month = self.s.get_int::<u32>()?;
39         self.s.get_ch_matching(&['-'])?;
40         let day = self.s.get_int::<u32>()?;
41         Ok(DateSpec::absolute(y,month,day))
42     }
43 
informal_date(&mut self, day_or_month: u32) -> DateResult<DateSpec>44     fn informal_date(&mut self, day_or_month: u32) -> DateResult<DateSpec> {
45         let month_or_day = self.s.get_int::<u32>()?;
46         let (d,m) = if self.american {
47             (month_or_day, day_or_month)
48         } else {
49             (day_or_month, month_or_day)
50         };
51         Ok(if self.s.peek() == '/' {
52             self.s.get();
53             let y = self.s.get_int::<u32>()?;
54             let y = if y < 100 { // pivot (1940, 2040)
55                 if y > 40 {
56                     1900 + y
57                 } else {
58                     2000 + y
59                 }
60             } else {
61                 y
62             };
63             DateSpec::absolute(y,m,d)
64         } else {
65             DateSpec::FromName(ByName::from_day_month(d,m,self.direct))
66         })
67     }
68 
parse_date(&mut self) -> DateResult<Option<DateSpec>>69     fn parse_date(&mut self) -> DateResult<Option<DateSpec>> {
70         let mut t = self.s.next().or_err("empty date string")?;
71 
72         let sign = if t.is_char() && t.as_char().unwrap() == '-' {
73             true
74         } else {
75             false
76         };
77         if sign {
78             t = self.s.next().or_err("nothing after '-'")?;
79         }
80         if let Some(name) = t.as_iden() {
81             let shortcut = match name {
82                 "now" => Some(0),
83                 "today" => Some(0),
84                 "yesterday" => Some(-1),
85                 "tomorrow" => Some(1),
86                 _ => None
87             };
88             if let Some(skip) = shortcut {
89                 return Ok(Some(
90                     DateSpec::skip(time_unit("day").unwrap(), skip)
91                 ));
92             } else // maybe next or last?
93             if let Some(d) = Direction::from_name(&name) {
94                 self.direct = d;
95             }
96         }
97         if self.direct != Direction::Here {
98             t = self.s.next().or_err("nothing after last/next")?;
99         }
100         Ok(match t {
101             Token::Iden(ref name) => {
102                 let name = name.to_lowercase();
103                 // maybe weekday or month name?
104                 if let Some(by_name) = ByName::from_name(&name,self.direct) {
105                     // however, MONTH _might_ be followed by DAY, YEAR
106                     if let Some(month) = by_name.as_month() {
107                         let t = self.s.get();
108                         if t.is_integer() {
109                             let day = t.to_int_result::<u32>()?;
110                             return Ok(Some(if self.s.peek() == ',' {
111                                 self.s.get_char()?; // eat ','
112                                 let year = self.s.get_int::<u32>()?;
113                                 DateSpec::absolute(year,month,day)
114                             } else { // MONTH DAY is like DAY MONTH (tho no time!)
115                                 DateSpec::from_day_month(day, month, self.direct)
116                             }));
117                         }
118                     }
119                     Some(DateSpec::FromName(by_name))
120                 } else {
121                     return date_result("expected week day or month name");
122                 }
123             },
124             Token::Int(_) => {
125                 let n = t.to_int_result::<u32>()?;
126                 let t = self.s.get();
127                 if t.finished() { // must be a year...
128                     return Ok(Some(DateSpec::absolute(n,1,1)));
129                 }
130                 match t {
131                     Token::Iden(ref name) => {
132                         let day = n;
133                         let name = name.to_lowercase();
134                         if let Some(month) = month_name(&name) {
135                             if let Ok(year) = self.s.get_int::<u32>() {
136                                 // 4 July 2017
137                                 Some(DateSpec::absolute(year,month,day))
138                             } else {
139                                 // 4 July
140                                 Some(DateSpec::from_day_month(day, month, self.direct))
141                             }
142                         } else
143                         if let Some(u) = time_unit(&name) { // '2 days'
144                             let mut n = n as i32;
145                             if sign {
146                                 n = -n;
147                             } else {
148                                 let t = self.s.get();
149                                 let got_ago = if let Some(name) = t.as_iden() {
150                                     if name == "ago" {
151                                         n = -n;
152                                         true
153                                     } else {
154                                         return date_result("only expected 'ago'");
155                                     }
156                                 } else {
157                                     false
158                                 };
159                                 if ! got_ago {
160                                     if let Some(h) = t.to_integer() {
161                                         self.maybe_time = Some((h as u32, TimeKind::Unknown));
162                                     }
163                                 }
164                             }
165                             Some(DateSpec::skip(u, n))
166                         } else
167                         if name == "am" || name == "pm" {
168                             self.maybe_time = Some((n, TimeKind::AmPm(name == "pm")));
169                             None
170                         } else {
171                             return date_result("expected month or time unit");
172                         }
173                     },
174                     Token::Char(ch) => {
175                         match ch {
176                             '-' => Some(self.iso_date(n)?),
177                             '/' => Some(self.informal_date(n)?),
178                             ':' | '.' => {
179                                 let kind = if ch == ':' {
180                                     TimeKind::Formal
181                                 } else {
182                                     TimeKind::Informal
183                                 };
184                                 self.maybe_time = Some((n,kind));
185                                 None
186                             }
187                             _ => return date_result(&format!("unexpected char {:?}",ch)),
188                         }
189                     },
190                     _ => return date_result(&format!("unexpected token {:?}",t)),
191 
192                 }
193             },
194             _ => return date_result(&format!("not expected token {:?}",t)),
195         })
196 
197     }
198 
formal_time(&mut self, hour: u32) -> DateResult<TimeSpec>199     fn formal_time(&mut self, hour: u32) -> DateResult<TimeSpec> {
200         let min = self.s.get_int::<u32>()?;
201         // minute may be followed by [:secs][am|pm]
202         let mut tnext = None;
203         let sec = if let Some(t) = self.s.next() {
204             if let Some(ch) = t.as_char() {
205                 if ch != ':' {
206                     return date_result("expecting ':'");
207                 }
208                 self.s.get_int::<u32>()?
209             } else {
210                 tnext = Some(t);
211                 0
212             }
213         } else {
214             0
215         };
216         // we found seconds, look ahead
217         if tnext.is_none() {
218             tnext = self.s.next();
219         }
220         let micros = if let Some(Some('.')) = tnext.as_ref().map(|t| t.as_char()) {
221             let frac = self.s.grab_while(char::is_numeric);
222             if frac.is_empty() {
223                 return date_result("expected fractional second after '.'");
224             }
225             let frac = "0.".to_owned() + &frac;
226             let micros_f = frac.parse::<f64>().unwrap() * 1.0e6;
227             tnext = self.s.next();
228             micros_f as u32
229         } else {
230             0
231         };
232         if tnext.is_none() {
233             Ok(TimeSpec::new(hour, min, sec, micros ))
234         } else {
235             let tok = tnext.as_ref().unwrap();
236             if let Some(ch) = tok.as_char() {
237                 let expecting_offset = match ch {
238                     '+' | '-' => true,
239                     _ => return date_result("expected +/- before timezone")
240                 };
241                 let offset = if expecting_offset {
242                     let h = self.s.get_int::<u32>()?;
243                     let (h, m) = if self.s.peek() == ':' { // 02:00
244                         self.s.nextch();
245                         (h, self.s.get_int::<u32>()?)
246                     } else { // 0030 ....
247                         let hh = h;
248                         let h = hh / 100;
249                         let m = hh % 100;
250                         (h, m)
251                     };
252                     let res = 60 * (m + 60 * h);
253                     (res as i64) * if ch == '-' { -1 } else { 1 }
254                 } else {
255                     0
256                 };
257                 Ok(TimeSpec::new_with_offset(hour, min, sec, offset,micros))
258             } else if let Some(id) = tok.as_iden() {
259                 if id == "Z" {
260                     Ok(TimeSpec::new_with_offset(hour,min,sec,0,micros))
261                 } else { // am or pm
262                     let hour = DateParser::am_pm(&id, hour)?;
263                     Ok(TimeSpec::new(hour, min, sec, micros))
264                 }
265             } else {
266                 Ok(TimeSpec::new(hour, min, sec, micros))
267             }
268         }
269     }
270 
informal_time(&mut self, hour: u32) -> DateResult<TimeSpec>271     fn informal_time(&mut self, hour: u32) -> DateResult<TimeSpec> {
272         let min = self.s.get_int::<u32>()?;
273         let hour = if let Some(t) = self.s.next() {
274             let name = t.to_iden_result()?;
275             DateParser::am_pm(&name,hour)?
276         } else {
277             hour
278         };
279         Ok(TimeSpec::new(hour, min, 0, 0))
280     }
281 
am_pm(name: &str, mut hour: u32) -> DateResult<u32>282     fn am_pm(name: &str, mut hour: u32) -> DateResult<u32> {
283         if name == "pm" {
284             hour += 12;
285         } else
286         if name != "am" {
287             return date_result("expected am or pm");
288         }
289         Ok(hour)
290     }
291 
hour_time(name: &str, hour: u32) -> DateResult<TimeSpec>292     fn hour_time(name: &str, hour: u32) -> DateResult<TimeSpec> {
293         Ok(TimeSpec::new (DateParser::am_pm(name, hour)?, 0, 0, 0))
294     }
295 
parse_time(&mut self) -> DateResult<Option<TimeSpec>>296     fn parse_time(&mut self) -> DateResult<Option<TimeSpec>> {
297         // here the date parser looked ahead and saw an hour followed by some separator
298         if let Some(hour_sep) = self.maybe_time { // didn't see a separator, so look...
299             let (h,mut kind) = hour_sep;
300             if let TimeKind::Unknown = kind {
301                 kind = match self.s.get_char()? {
302                     ':' => TimeKind::Formal,
303                     '.' => TimeKind::Informal,
304                     ch => return date_result(&format!("expected : or ., not {}", ch)),
305                 };
306             }
307             Ok(Some(
308                 match kind {
309                     TimeKind::Formal => self.formal_time(h)?,
310                     TimeKind::Informal => self.informal_time(h)?,
311                     TimeKind::AmPm(is_pm) =>
312                         DateParser::hour_time(if is_pm {"pm"} else {"am"},h)?,
313                     TimeKind::Unknown => unreachable!(),
314                 }
315             ))
316         } else { // no lookahead...
317             if self.s.peek() == 'T' {
318                 self.s.nextch();
319             }
320             let t = self.s.get();
321             if t.finished() {
322                 return Ok(None);
323             }
324 
325             let hour = t.to_int_result::<u32>()?;
326             Ok(Some(match self.s.get() {
327                 Token::Char(ch) => match ch {
328                     ':' => self.formal_time(hour)?,
329                     '.' => self.informal_time(hour)?,
330                     ch => return date_result(&format!("unexpected char {:?}",ch))
331                 },
332                 Token::Iden(name) => {
333                     DateParser::hour_time(&name,hour)?
334                 }
335                 t => return date_result(&format!("unexpected token {:?}",t))
336             }))
337         }
338     }
339 
parse(&mut self) -> DateResult<DateTimeSpec>340     pub fn parse(&mut self) -> DateResult<DateTimeSpec> {
341         let date = self.parse_date()?;
342         let time = self.parse_time()?;
343         Ok(DateTimeSpec{date: date, time: time})
344     }
345 
346 }
347