use super::{Timespec, Tm, at_utc, ParseError, NSEC_PER_SEC}; /// Parses the time from the string according to the format string. pub fn strptime(mut s: &str, format: &str) -> Result { let mut tm = Tm { tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 0, tm_mon: 0, tm_year: 0, tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_utcoff: 0, tm_nsec: 0, }; let mut chars = format.chars(); while let Some(ch) = chars.next() { if ch == '%' { if let Some(ch) = chars.next() { parse_type(&mut s, ch, &mut tm)?; } } else { parse_char(&mut s, ch)?; } } Ok(tm) } fn parse_type(s: &mut &str, ch: char, tm: &mut Tm) -> Result<(), ParseError> { match ch { 'A' => match match_strs(s, &[("Sunday", 0), ("Monday", 1), ("Tuesday", 2), ("Wednesday", 3), ("Thursday", 4), ("Friday", 5), ("Saturday", 6)]) { Some(v) => { tm.tm_wday = v; Ok(()) } None => Err(ParseError::InvalidDay) }, 'a' => match match_strs(s, &[("Sun", 0), ("Mon", 1), ("Tue", 2), ("Wed", 3), ("Thu", 4), ("Fri", 5), ("Sat", 6)]) { Some(v) => { tm.tm_wday = v; Ok(()) } None => Err(ParseError::InvalidDay) }, 'B' => match match_strs(s, &[("January", 0), ("February", 1), ("March", 2), ("April", 3), ("May", 4), ("June", 5), ("July", 6), ("August", 7), ("September", 8), ("October", 9), ("November", 10), ("December", 11)]) { Some(v) => { tm.tm_mon = v; Ok(()) } None => Err(ParseError::InvalidMonth) }, 'b' | 'h' => match match_strs(s, &[("Jan", 0), ("Feb", 1), ("Mar", 2), ("Apr", 3), ("May", 4), ("Jun", 5), ("Jul", 6), ("Aug", 7), ("Sep", 8), ("Oct", 9), ("Nov", 10), ("Dec", 11)]) { Some(v) => { tm.tm_mon = v; Ok(()) } None => Err(ParseError::InvalidMonth) }, 'C' => match match_digits_in_range(s, 1, 2, false, 0, 99) { Some(v) => { tm.tm_year += (v * 100) - 1900; Ok(()) } None => Err(ParseError::InvalidYear) }, 'c' => { parse_type(s, 'a', tm) .and_then(|()| parse_char(s, ' ')) .and_then(|()| parse_type(s, 'b', tm)) .and_then(|()| parse_char(s, ' ')) .and_then(|()| parse_type(s, 'e', tm)) .and_then(|()| parse_char(s, ' ')) .and_then(|()| parse_type(s, 'T', tm)) .and_then(|()| parse_char(s, ' ')) .and_then(|()| parse_type(s, 'Y', tm)) } 'D' | 'x' => { parse_type(s, 'm', tm) .and_then(|()| parse_char(s, '/')) .and_then(|()| parse_type(s, 'd', tm)) .and_then(|()| parse_char(s, '/')) .and_then(|()| parse_type(s, 'y', tm)) } 'd' => match match_digits_in_range(s, 1, 2, false, 1, 31) { Some(v) => { tm.tm_mday = v; Ok(()) } None => Err(ParseError::InvalidDayOfMonth) }, 'e' => match match_digits_in_range(s, 1, 2, true, 1, 31) { Some(v) => { tm.tm_mday = v; Ok(()) } None => Err(ParseError::InvalidDayOfMonth) }, 'f' => { tm.tm_nsec = match_fractional_seconds(s); Ok(()) } 'F' => { parse_type(s, 'Y', tm) .and_then(|()| parse_char(s, '-')) .and_then(|()| parse_type(s, 'm', tm)) .and_then(|()| parse_char(s, '-')) .and_then(|()| parse_type(s, 'd', tm)) } 'H' => { match match_digits_in_range(s, 1, 2, false, 0, 23) { Some(v) => { tm.tm_hour = v; Ok(()) } None => Err(ParseError::InvalidHour) } } 'I' => { match match_digits_in_range(s, 1, 2, false, 1, 12) { Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) } None => Err(ParseError::InvalidHour) } } 'j' => { match match_digits_in_range(s, 1, 3, false, 1, 366) { Some(v) => { tm.tm_yday = v - 1; Ok(()) } None => Err(ParseError::InvalidDayOfYear) } } 'k' => { match match_digits_in_range(s, 1, 2, true, 0, 23) { Some(v) => { tm.tm_hour = v; Ok(()) } None => Err(ParseError::InvalidHour) } } 'l' => { match match_digits_in_range(s, 1, 2, true, 1, 12) { Some(v) => { tm.tm_hour = if v == 12 { 0 } else { v }; Ok(()) } None => Err(ParseError::InvalidHour) } } 'M' => { match match_digits_in_range(s, 1, 2, false, 0, 59) { Some(v) => { tm.tm_min = v; Ok(()) } None => Err(ParseError::InvalidMinute) } } 'm' => { match match_digits_in_range(s, 1, 2, false, 1, 12) { Some(v) => { tm.tm_mon = v - 1; Ok(()) } None => Err(ParseError::InvalidMonth) } } 'n' => parse_char(s, '\n'), 'P' => match match_strs(s, &[("am", 0), ("pm", 12)]) { Some(v) => { tm.tm_hour += v; Ok(()) } None => Err(ParseError::InvalidHour) }, 'p' => match match_strs(s, &[("AM", 0), ("PM", 12)]) { Some(v) => { tm.tm_hour += v; Ok(()) } None => Err(ParseError::InvalidHour) }, 'R' => { parse_type(s, 'H', tm) .and_then(|()| parse_char(s, ':')) .and_then(|()| parse_type(s, 'M', tm)) } 'r' => { parse_type(s, 'I', tm) .and_then(|()| parse_char(s, ':')) .and_then(|()| parse_type(s, 'M', tm)) .and_then(|()| parse_char(s, ':')) .and_then(|()| parse_type(s, 'S', tm)) .and_then(|()| parse_char(s, ' ')) .and_then(|()| parse_type(s, 'p', tm)) } 's' => { match match_digits_i64(s, 1, 18, false) { Some(v) => { *tm = at_utc(Timespec::new(v, 0)); Ok(()) }, None => Err(ParseError::InvalidSecondsSinceEpoch) } } 'S' => { match match_digits_in_range(s, 1, 2, false, 0, 60) { Some(v) => { tm.tm_sec = v; Ok(()) } None => Err(ParseError::InvalidSecond) } } //'s' {} 'T' | 'X' => { parse_type(s, 'H', tm) .and_then(|()| parse_char(s, ':')) .and_then(|()| parse_type(s, 'M', tm)) .and_then(|()| parse_char(s, ':')) .and_then(|()| parse_type(s, 'S', tm)) } 't' => parse_char(s, '\t'), 'u' => { match match_digits_in_range(s, 1, 1, false, 1, 7) { Some(v) => { tm.tm_wday = if v == 7 { 0 } else { v }; Ok(()) } None => Err(ParseError::InvalidDayOfWeek) } } 'v' => { parse_type(s, 'e', tm) .and_then(|()| parse_char(s, '-')) .and_then(|()| parse_type(s, 'b', tm)) .and_then(|()| parse_char(s, '-')) .and_then(|()| parse_type(s, 'Y', tm)) } //'W' {} 'w' => { match match_digits_in_range(s, 1, 1, false, 0, 6) { Some(v) => { tm.tm_wday = v; Ok(()) } None => Err(ParseError::InvalidDayOfWeek) } } 'Y' => { match match_digits(s, 4, 4, false) { Some(v) => { tm.tm_year = v - 1900; Ok(()) } None => Err(ParseError::InvalidYear) } } 'y' => { match match_digits_in_range(s, 1, 2, false, 0, 99) { Some(v) => { tm.tm_year = v; Ok(()) } None => Err(ParseError::InvalidYear) } } 'Z' => { if match_str(s, "UTC") || match_str(s, "GMT") { tm.tm_utcoff = 0; Ok(()) } else { // It's odd, but to maintain compatibility with c's // strptime we ignore the timezone. for (i, ch) in s.char_indices() { if ch == ' ' { *s = &s[i..]; return Ok(()) } } *s = ""; Ok(()) } } 'z' => { if parse_char(s, 'Z').is_ok() { tm.tm_utcoff = 0; Ok(()) } else { let sign = if parse_char(s, '+').is_ok() {1} else if parse_char(s, '-').is_ok() {-1} else { return Err(ParseError::InvalidZoneOffset) }; let hours; let minutes; match match_digits(s, 2, 2, false) { Some(h) => hours = h, None => return Err(ParseError::InvalidZoneOffset) } // consume the colon if its present, // just ignore it otherwise let _ = parse_char(s, ':'); match match_digits(s, 2, 2, false) { Some(m) => minutes = m, None => return Err(ParseError::InvalidZoneOffset) } tm.tm_utcoff = sign * (hours * 60 * 60 + minutes * 60); Ok(()) } } '%' => parse_char(s, '%'), ch => Err(ParseError::InvalidFormatSpecifier(ch)) } } fn match_str(s: &mut &str, needle: &str) -> bool { if s.starts_with(needle) { *s = &s[needle.len()..]; true } else { false } } fn match_strs(ss: &mut &str, strs: &[(&str, i32)]) -> Option { for &(needle, value) in strs.iter() { if match_str(ss, needle) { return Some(value) } } None } fn match_digits(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option { match match_digits_i64(ss, min_digits, max_digits, ws) { Some(v) => Some(v as i32), None => None } } fn match_digits_i64(ss: &mut &str, min_digits : usize, max_digits: usize, ws: bool) -> Option { let mut value : i64 = 0; let mut n = 0; if ws { #[allow(deprecated)] // use `trim_start_matches` starting in 1.30 let s2 = ss.trim_left_matches(" "); n = ss.len() - s2.len(); if n > max_digits { return None } } let chars = ss[n..].char_indices(); for (_, ch) in chars.take(max_digits - n) { match ch { '0' ... '9' => value = value * 10 + (ch as i64 - '0' as i64), _ => break, } n += 1; } if n >= min_digits && n <= max_digits { *ss = &ss[n..]; Some(value) } else { None } } fn match_fractional_seconds(ss: &mut &str) -> i32 { let mut value = 0; let mut multiplier = NSEC_PER_SEC / 10; let mut chars = ss.char_indices(); let orig = *ss; for (i, ch) in &mut chars { *ss = &orig[i..]; match ch { '0' ... '9' => { // This will drop digits after the nanoseconds place let digit = ch as i32 - '0' as i32; value += digit * multiplier; multiplier /= 10; } _ => break } } value } fn match_digits_in_range(ss: &mut &str, min_digits : usize, max_digits : usize, ws: bool, min: i32, max: i32) -> Option { let before = *ss; match match_digits(ss, min_digits, max_digits, ws) { Some(val) if val >= min && val <= max => Some(val), _ => { *ss = before; None } } } fn parse_char(s: &mut &str, c: char) -> Result<(), ParseError> { match s.char_indices().next() { Some((i, c2)) => { if c == c2 { *s = &s[i + c2.len_utf8()..]; Ok(()) } else { Err(ParseError::UnexpectedCharacter(c, c2)) } } None => Err(ParseError::InvalidTime), } }