• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3 
4 /*!
5  * Various scanning routines for the parser.
6  */
7 
8 #![allow(deprecated)]
9 
10 use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT};
11 use Weekday;
12 
13 /// Returns true when two slices are equal case-insensitively (in ASCII).
14 /// Assumes that the `pattern` is already converted to lower case.
equals(s: &str, pattern: &str) -> bool15 fn equals(s: &str, pattern: &str) -> bool {
16     let mut xs = s.as_bytes().iter().map(|&c| match c {
17         b'A'...b'Z' => c + 32,
18         _ => c,
19     });
20     let mut ys = pattern.as_bytes().iter().cloned();
21     loop {
22         match (xs.next(), ys.next()) {
23             (None, None) => return true,
24             (None, _) | (_, None) => return false,
25             (Some(x), Some(y)) if x != y => return false,
26             _ => (),
27         }
28     }
29 }
30 
31 /// Tries to parse the non-negative number from `min` to `max` digits.
32 ///
33 /// The absence of digits at all is an unconditional error.
34 /// More than `max` digits are consumed up to the first `max` digits.
35 /// Any number that does not fit in `i64` is an error.
36 #[inline]
number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)>37 pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
38     assert!(min <= max);
39 
40     // We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
41     // the first non-numeric byte, which may be another ascii character or beginning of multi-byte
42     // UTF-8 character.
43     let bytes = s.as_bytes();
44     if bytes.len() < min {
45         return Err(TOO_SHORT);
46     }
47 
48     let mut n = 0i64;
49     for (i, c) in bytes.iter().take(max).cloned().enumerate() {
50         // cloned() = copied()
51         if c < b'0' || b'9' < c {
52             if i < min {
53                 return Err(INVALID);
54             } else {
55                 return Ok((&s[i..], n));
56             }
57         }
58 
59         n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) {
60             Some(n) => n,
61             None => return Err(OUT_OF_RANGE),
62         };
63     }
64 
65     Ok((&s[::core::cmp::min(max, bytes.len())..], n))
66 }
67 
68 /// Tries to consume at least one digits as a fractional second.
69 /// Returns the number of whole nanoseconds (0--999,999,999).
nanosecond(s: &str) -> ParseResult<(&str, i64)>70 pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
71     // record the number of digits consumed for later scaling.
72     let origlen = s.len();
73     let (s, v) = number(s, 1, 9)?;
74     let consumed = origlen - s.len();
75 
76     // scale the number accordingly.
77     static SCALE: [i64; 10] =
78         [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
79     let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?;
80 
81     // if there are more than 9 digits, skip next digits.
82     let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
83 
84     Ok((s, v))
85 }
86 
87 /// Tries to consume a fixed number of digits as a fractional second.
88 /// Returns the number of whole nanoseconds (0--999,999,999).
nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)>89 pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
90     // record the number of digits consumed for later scaling.
91     let (s, v) = number(s, digits, digits)?;
92 
93     // scale the number accordingly.
94     static SCALE: [i64; 10] =
95         [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
96     let v = v.checked_mul(SCALE[digits]).ok_or(OUT_OF_RANGE)?;
97 
98     Ok((s, v))
99 }
100 
101 /// Tries to parse the month index (0 through 11) with the first three ASCII letters.
short_month0(s: &str) -> ParseResult<(&str, u8)>102 pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
103     if s.len() < 3 {
104         return Err(TOO_SHORT);
105     }
106     let buf = s.as_bytes();
107     let month0 = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) {
108         (b'j', b'a', b'n') => 0,
109         (b'f', b'e', b'b') => 1,
110         (b'm', b'a', b'r') => 2,
111         (b'a', b'p', b'r') => 3,
112         (b'm', b'a', b'y') => 4,
113         (b'j', b'u', b'n') => 5,
114         (b'j', b'u', b'l') => 6,
115         (b'a', b'u', b'g') => 7,
116         (b's', b'e', b'p') => 8,
117         (b'o', b'c', b't') => 9,
118         (b'n', b'o', b'v') => 10,
119         (b'd', b'e', b'c') => 11,
120         _ => return Err(INVALID),
121     };
122     Ok((&s[3..], month0))
123 }
124 
125 /// Tries to parse the weekday with the first three ASCII letters.
short_weekday(s: &str) -> ParseResult<(&str, Weekday)>126 pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
127     if s.len() < 3 {
128         return Err(TOO_SHORT);
129     }
130     let buf = s.as_bytes();
131     let weekday = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) {
132         (b'm', b'o', b'n') => Weekday::Mon,
133         (b't', b'u', b'e') => Weekday::Tue,
134         (b'w', b'e', b'd') => Weekday::Wed,
135         (b't', b'h', b'u') => Weekday::Thu,
136         (b'f', b'r', b'i') => Weekday::Fri,
137         (b's', b'a', b't') => Weekday::Sat,
138         (b's', b'u', b'n') => Weekday::Sun,
139         _ => return Err(INVALID),
140     };
141     Ok((&s[3..], weekday))
142 }
143 
144 /// Tries to parse the month index (0 through 11) with short or long month names.
145 /// It prefers long month names to short month names when both are possible.
short_or_long_month0(s: &str) -> ParseResult<(&str, u8)>146 pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
147     // lowercased month names, minus first three chars
148     static LONG_MONTH_SUFFIXES: [&'static str; 12] =
149         ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"];
150 
151     let (mut s, month0) = short_month0(s)?;
152 
153     // tries to consume the suffix if possible
154     let suffix = LONG_MONTH_SUFFIXES[month0 as usize];
155     if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
156         s = &s[suffix.len()..];
157     }
158 
159     Ok((s, month0))
160 }
161 
162 /// Tries to parse the weekday with short or long weekday names.
163 /// It prefers long weekday names to short weekday names when both are possible.
short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)>164 pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
165     // lowercased weekday names, minus first three chars
166     static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] =
167         ["day", "sday", "nesday", "rsday", "day", "urday", "day"];
168 
169     let (mut s, weekday) = short_weekday(s)?;
170 
171     // tries to consume the suffix if possible
172     let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize];
173     if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
174         s = &s[suffix.len()..];
175     }
176 
177     Ok((s, weekday))
178 }
179 
180 /// Tries to consume exactly one given character.
char(s: &str, c1: u8) -> ParseResult<&str>181 pub fn char(s: &str, c1: u8) -> ParseResult<&str> {
182     match s.as_bytes().first() {
183         Some(&c) if c == c1 => Ok(&s[1..]),
184         Some(_) => Err(INVALID),
185         None => Err(TOO_SHORT),
186     }
187 }
188 
189 /// Tries to consume one or more whitespace.
space(s: &str) -> ParseResult<&str>190 pub fn space(s: &str) -> ParseResult<&str> {
191     let s_ = s.trim_left();
192     if s_.len() < s.len() {
193         Ok(s_)
194     } else if s.is_empty() {
195         Err(TOO_SHORT)
196     } else {
197         Err(INVALID)
198     }
199 }
200 
201 /// Consumes any number (including zero) of colon or spaces.
colon_or_space(s: &str) -> ParseResult<&str>202 pub fn colon_or_space(s: &str) -> ParseResult<&str> {
203     Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace()))
204 }
205 
206 /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
207 ///
208 /// The additional `colon` may be used to parse a mandatory or optional `:`
209 /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails.
timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>,210 pub fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)>
211 where
212     F: FnMut(&str) -> ParseResult<&str>,
213 {
214     timezone_offset_internal(s, consume_colon, false)
215 }
216 
timezone_offset_internal<F>( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>,217 fn timezone_offset_internal<F>(
218     mut s: &str,
219     mut consume_colon: F,
220     allow_missing_minutes: bool,
221 ) -> ParseResult<(&str, i32)>
222 where
223     F: FnMut(&str) -> ParseResult<&str>,
224 {
225     fn digits(s: &str) -> ParseResult<(u8, u8)> {
226         let b = s.as_bytes();
227         if b.len() < 2 {
228             Err(TOO_SHORT)
229         } else {
230             Ok((b[0], b[1]))
231         }
232     }
233     let negative = match s.as_bytes().first() {
234         Some(&b'+') => false,
235         Some(&b'-') => true,
236         Some(_) => return Err(INVALID),
237         None => return Err(TOO_SHORT),
238     };
239     s = &s[1..];
240 
241     // hours (00--99)
242     let hours = match digits(s)? {
243         (h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
244         _ => return Err(INVALID),
245     };
246     s = &s[2..];
247 
248     // colons (and possibly other separators)
249     s = consume_colon(s)?;
250 
251     // minutes (00--59)
252     // if the next two items are digits then we have to add minutes
253     let minutes = if let Ok(ds) = digits(s) {
254         match ds {
255             (m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
256             (b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE),
257             _ => return Err(INVALID),
258         }
259     } else if allow_missing_minutes {
260         0
261     } else {
262         return Err(TOO_SHORT);
263     };
264     s = match s.len() {
265         len if len >= 2 => &s[2..],
266         len if len == 0 => s,
267         _ => return Err(TOO_SHORT),
268     };
269 
270     let seconds = hours * 3600 + minutes * 60;
271     Ok((s, if negative { -seconds } else { seconds }))
272 }
273 
274 /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`.
timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>,275 pub fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
276 where
277     F: FnMut(&str) -> ParseResult<&str>,
278 {
279     let bytes = s.as_bytes();
280     match bytes.first() {
281         Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
282         Some(&b'u') | Some(&b'U') => {
283             if bytes.len() >= 3 {
284                 let (b, c) = (bytes[1], bytes[2]);
285                 match (b | 32, c | 32) {
286                     (b't', b'c') => Ok((&s[3..], 0)),
287                     _ => Err(INVALID),
288                 }
289             } else {
290                 Err(INVALID)
291             }
292         }
293         _ => timezone_offset(s, colon),
294     }
295 }
296 
297 /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as
298 /// `+00:00`, and allows missing minutes entirely.
timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>,299 pub fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
300 where
301     F: FnMut(&str) -> ParseResult<&str>,
302 {
303     match s.as_bytes().first() {
304         Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
305         _ => timezone_offset_internal(s, colon, true),
306     }
307 }
308 
309 /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones.
310 /// May return `None` which indicates an insufficient offset data (i.e. `-0000`).
timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)>311 pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> {
312     // tries to parse legacy time zone names
313     let upto = s
314         .as_bytes()
315         .iter()
316         .position(|&c| match c {
317             b'a'...b'z' | b'A'...b'Z' => false,
318             _ => true,
319         })
320         .unwrap_or_else(|| s.len());
321     if upto > 0 {
322         let name = &s[..upto];
323         let s = &s[upto..];
324         let offset_hours = |o| Ok((s, Some(o * 3600)));
325         if equals(name, "gmt") || equals(name, "ut") {
326             offset_hours(0)
327         } else if equals(name, "edt") {
328             offset_hours(-4)
329         } else if equals(name, "est") || equals(name, "cdt") {
330             offset_hours(-5)
331         } else if equals(name, "cst") || equals(name, "mdt") {
332             offset_hours(-6)
333         } else if equals(name, "mst") || equals(name, "pdt") {
334             offset_hours(-7)
335         } else if equals(name, "pst") {
336             offset_hours(-8)
337         } else {
338             Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000
339         }
340     } else {
341         let (s_, offset) = timezone_offset(s, |s| Ok(s))?;
342         Ok((s_, Some(offset)))
343     }
344 }
345 
346 /// Tries to consume everyting until next whitespace-like symbol.
347 /// Does not provide any offset information from the consumed data.
timezone_name_skip(s: &str) -> ParseResult<(&str, ())>348 pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> {
349     Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ()))
350 }
351