1 use super::{Timespec, Tm, at_utc, ParseError, NSEC_PER_SEC};
2 
3 /// Parses the time from the string according to the format string.
strptime(mut s: &str, format: &str) -> Result<Tm, ParseError>4 pub fn strptime(mut s: &str, format: &str) -> Result<Tm, ParseError> {
5     let mut tm = Tm {
6         tm_sec: 0,
7         tm_min: 0,
8         tm_hour: 0,
9         tm_mday: 0,
10         tm_mon: 0,
11         tm_year: 0,
12         tm_wday: 0,
13         tm_yday: 0,
14         tm_isdst: 0,
15         tm_utcoff: 0,
16         tm_nsec: 0,
17     };
18     let mut chars = format.chars();
19 
20     while let Some(ch) = chars.next() {
21         if ch == '%' {
22             if let Some(ch) = chars.next() {
23                 parse_type(&mut s, ch, &mut tm)?;
24             }
25         } else {
26             parse_char(&mut s, ch)?;
27         }
28     }
29 
30     Ok(tm)
31 }
32 
parse_type(s: &mut &str, ch: char, tm: &mut Tm) -> Result<(), ParseError>33 fn parse_type(s: &mut &str, ch: char, tm: &mut Tm) -> Result<(), ParseError> {
34     match ch {
35         'A' => match match_strs(s, &[("Sunday", 0),
36                                      ("Monday", 1),
37                                      ("Tuesday", 2),
38                                      ("Wednesday", 3),
39                                      ("Thursday", 4),
40                                      ("Friday", 5),
41                                      ("Saturday", 6)]) {
42             Some(v) => { tm.tm_wday = v; Ok(()) }
43             None => Err(ParseError::InvalidDay)
44         },
45         'a' => match match_strs(s, &[("Sun", 0),
46                                      ("Mon", 1),
47                                      ("Tue", 2),
48                                      ("Wed", 3),
49                                      ("Thu", 4),
50                                      ("Fri", 5),
51                                      ("Sat", 6)]) {
52             Some(v) => { tm.tm_wday = v; Ok(()) }
53             None => Err(ParseError::InvalidDay)
54         },
55         'B' => match match_strs(s, &[("January", 0),
56                                      ("February", 1),
57                                      ("March", 2),
58                                      ("April", 3),
59                                      ("May", 4),
60                                      ("June", 5),
61                                      ("July", 6),
62                                      ("August", 7),
63                                      ("September", 8),
64                                      ("October", 9),
65                                      ("November", 10),
66                                      ("December", 11)]) {
67             Some(v) => { tm.tm_mon = v; Ok(()) }
68             None => Err(ParseError::InvalidMonth)
69         },
70         'b' | 'h' => match match_strs(s, &[("Jan", 0),
71                                            ("Feb", 1),
72                                            ("Mar", 2),
73                                            ("Apr", 3),
74                                            ("May", 4),
75                                            ("Jun", 5),
76                                            ("Jul", 6),
77                                            ("Aug", 7),
78                                            ("Sep", 8),
79                                            ("Oct", 9),
80                                            ("Nov", 10),
81                                            ("Dec", 11)]) {
82             Some(v) => { tm.tm_mon = v; Ok(()) }
83             None => Err(ParseError::InvalidMonth)
84         },
85         'C' => match match_digits_in_range(s, 1, 2, false, 0, 99) {
86             Some(v) => { tm.tm_year += (v * 100) - 1900; Ok(()) }
87             None => Err(ParseError::InvalidYear)
88         },
89         'c' => {
90             parse_type(s, 'a', tm)
91                 .and_then(|()| parse_char(s, ' '))
92                 .and_then(|()| parse_type(s, 'b', tm))
93                 .and_then(|()| parse_char(s, ' '))
94                 .and_then(|()| parse_type(s, 'e', tm))
95                 .and_then(|()| parse_char(s, ' '))
96                 .and_then(|()| parse_type(s, 'T', tm))
97                 .and_then(|()| parse_char(s, ' '))
98                 .and_then(|()| parse_type(s, 'Y', tm))
99         }
100         'D' | 'x' => {
101             parse_type(s, 'm', tm)
102                 .and_then(|()| parse_char(s, '/'))
103                 .and_then(|()| parse_type(s, 'd', tm))
104                 .and_then(|()| parse_char(s, '/'))
105                 .and_then(|()| parse_type(s, 'y', tm))
106         }
107         'd' => match match_digits_in_range(s, 1, 2, false, 1, 31) {
108             Some(v) => { tm.tm_mday = v; Ok(()) }
109             None => Err(ParseError::InvalidDayOfMonth)
110         },
111         'e' => match match_digits_in_range(s, 1, 2, true, 1, 31) {
112             Some(v) => { tm.tm_mday = v; Ok(()) }
113             None => Err(ParseError::InvalidDayOfMonth)
114         },
115         'f' => {
116             tm.tm_nsec = match_fractional_seconds(s);
117             Ok(())
118         }
119         'F' => {
120             parse_type(s, 'Y', tm)
121                 .and_then(|()| parse_char(s, '-'))
122                 .and_then(|()| parse_type(s, 'm', tm))
123                 .and_then(|()| parse_char(s, '-'))
124                 .and_then(|()| parse_type(s, 'd', tm))
125         }
126         'H' => {
127             match match_digits_in_range(s, 1, 2, false, 0, 23) {
128                 Some(v) => { tm.tm_hour = v; Ok(()) }
129                 None => Err(ParseError::InvalidHour)
130             }
131         }
132         'I' => {
133             match match_digits_in_range(s, 1, 2, false, 1, 12) {
134                 Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) }
135                 None => Err(ParseError::InvalidHour)
136             }
137         }
138         'j' => {
139             match match_digits_in_range(s, 1, 3, false, 1, 366) {
140                 Some(v) => { tm.tm_yday = v - 1; Ok(()) }
141                 None => Err(ParseError::InvalidDayOfYear)
142             }
143         }
144         'k' => {
145             match match_digits_in_range(s, 1, 2, true, 0, 23) {
146                 Some(v) => { tm.tm_hour = v; Ok(()) }
147                 None => Err(ParseError::InvalidHour)
148             }
149         }
150         'l' => {
151             match match_digits_in_range(s, 1, 2, true, 1, 12) {
152                 Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) }
153                 None => Err(ParseError::InvalidHour)
154             }
155         }
156         'M' => {
157             match match_digits_in_range(s, 1, 2, false, 0, 59) {
158                 Some(v) => { tm.tm_min = v; Ok(()) }
159                 None => Err(ParseError::InvalidMinute)
160             }
161         }
162         'm' => {
163             match match_digits_in_range(s, 1, 2, false, 1, 12) {
164                 Some(v) => { tm.tm_mon = v - 1; Ok(()) }
165                 None => Err(ParseError::InvalidMonth)
166             }
167         }
168         'n' => parse_char(s, '\n'),
169         'P' => match match_strs(s, &[("am", 0), ("pm", 12)]) {
170             Some(v) => { tm.tm_hour += v; Ok(()) }
171             None => Err(ParseError::InvalidHour)
172         },
173         'p' => match match_strs(s, &[("AM", 0), ("PM", 12)]) {
174             Some(v) => { tm.tm_hour += v; Ok(()) }
175             None => Err(ParseError::InvalidHour)
176         },
177         'R' => {
178             parse_type(s, 'H', tm)
179                 .and_then(|()| parse_char(s, ':'))
180                 .and_then(|()| parse_type(s, 'M', tm))
181         }
182         'r' => {
183             parse_type(s, 'I', tm)
184                 .and_then(|()| parse_char(s, ':'))
185                 .and_then(|()| parse_type(s, 'M', tm))
186                 .and_then(|()| parse_char(s, ':'))
187                 .and_then(|()| parse_type(s, 'S', tm))
188                 .and_then(|()| parse_char(s, ' '))
189                 .and_then(|()| parse_type(s, 'p', tm))
190         }
191         's' => {
192             match match_digits_i64(s, 1, 18, false) {
193                 Some(v) => {
194                     *tm = at_utc(Timespec::new(v, 0));
195                     Ok(())
196                 },
197                 None => Err(ParseError::InvalidSecondsSinceEpoch)
198             }
199         }
200         'S' => {
201             match match_digits_in_range(s, 1, 2, false, 0, 60) {
202                 Some(v) => { tm.tm_sec = v; Ok(()) }
203                 None => Err(ParseError::InvalidSecond)
204             }
205         }
206         //'s' {}
207         'T' | 'X' => {
208             parse_type(s, 'H', tm)
209                 .and_then(|()| parse_char(s, ':'))
210                 .and_then(|()| parse_type(s, 'M', tm))
211                 .and_then(|()| parse_char(s, ':'))
212                 .and_then(|()| parse_type(s, 'S', tm))
213         }
214         't' => parse_char(s, '\t'),
215         'u' => {
216             match match_digits_in_range(s, 1, 1, false, 1, 7) {
217                 Some(v) => { tm.tm_wday = if v == 7 { 0 } else { v }; Ok(()) }
218                 None => Err(ParseError::InvalidDayOfWeek)
219             }
220         }
221         'v' => {
222             parse_type(s, 'e', tm)
223                 .and_then(|()| parse_char(s, '-'))
224                 .and_then(|()| parse_type(s, 'b', tm))
225                 .and_then(|()| parse_char(s, '-'))
226                 .and_then(|()| parse_type(s, 'Y', tm))
227         }
228         //'W' {}
229         'w' => {
230             match match_digits_in_range(s, 1, 1, false, 0, 6) {
231                 Some(v) => { tm.tm_wday = v; Ok(()) }
232                 None => Err(ParseError::InvalidDayOfWeek)
233             }
234         }
235         'Y' => {
236             match match_digits(s, 4, 4, false) {
237                 Some(v) => { tm.tm_year = v - 1900; Ok(()) }
238                 None => Err(ParseError::InvalidYear)
239             }
240         }
241         'y' => {
242             match match_digits_in_range(s, 1, 2, false, 0, 99) {
243                 Some(v) => { tm.tm_year = v; Ok(()) }
244                 None => Err(ParseError::InvalidYear)
245             }
246         }
247         'Z' => {
248             if match_str(s, "UTC") || match_str(s, "GMT") {
249                 tm.tm_utcoff = 0;
250                 Ok(())
251             } else {
252                 // It's odd, but to maintain compatibility with c's
253                 // strptime we ignore the timezone.
254                 for (i, ch) in s.char_indices() {
255                     if ch == ' ' {
256                         *s = &s[i..];
257                         return Ok(())
258                     }
259                 }
260                 *s = "";
261                 Ok(())
262             }
263         }
264         'z' => {
265             if parse_char(s, 'Z').is_ok() {
266                 tm.tm_utcoff = 0;
267                 Ok(())
268             } else {
269                 let sign = if parse_char(s, '+').is_ok() {1}
270                            else if parse_char(s, '-').is_ok() {-1}
271                            else { return Err(ParseError::InvalidZoneOffset) };
272 
273                 let hours;
274                 let minutes;
275 
276                 match match_digits(s, 2, 2, false) {
277                     Some(h) => hours = h,
278                     None => return Err(ParseError::InvalidZoneOffset)
279                 }
280 
281                 // consume the colon if its present,
282                 // just ignore it otherwise
283                 let _ = parse_char(s, ':');
284 
285                 match match_digits(s, 2, 2, false) {
286                     Some(m) => minutes = m,
287                     None => return Err(ParseError::InvalidZoneOffset)
288                 }
289 
290                 tm.tm_utcoff = sign * (hours * 60 * 60 + minutes * 60);
291                 Ok(())
292             }
293         }
294         '%' => parse_char(s, '%'),
295         ch => Err(ParseError::InvalidFormatSpecifier(ch))
296     }
297 }
298 
299 
match_str(s: &mut &str, needle: &str) -> bool300 fn match_str(s: &mut &str, needle: &str) -> bool {
301     if s.starts_with(needle) {
302         *s = &s[needle.len()..];
303         true
304     } else {
305         false
306     }
307 }
308 
match_strs(ss: &mut &str, strs: &[(&str, i32)]) -> Option<i32>309 fn match_strs(ss: &mut &str, strs: &[(&str, i32)]) -> Option<i32> {
310     for &(needle, value) in strs.iter() {
311         if match_str(ss, needle) {
312             return Some(value)
313         }
314     }
315     None
316 }
317 
match_digits(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i32>318 fn match_digits(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i32> {
319     match match_digits_i64(ss, min_digits, max_digits, ws) {
320         Some(v) => Some(v as i32),
321         None => None
322     }
323 }
324 
match_digits_i64(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i64>325 fn match_digits_i64(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option<i64> {
326     let mut value : i64 = 0;
327     let mut n = 0;
328     if ws {
329         #[allow(deprecated)] // use `trim_start_matches` starting in 1.30
330         let s2 = ss.trim_left_matches(" ");
331         n = ss.len() - s2.len();
332         if n > max_digits { return None }
333     }
334     let chars = ss[n..].char_indices();
335     for (_, ch) in chars.take(max_digits - n) {
336         match ch {
337             '0' ... '9' => value = value * 10 + (ch as i64 - '0' as i64),
338             _ => break,
339         }
340         n += 1;
341     }
342 
343     if n >= min_digits && n <= max_digits {
344         *ss = &ss[n..];
345         Some(value)
346     } else {
347         None
348     }
349 }
350 
match_fractional_seconds(ss: &mut &str) -> i32351 fn match_fractional_seconds(ss: &mut &str) -> i32 {
352     let mut value = 0;
353     let mut multiplier = NSEC_PER_SEC / 10;
354 
355     let mut chars = ss.char_indices();
356     let orig = *ss;
357     for (i, ch) in &mut chars {
358         *ss = &orig[i..];
359         match ch {
360             '0' ... '9' => {
361                 // This will drop digits after the nanoseconds place
362                 let digit = ch as i32 - '0' as i32;
363                 value += digit * multiplier;
364                 multiplier /= 10;
365             }
366             _ => break
367         }
368     }
369 
370     value
371 }
372 
match_digits_in_range(ss: &mut &str, min_digits : usize, max_digits : usize, ws: bool, min: i32, max: i32) -> Option<i32>373 fn match_digits_in_range(ss: &mut &str,
374                          min_digits : usize, max_digits : usize,
375                          ws: bool, min: i32, max: i32) -> Option<i32> {
376     let before = *ss;
377     match match_digits(ss, min_digits, max_digits, ws) {
378         Some(val) if val >= min && val <= max => Some(val),
379         _ => { *ss = before; None }
380     }
381 }
382 
parse_char(s: &mut &str, c: char) -> Result<(), ParseError>383 fn parse_char(s: &mut &str, c: char) -> Result<(), ParseError> {
384     match s.char_indices().next() {
385         Some((i, c2)) => {
386             if c == c2 {
387                 *s = &s[i + c2.len_utf8()..];
388                 Ok(())
389             } else {
390                 Err(ParseError::UnexpectedCharacter(c, c2))
391             }
392         }
393         None => Err(ParseError::InvalidTime),
394     }
395 }
396