chrono_english/
parser.rs

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