use super::{Item, Numeric, Fixed, InternalFixed, InternalInternal, Pad};
#[derive(Clone, Debug)]
pub struct StrftimeItems<'a> {
remainder: &'a str,
recons: &'static [Item<'static>],
}
impl<'a> StrftimeItems<'a> {
pub fn new(s: &'a str) -> StrftimeItems<'a> {
static FMT_NONE: [Item<'static>; 0] = [];
StrftimeItems { remainder: s, recons: &FMT_NONE }
}
}
const HAVE_ALTERNATES: &'static str = "z";
impl<'a> Iterator for StrftimeItems<'a> {
type Item = Item<'a>;
fn next(&mut self) -> Option<Item<'a>> {
if !self.recons.is_empty() {
let item = self.recons[0].clone();
self.recons = &self.recons[1..];
return Some(item);
}
match self.remainder.chars().next() {
None => None,
Some('%') => {
self.remainder = &self.remainder[1..];
macro_rules! next {
() => (
match self.remainder.chars().next() {
Some(x) => {
self.remainder = &self.remainder[x.len_utf8()..];
x
},
None => return Some(Item::Error), }
)
}
let spec = next!();
let pad_override = match spec {
'-' => Some(Pad::None),
'0' => Some(Pad::Zero),
'_' => Some(Pad::Space),
_ => None,
};
let is_alternate = spec == '#';
let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
if is_alternate && !HAVE_ALTERNATES.contains(spec) {
return Some(Item::Error);
}
macro_rules! recons {
[$head:expr, $($tail:expr),+] => ({
const RECONS: &'static [Item<'static>] = &[$($tail),+];
self.recons = RECONS;
$head
})
}
let item = match spec {
'A' => fix!(LongWeekdayName),
'B' => fix!(LongMonthName),
'C' => num0!(YearDiv100),
'D' => recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
num0!(YearMod100)],
'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)],
'G' => num0!(IsoYear),
'H' => num0!(Hour),
'I' => num0!(Hour12),
'M' => num0!(Minute),
'P' => fix!(LowerAmPm),
'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)],
'S' => num0!(Second),
'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
'U' => num0!(WeekFromSun),
'V' => num0!(IsoWeek),
'W' => num0!(WeekFromMon),
'X' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
'Y' => num0!(Year),
'Z' => fix!(TimezoneName),
'a' => fix!(ShortWeekdayName),
'b' | 'h' => fix!(ShortMonthName),
'c' => recons![fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName),
sp!(" "), nums!(Day), sp!(" "), num0!(Hour), lit!(":"),
num0!(Minute), lit!(":"), num0!(Second), sp!(" "), num0!(Year)],
'd' => num0!(Day),
'e' => nums!(Day),
'f' => num0!(Nanosecond),
'g' => num0!(IsoYearMod100),
'j' => num0!(Ordinal),
'k' => nums!(Hour),
'l' => nums!(Hour12),
'm' => num0!(Month),
'n' => sp!("\n"),
'p' => fix!(UpperAmPm),
'r' => recons![num0!(Hour12), lit!(":"), num0!(Minute), lit!(":"),
num0!(Second), sp!(" "), fix!(UpperAmPm)],
's' => num!(Timestamp),
't' => sp!("\t"),
'u' => num!(WeekdayFromMon),
'v' => recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"),
num0!(Year)],
'w' => num!(NumDaysFromSun),
'x' => recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"),
num0!(YearMod100)],
'y' => num0!(YearMod100),
'z' => if is_alternate {
internal_fix!(TimezoneOffsetPermissive)
} else {
fix!(TimezoneOffset)
},
'+' => fix!(RFC3339),
':' => match next!() {
'z' => fix!(TimezoneOffsetColon),
_ => Item::Error,
},
'.' => match next!() {
'3' => match next!() {
'f' => fix!(Nanosecond3),
_ => Item::Error,
},
'6' => match next!() {
'f' => fix!(Nanosecond6),
_ => Item::Error,
},
'9' => match next!() {
'f' => fix!(Nanosecond9),
_ => Item::Error,
},
'f' => fix!(Nanosecond),
_ => Item::Error,
},
'%' => lit!("%"),
_ => Item::Error, };
if let Some(new_pad) = pad_override {
match item {
Item::Numeric(ref kind, _pad) if self.recons.is_empty() =>
Some(Item::Numeric(kind.clone(), new_pad)),
_ => Some(Item::Error), }
} else {
Some(item)
}
},
Some(c) if c.is_whitespace() => {
let nextspec = self.remainder.find(|c: char| !c.is_whitespace())
.unwrap_or_else(|| self.remainder.len());
assert!(nextspec > 0);
let item = sp!(&self.remainder[..nextspec]);
self.remainder = &self.remainder[nextspec..];
Some(item)
},
_ => {
let nextspec = self.remainder.find(|c: char| c.is_whitespace() || c == '%')
.unwrap_or_else(|| self.remainder.len());
assert!(nextspec > 0);
let item = lit!(&self.remainder[..nextspec]);
self.remainder = &self.remainder[nextspec..];
Some(item)
},
}
}
}
#[cfg(test)]
#[test]
fn test_strftime_items() {
fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> {
let items = StrftimeItems::new(s);
let items = items.map(|spec| if spec == Item::Error {None} else {Some(spec)});
items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error])
}
assert_eq!(parse_and_collect(""), []);
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
assert_eq!(parse_and_collect("a b\t\nc"), [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"),
lit!("c")]);
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
assert_eq!(parse_and_collect("%Y-%m-%d"), [num0!(Year), lit!("-"), num0!(Month), lit!("-"),
num0!(Day)]);
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
assert_eq!(parse_and_collect("%"), [Item::Error]);
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
assert_eq!(parse_and_collect("%.j"), [Item::Error]);
assert_eq!(parse_and_collect("%:j"), [Item::Error]);
assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
assert_eq!(parse_and_collect("%.e"), [Item::Error]);
assert_eq!(parse_and_collect("%:e"), [Item::Error]);
assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
}
#[cfg(test)]
#[test]
fn test_strftime_docs() {
use {FixedOffset, TimeZone};
let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_000);
assert_eq!(dt.format("%Y").to_string(), "2001");
assert_eq!(dt.format("%C").to_string(), "20");
assert_eq!(dt.format("%y").to_string(), "01");
assert_eq!(dt.format("%m").to_string(), "07");
assert_eq!(dt.format("%b").to_string(), "Jul");
assert_eq!(dt.format("%B").to_string(), "July");
assert_eq!(dt.format("%h").to_string(), "Jul");
assert_eq!(dt.format("%d").to_string(), "08");
assert_eq!(dt.format("%e").to_string(), " 8");
assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
assert_eq!(dt.format("%a").to_string(), "Sun");
assert_eq!(dt.format("%A").to_string(), "Sunday");
assert_eq!(dt.format("%w").to_string(), "0");
assert_eq!(dt.format("%u").to_string(), "7");
assert_eq!(dt.format("%U").to_string(), "28");
assert_eq!(dt.format("%W").to_string(), "27");
assert_eq!(dt.format("%G").to_string(), "2001");
assert_eq!(dt.format("%g").to_string(), "01");
assert_eq!(dt.format("%V").to_string(), "27");
assert_eq!(dt.format("%j").to_string(), "189");
assert_eq!(dt.format("%D").to_string(), "07/08/01");
assert_eq!(dt.format("%x").to_string(), "07/08/01");
assert_eq!(dt.format("%F").to_string(), "2001-07-08");
assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
assert_eq!(dt.format("%H").to_string(), "00");
assert_eq!(dt.format("%k").to_string(), " 0");
assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
assert_eq!(dt.format("%I").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
assert_eq!(dt.format("%P").to_string(), "am");
assert_eq!(dt.format("%p").to_string(), "AM");
assert_eq!(dt.format("%M").to_string(), "34");
assert_eq!(dt.format("%S").to_string(), "60");
assert_eq!(dt.format("%f").to_string(), "026490000");
assert_eq!(dt.format("%.f").to_string(), ".026490");
assert_eq!(dt.format("%.3f").to_string(), ".026");
assert_eq!(dt.format("%.6f").to_string(), ".026490");
assert_eq!(dt.format("%.9f").to_string(), ".026490000");
assert_eq!(dt.format("%R").to_string(), "00:34");
assert_eq!(dt.format("%T").to_string(), "00:34:60");
assert_eq!(dt.format("%X").to_string(), "00:34:60");
assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
assert_eq!(dt.format("%z").to_string(), "+0930");
assert_eq!(dt.format("%:z").to_string(), "+09:30");
assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490+09:30");
assert_eq!(dt.format("%s").to_string(), "994518299");
assert_eq!(dt.format("%t").to_string(), "\t");
assert_eq!(dt.format("%n").to_string(), "\n");
assert_eq!(dt.format("%%").to_string(), "%");
}