summaryrefslogtreecommitdiffstats
path: root/third_party/rust/chrono/src/format/strftime.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/chrono/src/format/strftime.rs')
-rw-r--r--third_party/rust/chrono/src/format/strftime.rs649
1 files changed, 649 insertions, 0 deletions
diff --git a/third_party/rust/chrono/src/format/strftime.rs b/third_party/rust/chrono/src/format/strftime.rs
new file mode 100644
index 0000000000..93820a2329
--- /dev/null
+++ b/third_party/rust/chrono/src/format/strftime.rs
@@ -0,0 +1,649 @@
+// This is a part of Chrono.
+// See README.md and LICENSE.txt for details.
+
+/*!
+`strftime`/`strptime`-inspired date and time formatting syntax.
+
+## Specifiers
+
+The following specifiers are available both to formatting and parsing.
+
+| Spec. | Example | Description |
+|-------|----------|----------------------------------------------------------------------------|
+| | | **DATE SPECIFIERS:** |
+| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] |
+| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] |
+| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] |
+| | | |
+| `%m` | `07` | Month number (01--12), zero-padded to 2 digits. |
+| `%b` | `Jul` | Abbreviated month name. Always 3 letters. |
+| `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. |
+| `%h` | `Jul` | Same as `%b`. |
+| | | |
+| `%d` | `08` | Day number (01--31), zero-padded to 2 digits. |
+| `%e` | ` 8` | Same as `%d` but space-padded. Same as `%_d`. |
+| | | |
+| `%a` | `Sun` | Abbreviated weekday name. Always 3 letters. |
+| `%A` | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in parsing. |
+| `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. |
+| `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) |
+| | | |
+| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] |
+| `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.|
+| | | |
+| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] |
+| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] |
+| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] |
+| | | |
+| `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. |
+| | | |
+| `%D` | `07/08/01` | Month-day-year format. Same as `%m/%d/%y`. |
+| `%x` | `07/08/01` | Locale's date representation (e.g., 12/31/99). |
+| `%F` | `2001-07-08` | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`. |
+| `%v` | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`. |
+| | | |
+| | | **TIME SPECIFIERS:** |
+| `%H` | `00` | Hour number (00--23), zero-padded to 2 digits. |
+| `%k` | ` 0` | Same as `%H` but space-padded. Same as `%_H`. |
+| `%I` | `12` | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits. |
+| `%l` | `12` | Same as `%I` but space-padded. Same as `%_I`. |
+| | | |
+| `%P` | `am` | `am` or `pm` in 12-hour clocks. |
+| `%p` | `AM` | `AM` or `PM` in 12-hour clocks. |
+| | | |
+| `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. |
+| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] |
+| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] |
+| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] |
+| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] |
+| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] |
+| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] |
+| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] |
+| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] |
+| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] |
+| | | |
+| `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. |
+| `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. |
+| `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). |
+| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. |
+| | | |
+| | | **TIME ZONE SPECIFIERS:** |
+| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] |
+| `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). |
+| `%:z` | `+09:30` | Same as `%z` but with a colon. |
+| `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. |
+| | | |
+| | | **DATE & TIME SPECIFIERS:** |
+|`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). |
+| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] |
+| | | |
+| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]|
+| | | |
+| | | **SPECIAL SPECIFIERS:** |
+| `%t` | | Literal tab (`\t`). |
+| `%n` | | Literal newline (`\n`). |
+| `%%` | | Literal percent sign. |
+
+It is possible to override the default padding behavior of numeric specifiers `%?`.
+This is not allowed for other specifiers and will result in the `BAD_FORMAT` error.
+
+Modifier | Description
+-------- | -----------
+`%-?` | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`)
+`%_?` | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`)
+`%0?` | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`)
+
+Notes:
+
+[^1]: `%Y`:
+ Negative years are allowed in formatting but not in parsing.
+
+[^2]: `%C`, `%y`:
+ This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively.
+
+[^3]: `%U`:
+ Week 1 starts with the first Sunday in that year.
+ It is possible to have week 0 for days before the first Sunday.
+
+[^4]: `%G`, `%g`, `%V`:
+ Week 1 is the first week with at least 4 days in that year.
+ Week 0 does not exist, so this should be used with `%G` or `%g`.
+
+[^5]: `%S`:
+ It accounts for leap seconds, so `60` is possible.
+
+[^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional
+ digits for seconds and colons in the time zone offset.
+ <br>
+ <br>
+ The typical `strftime` implementations have different (and locale-dependent)
+ formats for this specifier. While Chrono's format for `%+` is far more
+ stable, it is best to avoid this specifier if you want to control the exact
+ output.
+
+[^7]: `%s`:
+ This is not padded and can be negative.
+ For the purpose of Chrono, it only accounts for non-leap seconds
+ so it slightly differs from ISO C `strftime` behavior.
+
+[^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`:
+ <br>
+ The default `%f` is right-aligned and always zero-padded to 9 digits
+ for the compatibility with glibc and others,
+ so it always counts the number of nanoseconds since the last whole second.
+ E.g. 7ms after the last second will print `007000000`,
+ and parsing `7000000` will yield the same.
+ <br>
+ <br>
+ The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits
+ according to the precision.
+ E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`),
+ and parsing `.07`, `.070000` etc. will yield the same.
+ Note that they can print or read nothing if the fractional part is zero or
+ the next character is not `.`.
+ <br>
+ <br>
+ The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits
+ according to the number preceding `f`.
+ E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`),
+ and parsing `.07`, `.070000` etc. will yield the same.
+ Note that they can read nothing if the fractional part is zero or
+ the next character is not `.` however will print with the specified length.
+ <br>
+ <br>
+ The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits
+ according to the number preceding `f`, but without the leading dot.
+ E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`),
+ and parsing `07`, `070000` etc. will yield the same.
+ Note that they can read nothing if the fractional part is zero.
+
+[^9]: `%Z`:
+ Offset will not be populated from the parsed data, nor will it be validated.
+ Timezone is completely ignored. Similar to the glibc `strptime` treatment of
+ this format code.
+ <br>
+ <br>
+ It is not possible to reliably convert from an abbreviation to an offset,
+ for example CDT can mean either Central Daylight Time (North America) or
+ China Daylight Time.
+*/
+
+#[cfg(feature = "unstable-locales")]
+use super::{locales, Locale};
+use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad};
+
+#[cfg(feature = "unstable-locales")]
+type Fmt<'a> = Vec<Item<'a>>;
+#[cfg(not(feature = "unstable-locales"))]
+type Fmt<'a> = &'static [Item<'static>];
+
+static D_FMT: &'static [Item<'static>] =
+ &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)];
+static D_T_FMT: &'static [Item<'static>] = &[
+ fix!(ShortWeekdayName),
+ sp!(" "),
+ fix!(ShortMonthName),
+ sp!(" "),
+ nums!(Day),
+ sp!(" "),
+ num0!(Hour),
+ lit!(":"),
+ num0!(Minute),
+ lit!(":"),
+ num0!(Second),
+ sp!(" "),
+ num0!(Year),
+];
+static T_FMT: &'static [Item<'static>] =
+ &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)];
+
+/// Parsing iterator for `strftime`-like format strings.
+#[derive(Clone, Debug)]
+pub struct StrftimeItems<'a> {
+ /// Remaining portion of the string.
+ remainder: &'a str,
+ /// If the current specifier is composed of multiple formatting items (e.g. `%+`),
+ /// parser refers to the statically reconstructed slice of them.
+ /// If `recons` is not empty they have to be returned earlier than the `remainder`.
+ recons: Fmt<'a>,
+ /// Date format
+ d_fmt: Fmt<'a>,
+ /// Date and time format
+ d_t_fmt: Fmt<'a>,
+ /// Time format
+ t_fmt: Fmt<'a>,
+}
+
+impl<'a> StrftimeItems<'a> {
+ /// Creates a new parsing iterator from the `strftime`-like format string.
+ pub fn new(s: &'a str) -> StrftimeItems<'a> {
+ Self::with_remainer(s)
+ }
+
+ /// Creates a new parsing iterator from the `strftime`-like format string.
+ #[cfg(feature = "unstable-locales")]
+ pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
+ let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect();
+ let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect();
+ let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect();
+
+ StrftimeItems {
+ remainder: s,
+ recons: Vec::new(),
+ d_fmt: d_fmt,
+ d_t_fmt: d_t_fmt,
+ t_fmt: t_fmt,
+ }
+ }
+
+ #[cfg(not(feature = "unstable-locales"))]
+ fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
+ static FMT_NONE: &'static [Item<'static>; 0] = &[];
+
+ StrftimeItems {
+ remainder: s,
+ recons: FMT_NONE,
+ d_fmt: D_FMT,
+ d_t_fmt: D_T_FMT,
+ t_fmt: T_FMT,
+ }
+ }
+
+ #[cfg(feature = "unstable-locales")]
+ fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
+ StrftimeItems {
+ remainder: s,
+ recons: Vec::new(),
+ d_fmt: D_FMT.to_vec(),
+ d_t_fmt: D_T_FMT.to_vec(),
+ t_fmt: T_FMT.to_vec(),
+ }
+ }
+}
+
+const HAVE_ALTERNATES: &'static str = "z";
+
+impl<'a> Iterator for StrftimeItems<'a> {
+ type Item = Item<'a>;
+
+ fn next(&mut self) -> Option<Item<'a>> {
+ // we have some reconstructed items to return
+ if !self.recons.is_empty() {
+ let item;
+ #[cfg(feature = "unstable-locales")]
+ {
+ item = self.recons.remove(0);
+ }
+ #[cfg(not(feature = "unstable-locales"))]
+ {
+ item = self.recons[0].clone();
+ self.recons = &self.recons[1..];
+ }
+ return Some(item);
+ }
+
+ match self.remainder.chars().next() {
+ // we are done
+ None => None,
+
+ // the next item is a specifier
+ 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), // premature end of string
+ }
+ };
+ }
+
+ 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),+ $(,)*] => ({
+ #[cfg(feature = "unstable-locales")]
+ {
+ self.recons.clear();
+ $(self.recons.push($tail);)+
+ }
+ #[cfg(not(feature = "unstable-locales"))]
+ {
+ const RECONS: &'static [Item<'static>] = &[$($tail),+];
+ self.recons = RECONS;
+ }
+ $head
+ })
+ }
+
+ macro_rules! recons_from_slice {
+ ($slice:expr) => {{
+ #[cfg(feature = "unstable-locales")]
+ {
+ self.recons.clear();
+ self.recons.extend_from_slice(&$slice[1..]);
+ }
+ #[cfg(not(feature = "unstable-locales"))]
+ {
+ self.recons = &$slice[1..];
+ }
+ $slice[0].clone()
+ }};
+ }
+
+ 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_from_slice!(self.t_fmt),
+ 'Y' => num0!(Year),
+ 'Z' => fix!(TimezoneName),
+ 'a' => fix!(ShortWeekdayName),
+ 'b' | 'h' => fix!(ShortMonthName),
+ 'c' => recons_from_slice!(self.d_t_fmt),
+ '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_from_slice!(self.d_fmt),
+ '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,
+ },
+ '3' => match next!() {
+ 'f' => internal_fix!(Nanosecond3NoDot),
+ _ => Item::Error,
+ },
+ '6' => match next!() {
+ 'f' => internal_fix!(Nanosecond6NoDot),
+ _ => Item::Error,
+ },
+ '9' => match next!() {
+ 'f' => internal_fix!(Nanosecond9NoDot),
+ _ => Item::Error,
+ },
+ '%' => lit!("%"),
+ _ => Item::Error, // no such specifier
+ };
+
+ // adjust `item` if we have any padding modifier
+ 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), // no reconstructed or non-numeric item allowed
+ }
+ } else {
+ Some(item)
+ }
+ }
+
+ // the next item is space
+ Some(c) if c.is_whitespace() => {
+ // `%` is not a whitespace, so `c != '%'` is redundant
+ 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)
+ }
+
+ // the next item is literal
+ _ => {
+ 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>> {
+ // map any error into `[Item::Error]`. useful for easy testing.
+ 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, Timelike};
+
+ let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
+
+ // date specifiers
+ 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");
+
+ // time specifiers
+ 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(), "026490708");
+ assert_eq!(dt.format("%.f").to_string(), ".026490708");
+ assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().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(), ".026490708");
+ assert_eq!(dt.format("%3f").to_string(), "026");
+ assert_eq!(dt.format("%6f").to_string(), "026490");
+ assert_eq!(dt.format("%9f").to_string(), "026490708");
+ 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");
+
+ // time zone specifiers
+ //assert_eq!(dt.format("%Z").to_string(), "ACST");
+ assert_eq!(dt.format("%z").to_string(), "+0930");
+ assert_eq!(dt.format("%:z").to_string(), "+09:30");
+
+ // date & time specifiers
+ 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.026490708+09:30");
+ assert_eq!(
+ dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
+ "2001-07-08T00:34:60.026490+09:30"
+ );
+ assert_eq!(dt.format("%s").to_string(), "994518299");
+
+ // special specifiers
+ assert_eq!(dt.format("%t").to_string(), "\t");
+ assert_eq!(dt.format("%n").to_string(), "\n");
+ assert_eq!(dt.format("%%").to_string(), "%");
+}
+
+#[cfg(feature = "unstable-locales")]
+#[test]
+fn test_strftime_docs_localized() {
+ use {FixedOffset, TimeZone};
+
+ let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
+
+ // date specifiers
+ assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
+ assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
+ assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
+ assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
+ assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
+ assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
+ assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
+ assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
+ assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
+
+ // time specifiers
+ assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
+ assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
+ assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
+ assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
+ assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
+ assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
+
+ // date & time specifiers
+ assert_eq!(
+ dt.format_localized("%c", Locale::fr_BE).to_string(),
+ "dim 08 jui 2001 00:34:60 +09:30"
+ );
+}