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