summaryrefslogtreecommitdiffstats
path: root/vendor/chrono/src/format
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/chrono/src/format
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/chrono/src/format')
-rw-r--r--vendor/chrono/src/format/locales.rs33
-rw-r--r--vendor/chrono/src/format/mod.rs938
-rw-r--r--vendor/chrono/src/format/parse.rs934
-rw-r--r--vendor/chrono/src/format/parsed.rs1283
-rw-r--r--vendor/chrono/src/format/scan.rs350
-rw-r--r--vendor/chrono/src/format/strftime.rs649
6 files changed, 4187 insertions, 0 deletions
diff --git a/vendor/chrono/src/format/locales.rs b/vendor/chrono/src/format/locales.rs
new file mode 100644
index 000000000..f7b4bbde5
--- /dev/null
+++ b/vendor/chrono/src/format/locales.rs
@@ -0,0 +1,33 @@
+use pure_rust_locales::{locale_match, Locale};
+
+pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] {
+ locale_match!(locale => LC_TIME::ABMON)
+}
+
+pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] {
+ locale_match!(locale => LC_TIME::MON)
+}
+
+pub(crate) fn short_weekdays(locale: Locale) -> &'static [&'static str] {
+ locale_match!(locale => LC_TIME::ABDAY)
+}
+
+pub(crate) fn long_weekdays(locale: Locale) -> &'static [&'static str] {
+ locale_match!(locale => LC_TIME::DAY)
+}
+
+pub(crate) fn am_pm(locale: Locale) -> &'static [&'static str] {
+ locale_match!(locale => LC_TIME::AM_PM)
+}
+
+pub(crate) fn d_fmt(locale: Locale) -> &'static str {
+ locale_match!(locale => LC_TIME::D_FMT)
+}
+
+pub(crate) fn d_t_fmt(locale: Locale) -> &'static str {
+ locale_match!(locale => LC_TIME::D_T_FMT)
+}
+
+pub(crate) fn t_fmt(locale: Locale) -> &'static str {
+ locale_match!(locale => LC_TIME::T_FMT)
+}
diff --git a/vendor/chrono/src/format/mod.rs b/vendor/chrono/src/format/mod.rs
new file mode 100644
index 000000000..a641f196d
--- /dev/null
+++ b/vendor/chrono/src/format/mod.rs
@@ -0,0 +1,938 @@
+// This is a part of Chrono.
+// See README.md and LICENSE.txt for details.
+
+//! Formatting (and parsing) utilities for date and time.
+//!
+//! This module provides the common types and routines to implement,
+//! for example, [`DateTime::format`](../struct.DateTime.html#method.format) or
+//! [`DateTime::parse_from_str`](../struct.DateTime.html#method.parse_from_str) methods.
+//! For most cases you should use these high-level interfaces.
+//!
+//! Internally the formatting and parsing shares the same abstract **formatting items**,
+//! which are just an [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) of
+//! the [`Item`](./enum.Item.html) type.
+//! They are generated from more readable **format strings**;
+//! currently Chrono supports [one built-in syntax closely resembling
+//! C's `strftime` format](./strftime/index.html).
+
+#![allow(ellipsis_inclusive_range_patterns)]
+
+#[cfg(feature = "alloc")]
+use alloc::boxed::Box;
+#[cfg(feature = "alloc")]
+use alloc::string::{String, ToString};
+#[cfg(any(feature = "alloc", feature = "std", test))]
+use core::borrow::Borrow;
+use core::fmt;
+use core::str::FromStr;
+#[cfg(any(feature = "std", test))]
+use std::error::Error;
+
+#[cfg(any(feature = "alloc", feature = "std", test))]
+use naive::{NaiveDate, NaiveTime};
+#[cfg(any(feature = "alloc", feature = "std", test))]
+use offset::{FixedOffset, Offset};
+#[cfg(any(feature = "alloc", feature = "std", test))]
+use {Datelike, Timelike};
+use {Month, ParseMonthError, ParseWeekdayError, Weekday};
+
+#[cfg(feature = "unstable-locales")]
+pub(crate) mod locales;
+
+pub use self::parse::parse;
+pub use self::parsed::Parsed;
+pub use self::strftime::StrftimeItems;
+/// L10n locales.
+#[cfg(feature = "unstable-locales")]
+pub use pure_rust_locales::Locale;
+
+#[cfg(not(feature = "unstable-locales"))]
+#[derive(Debug)]
+struct Locale;
+
+/// An uninhabited type used for `InternalNumeric` and `InternalFixed` below.
+#[derive(Clone, PartialEq, Eq)]
+enum Void {}
+
+/// Padding characters for numeric items.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum Pad {
+ /// No padding.
+ None,
+ /// Zero (`0`) padding.
+ Zero,
+ /// Space padding.
+ Space,
+}
+
+/// Numeric item types.
+/// They have associated formatting width (FW) and parsing width (PW).
+///
+/// The **formatting width** is the minimal width to be formatted.
+/// If the number is too short, and the padding is not [`Pad::None`](./enum.Pad.html#variant.None),
+/// then it is left-padded.
+/// If the number is too long or (in some cases) negative, it is printed as is.
+///
+/// The **parsing width** is the maximal width to be scanned.
+/// The parser only tries to consume from one to given number of digits (greedily).
+/// It also trims the preceding whitespace if any.
+/// It cannot parse the negative number, so some date and time cannot be formatted then
+/// parsed with the same formatting items.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub enum Numeric {
+ /// Full Gregorian year (FW=4, PW=∞).
+ /// May accept years before 1 BCE or after 9999 CE, given an initial sign.
+ Year,
+ /// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year.
+ YearDiv100,
+ /// Gregorian year modulo 100 (FW=PW=2). Cannot be negative.
+ YearMod100,
+ /// Year in the ISO week date (FW=4, PW=∞).
+ /// May accept years before 1 BCE or after 9999 CE, given an initial sign.
+ IsoYear,
+ /// Year in the ISO week date, divided by 100 (FW=PW=2). Implies the non-negative year.
+ IsoYearDiv100,
+ /// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative.
+ IsoYearMod100,
+ /// Month (FW=PW=2).
+ Month,
+ /// Day of the month (FW=PW=2).
+ Day,
+ /// Week number, where the week 1 starts at the first Sunday of January (FW=PW=2).
+ WeekFromSun,
+ /// Week number, where the week 1 starts at the first Monday of January (FW=PW=2).
+ WeekFromMon,
+ /// Week number in the ISO week date (FW=PW=2).
+ IsoWeek,
+ /// Day of the week, where Sunday = 0 and Saturday = 6 (FW=PW=1).
+ NumDaysFromSun,
+ /// Day of the week, where Monday = 1 and Sunday = 7 (FW=PW=1).
+ WeekdayFromMon,
+ /// Day of the year (FW=PW=3).
+ Ordinal,
+ /// Hour number in the 24-hour clocks (FW=PW=2).
+ Hour,
+ /// Hour number in the 12-hour clocks (FW=PW=2).
+ Hour12,
+ /// The number of minutes since the last whole hour (FW=PW=2).
+ Minute,
+ /// The number of seconds since the last whole minute (FW=PW=2).
+ Second,
+ /// The number of nanoseconds since the last whole second (FW=PW=9).
+ /// Note that this is *not* left-aligned;
+ /// see also [`Fixed::Nanosecond`](./enum.Fixed.html#variant.Nanosecond).
+ Nanosecond,
+ /// The number of non-leap seconds since the midnight UTC on January 1, 1970 (FW=1, PW=∞).
+ /// For formatting, it assumes UTC upon the absence of time zone offset.
+ Timestamp,
+
+ /// Internal uses only.
+ ///
+ /// This item exists so that one can add additional internal-only formatting
+ /// without breaking major compatibility (as enum variants cannot be selectively private).
+ Internal(InternalNumeric),
+}
+
+/// An opaque type representing numeric item types for internal uses only.
+pub struct InternalNumeric {
+ _dummy: Void,
+}
+
+impl Clone for InternalNumeric {
+ fn clone(&self) -> Self {
+ match self._dummy {}
+ }
+}
+
+impl PartialEq for InternalNumeric {
+ fn eq(&self, _other: &InternalNumeric) -> bool {
+ match self._dummy {}
+ }
+}
+
+impl Eq for InternalNumeric {}
+
+impl fmt::Debug for InternalNumeric {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "<InternalNumeric>")
+ }
+}
+
+/// Fixed-format item types.
+///
+/// They have their own rules of formatting and parsing.
+/// Otherwise noted, they print in the specified cases but parse case-insensitively.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub enum Fixed {
+ /// Abbreviated month names.
+ ///
+ /// Prints a three-letter-long name in the title case, reads the same name in any case.
+ ShortMonthName,
+ /// Full month names.
+ ///
+ /// Prints a full name in the title case, reads either a short or full name in any case.
+ LongMonthName,
+ /// Abbreviated day of the week names.
+ ///
+ /// Prints a three-letter-long name in the title case, reads the same name in any case.
+ ShortWeekdayName,
+ /// Full day of the week names.
+ ///
+ /// Prints a full name in the title case, reads either a short or full name in any case.
+ LongWeekdayName,
+ /// AM/PM.
+ ///
+ /// Prints in lower case, reads in any case.
+ LowerAmPm,
+ /// AM/PM.
+ ///
+ /// Prints in upper case, reads in any case.
+ UpperAmPm,
+ /// An optional dot plus one or more digits for left-aligned nanoseconds.
+ /// May print nothing, 3, 6 or 9 digits according to the available accuracy.
+ /// See also [`Numeric::Nanosecond`](./enum.Numeric.html#variant.Nanosecond).
+ Nanosecond,
+ /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3.
+ Nanosecond3,
+ /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6.
+ Nanosecond6,
+ /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9.
+ Nanosecond9,
+ /// Timezone name.
+ ///
+ /// It does not support parsing, its use in the parser is an immediate failure.
+ TimezoneName,
+ /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`).
+ ///
+ /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace.
+ /// The offset is limited from `-24:00` to `+24:00`,
+ /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
+ TimezoneOffsetColon,
+ /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`).
+ ///
+ /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace,
+ /// and `Z` can be either in upper case or in lower case.
+ /// The offset is limited from `-24:00` to `+24:00`,
+ /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
+ TimezoneOffsetColonZ,
+ /// Same as [`TimezoneOffsetColon`](#variant.TimezoneOffsetColon) but prints no colon.
+ /// Parsing allows an optional colon.
+ TimezoneOffset,
+ /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ) but prints no colon.
+ /// Parsing allows an optional colon.
+ TimezoneOffsetZ,
+ /// RFC 2822 date and time syntax. Commonly used for email and MIME date and time.
+ RFC2822,
+ /// RFC 3339 & ISO 8601 date and time syntax.
+ RFC3339,
+
+ /// Internal uses only.
+ ///
+ /// This item exists so that one can add additional internal-only formatting
+ /// without breaking major compatibility (as enum variants cannot be selectively private).
+ Internal(InternalFixed),
+}
+
+/// An opaque type representing fixed-format item types for internal uses only.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct InternalFixed {
+ val: InternalInternal,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum InternalInternal {
+ /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but
+ /// allows missing minutes (per [ISO 8601][iso8601]).
+ ///
+ /// # Panics
+ ///
+ /// If you try to use this for printing.
+ ///
+ /// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC
+ TimezoneOffsetPermissive,
+ /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot.
+ Nanosecond3NoDot,
+ /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot.
+ Nanosecond6NoDot,
+ /// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot.
+ Nanosecond9NoDot,
+}
+
+/// A single formatting item. This is used for both formatting and parsing.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub enum Item<'a> {
+ /// A literally printed and parsed text.
+ Literal(&'a str),
+ /// Same as `Literal` but with the string owned by the item.
+ #[cfg(any(feature = "alloc", feature = "std", test))]
+ OwnedLiteral(Box<str>),
+ /// Whitespace. Prints literally but reads zero or more whitespace.
+ Space(&'a str),
+ /// Same as `Space` but with the string owned by the item.
+ #[cfg(any(feature = "alloc", feature = "std", test))]
+ OwnedSpace(Box<str>),
+ /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
+ /// the parser simply ignores any padded whitespace and zeroes.
+ Numeric(Numeric, Pad),
+ /// Fixed-format item.
+ Fixed(Fixed),
+ /// Issues a formatting error. Used to signal an invalid format string.
+ Error,
+}
+
+macro_rules! lit {
+ ($x:expr) => {
+ Item::Literal($x)
+ };
+}
+macro_rules! sp {
+ ($x:expr) => {
+ Item::Space($x)
+ };
+}
+macro_rules! num {
+ ($x:ident) => {
+ Item::Numeric(Numeric::$x, Pad::None)
+ };
+}
+macro_rules! num0 {
+ ($x:ident) => {
+ Item::Numeric(Numeric::$x, Pad::Zero)
+ };
+}
+macro_rules! nums {
+ ($x:ident) => {
+ Item::Numeric(Numeric::$x, Pad::Space)
+ };
+}
+macro_rules! fix {
+ ($x:ident) => {
+ Item::Fixed(Fixed::$x)
+ };
+}
+macro_rules! internal_fix {
+ ($x:ident) => {
+ Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x }))
+ };
+}
+
+/// An error from the `parse` function.
+#[derive(Debug, Clone, PartialEq, Eq, Copy)]
+pub struct ParseError(ParseErrorKind);
+
+/// The category of parse error
+#[derive(Debug, Clone, PartialEq, Eq, Copy)]
+enum ParseErrorKind {
+ /// Given field is out of permitted range.
+ OutOfRange,
+
+ /// There is no possible date and time value with given set of fields.
+ ///
+ /// This does not include the out-of-range conditions, which are trivially invalid.
+ /// It includes the case that there are one or more fields that are inconsistent to each other.
+ Impossible,
+
+ /// Given set of fields is not enough to make a requested date and time value.
+ ///
+ /// Note that there *may* be a case that given fields constrain the possible values so much
+ /// that there is a unique possible value. Chrono only tries to be correct for
+ /// most useful sets of fields however, as such constraint solving can be expensive.
+ NotEnough,
+
+ /// The input string has some invalid character sequence for given formatting items.
+ Invalid,
+
+ /// The input string has been prematurely ended.
+ TooShort,
+
+ /// All formatting items have been read but there is a remaining input.
+ TooLong,
+
+ /// There was an error on the formatting string, or there were non-supported formating items.
+ BadFormat,
+}
+
+/// Same as `Result<T, ParseError>`.
+pub type ParseResult<T> = Result<T, ParseError>;
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ ParseErrorKind::OutOfRange => write!(f, "input is out of range"),
+ ParseErrorKind::Impossible => write!(f, "no possible date and time matching input"),
+ ParseErrorKind::NotEnough => write!(f, "input is not enough for unique date and time"),
+ ParseErrorKind::Invalid => write!(f, "input contains invalid characters"),
+ ParseErrorKind::TooShort => write!(f, "premature end of input"),
+ ParseErrorKind::TooLong => write!(f, "trailing input"),
+ ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"),
+ }
+ }
+}
+
+#[cfg(any(feature = "std", test))]
+impl Error for ParseError {
+ #[allow(deprecated)]
+ fn description(&self) -> &str {
+ "parser error, see to_string() for details"
+ }
+}
+
+// to be used in this module and submodules
+const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange);
+const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible);
+const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough);
+const INVALID: ParseError = ParseError(ParseErrorKind::Invalid);
+const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort);
+const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
+const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
+
+/// Formats single formatting item
+#[cfg(any(feature = "alloc", feature = "std", test))]
+pub fn format_item<'a>(
+ w: &mut fmt::Formatter,
+ date: Option<&NaiveDate>,
+ time: Option<&NaiveTime>,
+ off: Option<&(String, FixedOffset)>,
+ item: &Item<'a>,
+) -> fmt::Result {
+ let mut result = String::new();
+ format_inner(&mut result, date, time, off, item, None)?;
+ w.pad(&result)
+}
+
+#[cfg(any(feature = "alloc", feature = "std", test))]
+fn format_inner<'a>(
+ result: &mut String,
+ date: Option<&NaiveDate>,
+ time: Option<&NaiveTime>,
+ off: Option<&(String, FixedOffset)>,
+ item: &Item<'a>,
+ _locale: Option<Locale>,
+) -> fmt::Result {
+ #[cfg(feature = "unstable-locales")]
+ let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = {
+ let locale = _locale.unwrap_or(Locale::POSIX);
+ let am_pm = locales::am_pm(locale);
+ (
+ locales::short_months(locale),
+ locales::long_months(locale),
+ locales::short_weekdays(locale),
+ locales::long_weekdays(locale),
+ am_pm,
+ &[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()],
+ )
+ };
+ #[cfg(not(feature = "unstable-locales"))]
+ let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = {
+ (
+ &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+ &[
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ],
+ &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+ &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+ &["AM", "PM"],
+ &["am", "pm"],
+ )
+ };
+
+ use core::fmt::Write;
+ use div::{div_floor, mod_floor};
+
+ match *item {
+ Item::Literal(s) | Item::Space(s) => result.push_str(s),
+ #[cfg(any(feature = "alloc", feature = "std", test))]
+ Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s),
+
+ Item::Numeric(ref spec, ref pad) => {
+ use self::Numeric::*;
+
+ let week_from_sun = |d: &NaiveDate| {
+ (d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7
+ };
+ let week_from_mon = |d: &NaiveDate| {
+ (d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7
+ };
+
+ let (width, v) = match *spec {
+ Year => (4, date.map(|d| i64::from(d.year()))),
+ YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))),
+ YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))),
+ IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
+ IsoYearDiv100 => (2, date.map(|d| div_floor(i64::from(d.iso_week().year()), 100))),
+ IsoYearMod100 => (2, date.map(|d| mod_floor(i64::from(d.iso_week().year()), 100))),
+ Month => (2, date.map(|d| i64::from(d.month()))),
+ Day => (2, date.map(|d| i64::from(d.day()))),
+ WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
+ WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
+ IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
+ NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))),
+ WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))),
+ Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
+ Hour => (2, time.map(|t| i64::from(t.hour()))),
+ Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
+ Minute => (2, time.map(|t| i64::from(t.minute()))),
+ Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))),
+ Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
+ Timestamp => (
+ 1,
+ match (date, time, off) {
+ (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()),
+ (Some(d), Some(t), Some(&(_, off))) => {
+ Some((d.and_time(*t) - off).timestamp())
+ }
+ (_, _, _) => None,
+ },
+ ),
+
+ // for the future expansion
+ Internal(ref int) => match int._dummy {},
+ };
+
+ if let Some(v) = v {
+ if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) {
+ // non-four-digit years require an explicit sign as per ISO 8601
+ match *pad {
+ Pad::None => write!(result, "{:+}", v),
+ Pad::Zero => write!(result, "{:+01$}", v, width + 1),
+ Pad::Space => write!(result, "{:+1$}", v, width + 1),
+ }
+ } else {
+ match *pad {
+ Pad::None => write!(result, "{}", v),
+ Pad::Zero => write!(result, "{:01$}", v, width),
+ Pad::Space => write!(result, "{:1$}", v, width),
+ }
+ }?
+ } else {
+ return Err(fmt::Error); // insufficient arguments for given format
+ }
+ }
+
+ Item::Fixed(ref spec) => {
+ use self::Fixed::*;
+
+ /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`.
+ /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true.
+ fn write_local_minus_utc(
+ result: &mut String,
+ off: FixedOffset,
+ allow_zulu: bool,
+ use_colon: bool,
+ ) -> fmt::Result {
+ let off = off.local_minus_utc();
+ if !allow_zulu || off != 0 {
+ let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
+ if use_colon {
+ write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60)
+ } else {
+ write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60)
+ }
+ } else {
+ result.push_str("Z");
+ Ok(())
+ }
+ }
+
+ let ret =
+ match *spec {
+ ShortMonthName => date.map(|d| {
+ result.push_str(short_months[d.month0() as usize]);
+ Ok(())
+ }),
+ LongMonthName => date.map(|d| {
+ result.push_str(long_months[d.month0() as usize]);
+ Ok(())
+ }),
+ ShortWeekdayName => date.map(|d| {
+ result
+ .push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]);
+ Ok(())
+ }),
+ LongWeekdayName => date.map(|d| {
+ result.push_str(long_weekdays[d.weekday().num_days_from_sunday() as usize]);
+ Ok(())
+ }),
+ LowerAmPm => time.map(|t| {
+ #[cfg_attr(feature = "cargo-clippy", allow(useless_asref))]
+ {
+ result.push_str(if t.hour12().0 {
+ am_pm_lowercase[1].as_ref()
+ } else {
+ am_pm_lowercase[0].as_ref()
+ });
+ }
+ Ok(())
+ }),
+ UpperAmPm => time.map(|t| {
+ result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] });
+ Ok(())
+ }),
+ Nanosecond => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ if nano == 0 {
+ Ok(())
+ } else if nano % 1_000_000 == 0 {
+ write!(result, ".{:03}", nano / 1_000_000)
+ } else if nano % 1_000 == 0 {
+ write!(result, ".{:06}", nano / 1_000)
+ } else {
+ write!(result, ".{:09}", nano)
+ }
+ }),
+ Nanosecond3 => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(result, ".{:03}", nano / 1_000_000)
+ }),
+ Nanosecond6 => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(result, ".{:06}", nano / 1_000)
+ }),
+ Nanosecond9 => time.map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(result, ".{:09}", nano)
+ }),
+ Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time
+ .map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(result, "{:03}", nano / 1_000_000)
+ }),
+ Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time
+ .map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(result, "{:06}", nano / 1_000)
+ }),
+ Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time
+ .map(|t| {
+ let nano = t.nanosecond() % 1_000_000_000;
+ write!(result, "{:09}", nano)
+ }),
+ TimezoneName => off.map(|&(ref name, _)| {
+ result.push_str(name);
+ Ok(())
+ }),
+ TimezoneOffsetColon => {
+ off.map(|&(_, off)| write_local_minus_utc(result, off, false, true))
+ }
+ TimezoneOffsetColonZ => {
+ off.map(|&(_, off)| write_local_minus_utc(result, off, true, true))
+ }
+ TimezoneOffset => {
+ off.map(|&(_, off)| write_local_minus_utc(result, off, false, false))
+ }
+ TimezoneOffsetZ => {
+ off.map(|&(_, off)| write_local_minus_utc(result, off, true, false))
+ }
+ Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
+ panic!("Do not try to write %#z it is undefined")
+ }
+ RFC2822 =>
+ // same as `%a, %d %b %Y %H:%M:%S %z`
+ {
+ if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
+ let sec = t.second() + t.nanosecond() / 1_000_000_000;
+ write!(
+ result,
+ "{}, {:02} {} {:04} {:02}:{:02}:{:02} ",
+ short_weekdays[d.weekday().num_days_from_sunday() as usize],
+ d.day(),
+ short_months[d.month0() as usize],
+ d.year(),
+ t.hour(),
+ t.minute(),
+ sec
+ )?;
+ Some(write_local_minus_utc(result, off, false, false))
+ } else {
+ None
+ }
+ }
+ RFC3339 =>
+ // same as `%Y-%m-%dT%H:%M:%S%.f%:z`
+ {
+ if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
+ // reuse `Debug` impls which already print ISO 8601 format.
+ // this is faster in this way.
+ write!(result, "{:?}T{:?}", d, t)?;
+ Some(write_local_minus_utc(result, off, false, true))
+ } else {
+ None
+ }
+ }
+ };
+
+ match ret {
+ Some(ret) => ret?,
+ None => return Err(fmt::Error), // insufficient arguments for given format
+ }
+ }
+
+ Item::Error => return Err(fmt::Error),
+ }
+ Ok(())
+}
+
+/// Tries to format given arguments with given formatting items.
+/// Internally used by `DelayedFormat`.
+#[cfg(any(feature = "alloc", feature = "std", test))]
+pub fn format<'a, I, B>(
+ w: &mut fmt::Formatter,
+ date: Option<&NaiveDate>,
+ time: Option<&NaiveTime>,
+ off: Option<&(String, FixedOffset)>,
+ items: I,
+) -> fmt::Result
+where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+{
+ let mut result = String::new();
+ for item in items {
+ format_inner(&mut result, date, time, off, item.borrow(), None)?;
+ }
+ w.pad(&result)
+}
+
+mod parsed;
+
+// due to the size of parsing routines, they are in separate modules.
+mod parse;
+mod scan;
+
+pub mod strftime;
+
+/// A *temporary* object which can be used as an argument to `format!` or others.
+/// This is normally constructed via `format` methods of each date and time type.
+#[cfg(any(feature = "alloc", feature = "std", test))]
+#[derive(Debug)]
+pub struct DelayedFormat<I> {
+ /// The date view, if any.
+ date: Option<NaiveDate>,
+ /// The time view, if any.
+ time: Option<NaiveTime>,
+ /// The name and local-to-UTC difference for the offset (timezone), if any.
+ off: Option<(String, FixedOffset)>,
+ /// An iterator returning formatting items.
+ items: I,
+ /// Locale used for text.
+ locale: Option<Locale>,
+}
+
+#[cfg(any(feature = "alloc", feature = "std", test))]
+impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
+ /// Makes a new `DelayedFormat` value out of local date and time.
+ pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
+ DelayedFormat { date: date, time: time, off: None, items: items, locale: None }
+ }
+
+ /// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
+ pub fn new_with_offset<Off>(
+ date: Option<NaiveDate>,
+ time: Option<NaiveTime>,
+ offset: &Off,
+ items: I,
+ ) -> DelayedFormat<I>
+ where
+ Off: Offset + fmt::Display,
+ {
+ let name_and_diff = (offset.to_string(), offset.fix());
+ DelayedFormat {
+ date: date,
+ time: time,
+ off: Some(name_and_diff),
+ items: items,
+ locale: None,
+ }
+ }
+
+ /// Makes a new `DelayedFormat` value out of local date and time and locale.
+ #[cfg(feature = "unstable-locales")]
+ pub fn new_with_locale(
+ date: Option<NaiveDate>,
+ time: Option<NaiveTime>,
+ items: I,
+ locale: Locale,
+ ) -> DelayedFormat<I> {
+ DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) }
+ }
+
+ /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
+ #[cfg(feature = "unstable-locales")]
+ pub fn new_with_offset_and_locale<Off>(
+ date: Option<NaiveDate>,
+ time: Option<NaiveTime>,
+ offset: &Off,
+ items: I,
+ locale: Locale,
+ ) -> DelayedFormat<I>
+ where
+ Off: Offset + fmt::Display,
+ {
+ let name_and_diff = (offset.to_string(), offset.fix());
+ DelayedFormat {
+ date: date,
+ time: time,
+ off: Some(name_and_diff),
+ items: items,
+ locale: Some(locale),
+ }
+ }
+}
+
+#[cfg(any(feature = "alloc", feature = "std", test))]
+impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ #[cfg(feature = "unstable-locales")]
+ {
+ if let Some(locale) = self.locale {
+ return format_localized(
+ f,
+ self.date.as_ref(),
+ self.time.as_ref(),
+ self.off.as_ref(),
+ self.items.clone(),
+ locale,
+ );
+ }
+ }
+
+ format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
+ }
+}
+
+// this implementation is here only because we need some private code from `scan`
+
+/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html).
+///
+/// # Example
+///
+/// ~~~~
+/// use chrono::Weekday;
+///
+/// assert_eq!("Sunday".parse::<Weekday>(), Ok(Weekday::Sun));
+/// assert!("any day".parse::<Weekday>().is_err());
+/// ~~~~
+///
+/// The parsing is case-insensitive.
+///
+/// ~~~~
+/// # use chrono::Weekday;
+/// assert_eq!("mON".parse::<Weekday>(), Ok(Weekday::Mon));
+/// ~~~~
+///
+/// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted.
+///
+/// ~~~~
+/// # use chrono::Weekday;
+/// assert!("thurs".parse::<Weekday>().is_err());
+/// ~~~~
+impl FromStr for Weekday {
+ type Err = ParseWeekdayError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if let Ok(("", w)) = scan::short_or_long_weekday(s) {
+ Ok(w)
+ } else {
+ Err(ParseWeekdayError { _dummy: () })
+ }
+ }
+}
+
+/// Formats single formatting item
+#[cfg(feature = "unstable-locales")]
+pub fn format_item_localized<'a>(
+ w: &mut fmt::Formatter,
+ date: Option<&NaiveDate>,
+ time: Option<&NaiveTime>,
+ off: Option<&(String, FixedOffset)>,
+ item: &Item<'a>,
+ locale: Locale,
+) -> fmt::Result {
+ let mut result = String::new();
+ format_inner(&mut result, date, time, off, item, Some(locale))?;
+ w.pad(&result)
+}
+
+/// Tries to format given arguments with given formatting items.
+/// Internally used by `DelayedFormat`.
+#[cfg(feature = "unstable-locales")]
+pub fn format_localized<'a, I, B>(
+ w: &mut fmt::Formatter,
+ date: Option<&NaiveDate>,
+ time: Option<&NaiveTime>,
+ off: Option<&(String, FixedOffset)>,
+ items: I,
+ locale: Locale,
+) -> fmt::Result
+where
+ I: Iterator<Item = B> + Clone,
+ B: Borrow<Item<'a>>,
+{
+ let mut result = String::new();
+ for item in items {
+ format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?;
+ }
+ w.pad(&result)
+}
+
+/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html).
+///
+/// # Example
+///
+/// ~~~~
+/// use chrono::Month;
+///
+/// assert_eq!("January".parse::<Month>(), Ok(Month::January));
+/// assert!("any day".parse::<Month>().is_err());
+/// ~~~~
+///
+/// The parsing is case-insensitive.
+///
+/// ~~~~
+/// # use chrono::Month;
+/// assert_eq!("fEbruARy".parse::<Month>(), Ok(Month::February));
+/// ~~~~
+///
+/// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted.
+///
+/// ~~~~
+/// # use chrono::Month;
+/// assert!("septem".parse::<Month>().is_err());
+/// assert!("Augustin".parse::<Month>().is_err());
+/// ~~~~
+impl FromStr for Month {
+ type Err = ParseMonthError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ if let Ok(("", w)) = scan::short_or_long_month0(s) {
+ match w {
+ 0 => Ok(Month::January),
+ 1 => Ok(Month::February),
+ 2 => Ok(Month::March),
+ 3 => Ok(Month::April),
+ 4 => Ok(Month::May),
+ 5 => Ok(Month::June),
+ 6 => Ok(Month::July),
+ 7 => Ok(Month::August),
+ 8 => Ok(Month::September),
+ 9 => Ok(Month::October),
+ 10 => Ok(Month::November),
+ 11 => Ok(Month::December),
+ _ => Err(ParseMonthError { _dummy: () }),
+ }
+ } else {
+ Err(ParseMonthError { _dummy: () })
+ }
+ }
+}
diff --git a/vendor/chrono/src/format/parse.rs b/vendor/chrono/src/format/parse.rs
new file mode 100644
index 000000000..2fce8277b
--- /dev/null
+++ b/vendor/chrono/src/format/parse.rs
@@ -0,0 +1,934 @@
+// This is a part of Chrono.
+// Portions copyright (c) 2015, John Nagle.
+// See README.md and LICENSE.txt for details.
+
+//! Date and time parsing routines.
+
+#![allow(deprecated)]
+
+use core::borrow::Borrow;
+use core::str;
+use core::usize;
+
+use super::scan;
+use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed};
+use super::{ParseError, ParseErrorKind, ParseResult};
+use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT};
+use {DateTime, FixedOffset, Weekday};
+
+fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> {
+ p.set_weekday(match v {
+ 0 => Weekday::Sun,
+ 1 => Weekday::Mon,
+ 2 => Weekday::Tue,
+ 3 => Weekday::Wed,
+ 4 => Weekday::Thu,
+ 5 => Weekday::Fri,
+ 6 => Weekday::Sat,
+ _ => return Err(OUT_OF_RANGE),
+ })
+}
+
+fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<()> {
+ p.set_weekday(match v {
+ 1 => Weekday::Mon,
+ 2 => Weekday::Tue,
+ 3 => Weekday::Wed,
+ 4 => Weekday::Thu,
+ 5 => Weekday::Fri,
+ 6 => Weekday::Sat,
+ 7 => Weekday::Sun,
+ _ => return Err(OUT_OF_RANGE),
+ })
+}
+
+fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> {
+ macro_rules! try_consume {
+ ($e:expr) => {{
+ let (s_, v) = $e?;
+ s = s_;
+ v
+ }};
+ }
+
+ // an adapted RFC 2822 syntax from Section 3.3 and 4.3:
+ //
+ // date-time = [ day-of-week "," ] date 1*S time *S
+ // day-of-week = *S day-name *S
+ // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
+ // date = day month year
+ // day = *S 1*2DIGIT *S
+ // month = 1*S month-name 1*S
+ // month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
+ // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
+ // year = *S 2*DIGIT *S
+ // time = time-of-day 1*S zone
+ // time-of-day = hour ":" minute [ ":" second ]
+ // hour = *S 2DIGIT *S
+ // minute = *S 2DIGIT *S
+ // second = *S 2DIGIT *S
+ // zone = ( "+" / "-" ) 4DIGIT /
+ // "UT" / "GMT" / ; same as +0000
+ // "EST" / "CST" / "MST" / "PST" / ; same as -0500 to -0800
+ // "EDT" / "CDT" / "MDT" / "PDT" / ; same as -0400 to -0700
+ // 1*(%d65-90 / %d97-122) ; same as -0000
+ //
+ // some notes:
+ //
+ // - quoted characters can be in any mixture of lower and upper cases.
+ //
+ // - we do not recognize a folding white space (FWS) or comment (CFWS).
+ // for our purposes, instead, we accept any sequence of Unicode
+ // white space characters (denoted here to `S`). any actual RFC 2822
+ // parser is expected to parse FWS and/or CFWS themselves and replace
+ // it with a single SP (`%x20`); this is legitimate.
+ //
+ // - two-digit year < 50 should be interpreted by adding 2000.
+ // two-digit year >= 50 or three-digit year should be interpreted
+ // by adding 1900. note that four-or-more-digit years less than 1000
+ // are *never* affected by this rule.
+ //
+ // - mismatching day-of-week is always an error, which is consistent to
+ // Chrono's own rules.
+ //
+ // - zones can range from `-9959` to `+9959`, but `FixedOffset` does not
+ // support offsets larger than 24 hours. this is not *that* problematic
+ // since we do not directly go to a `DateTime` so one can recover
+ // the offset information from `Parsed` anyway.
+
+ s = s.trim_left();
+
+ if let Ok((s_, weekday)) = scan::short_weekday(s) {
+ if !s_.starts_with(',') {
+ return Err(INVALID);
+ }
+ s = &s_[1..];
+ parsed.set_weekday(weekday)?;
+ }
+
+ s = s.trim_left();
+ parsed.set_day(try_consume!(scan::number(s, 1, 2)))?;
+ s = scan::space(s)?; // mandatory
+ parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?;
+ s = scan::space(s)?; // mandatory
+
+ // distinguish two- and three-digit years from four-digit years
+ let prevlen = s.len();
+ let mut year = try_consume!(scan::number(s, 2, usize::MAX));
+ let yearlen = prevlen - s.len();
+ match (yearlen, year) {
+ (2, 0...49) => {
+ year += 2000;
+ } // 47 -> 2047, 05 -> 2005
+ (2, 50...99) => {
+ year += 1900;
+ } // 79 -> 1979
+ (3, _) => {
+ year += 1900;
+ } // 112 -> 2012, 009 -> 1909
+ (_, _) => {} // 1987 -> 1987, 0654 -> 0654
+ }
+ parsed.set_year(year)?;
+
+ s = scan::space(s)?; // mandatory
+ parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?;
+ s = scan::char(s.trim_left(), b':')?.trim_left(); // *S ":" *S
+ parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
+ if let Ok(s_) = scan::char(s.trim_left(), b':') {
+ // [ ":" *S 2DIGIT ]
+ parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?;
+ }
+
+ s = scan::space(s)?; // mandatory
+ if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) {
+ // only set the offset when it is definitely known (i.e. not `-0000`)
+ parsed.set_offset(i64::from(offset))?;
+ }
+
+ Ok((s, ()))
+}
+
+fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> {
+ macro_rules! try_consume {
+ ($e:expr) => {{
+ let (s_, v) = $e?;
+ s = s_;
+ v
+ }};
+ }
+
+ // an adapted RFC 3339 syntax from Section 5.6:
+ //
+ // date-fullyear = 4DIGIT
+ // date-month = 2DIGIT ; 01-12
+ // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
+ // time-hour = 2DIGIT ; 00-23
+ // time-minute = 2DIGIT ; 00-59
+ // time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
+ // time-secfrac = "." 1*DIGIT
+ // time-numoffset = ("+" / "-") time-hour ":" time-minute
+ // time-offset = "Z" / time-numoffset
+ // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
+ // full-date = date-fullyear "-" date-month "-" date-mday
+ // full-time = partial-time time-offset
+ // date-time = full-date "T" full-time
+ //
+ // some notes:
+ //
+ // - quoted characters can be in any mixture of lower and upper cases.
+ //
+ // - it may accept any number of fractional digits for seconds.
+ // for Chrono, this means that we should skip digits past first 9 digits.
+ //
+ // - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59.
+ // note that this restriction is unique to RFC 3339 and not ISO 8601.
+ // since this is not a typical Chrono behavior, we check it earlier.
+
+ parsed.set_year(try_consume!(scan::number(s, 4, 4)))?;
+ s = scan::char(s, b'-')?;
+ parsed.set_month(try_consume!(scan::number(s, 2, 2)))?;
+ s = scan::char(s, b'-')?;
+ parsed.set_day(try_consume!(scan::number(s, 2, 2)))?;
+
+ s = match s.as_bytes().first() {
+ Some(&b't') | Some(&b'T') => &s[1..],
+ Some(_) => return Err(INVALID),
+ None => return Err(TOO_SHORT),
+ };
+
+ parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?;
+ s = scan::char(s, b':')?;
+ parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
+ s = scan::char(s, b':')?;
+ parsed.set_second(try_consume!(scan::number(s, 2, 2)))?;
+ if s.starts_with('.') {
+ let nanosecond = try_consume!(scan::nanosecond(&s[1..]));
+ parsed.set_nanosecond(nanosecond)?;
+ }
+
+ let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':')));
+ if offset <= -86_400 || offset >= 86_400 {
+ return Err(OUT_OF_RANGE);
+ }
+ parsed.set_offset(i64::from(offset))?;
+
+ Ok((s, ()))
+}
+
+/// Tries to parse given string into `parsed` with given formatting items.
+/// Returns `Ok` when the entire string has been parsed (otherwise `parsed` should not be used).
+/// There should be no trailing string after parsing;
+/// use a stray [`Item::Space`](./enum.Item.html#variant.Space) to trim whitespaces.
+///
+/// This particular date and time parser is:
+///
+/// - Greedy. It will consume the longest possible prefix.
+/// For example, `April` is always consumed entirely when the long month name is requested;
+/// it equally accepts `Apr`, but prefers the longer prefix in this case.
+///
+/// - Padding-agnostic (for numeric items).
+/// The [`Pad`](./enum.Pad.html) field is completely ignored,
+/// so one can prepend any number of whitespace then any number of zeroes before numbers.
+///
+/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`.
+pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()>
+where
+ I: Iterator<Item = B>,
+ B: Borrow<Item<'a>>,
+{
+ parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e)
+}
+
+fn parse_internal<'a, 'b, I, B>(
+ parsed: &mut Parsed,
+ mut s: &'b str,
+ items: I,
+) -> Result<&'b str, (&'b str, ParseError)>
+where
+ I: Iterator<Item = B>,
+ B: Borrow<Item<'a>>,
+{
+ macro_rules! try_consume {
+ ($e:expr) => {{
+ match $e {
+ Ok((s_, v)) => {
+ s = s_;
+ v
+ }
+ Err(e) => return Err((s, e)),
+ }
+ }};
+ }
+
+ for item in items {
+ match *item.borrow() {
+ Item::Literal(prefix) => {
+ if s.len() < prefix.len() {
+ return Err((s, TOO_SHORT));
+ }
+ if !s.starts_with(prefix) {
+ return Err((s, INVALID));
+ }
+ s = &s[prefix.len()..];
+ }
+
+ #[cfg(any(feature = "alloc", feature = "std", test))]
+ Item::OwnedLiteral(ref prefix) => {
+ if s.len() < prefix.len() {
+ return Err((s, TOO_SHORT));
+ }
+ if !s.starts_with(&prefix[..]) {
+ return Err((s, INVALID));
+ }
+ s = &s[prefix.len()..];
+ }
+
+ Item::Space(_) => {
+ s = s.trim_left();
+ }
+
+ #[cfg(any(feature = "alloc", feature = "std", test))]
+ Item::OwnedSpace(_) => {
+ s = s.trim_left();
+ }
+
+ Item::Numeric(ref spec, ref _pad) => {
+ use super::Numeric::*;
+ type Setter = fn(&mut Parsed, i64) -> ParseResult<()>;
+
+ let (width, signed, set): (usize, bool, Setter) = match *spec {
+ Year => (4, true, Parsed::set_year),
+ YearDiv100 => (2, false, Parsed::set_year_div_100),
+ YearMod100 => (2, false, Parsed::set_year_mod_100),
+ IsoYear => (4, true, Parsed::set_isoyear),
+ IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
+ IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
+ Month => (2, false, Parsed::set_month),
+ Day => (2, false, Parsed::set_day),
+ WeekFromSun => (2, false, Parsed::set_week_from_sun),
+ WeekFromMon => (2, false, Parsed::set_week_from_mon),
+ IsoWeek => (2, false, Parsed::set_isoweek),
+ NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
+ WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
+ Ordinal => (3, false, Parsed::set_ordinal),
+ Hour => (2, false, Parsed::set_hour),
+ Hour12 => (2, false, Parsed::set_hour12),
+ Minute => (2, false, Parsed::set_minute),
+ Second => (2, false, Parsed::set_second),
+ Nanosecond => (9, false, Parsed::set_nanosecond),
+ Timestamp => (usize::MAX, false, Parsed::set_timestamp),
+
+ // for the future expansion
+ Internal(ref int) => match int._dummy {},
+ };
+
+ s = s.trim_left();
+ let v = if signed {
+ if s.starts_with('-') {
+ let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
+ 0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))?
+ } else if s.starts_with('+') {
+ try_consume!(scan::number(&s[1..], 1, usize::MAX))
+ } else {
+ // if there is no explicit sign, we respect the original `width`
+ try_consume!(scan::number(s, 1, width))
+ }
+ } else {
+ try_consume!(scan::number(s, 1, width))
+ };
+ set(parsed, v).map_err(|e| (s, e))?;
+ }
+
+ Item::Fixed(ref spec) => {
+ use super::Fixed::*;
+
+ match spec {
+ &ShortMonthName => {
+ let month0 = try_consume!(scan::short_month0(s));
+ parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
+ }
+
+ &LongMonthName => {
+ let month0 = try_consume!(scan::short_or_long_month0(s));
+ parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
+ }
+
+ &ShortWeekdayName => {
+ let weekday = try_consume!(scan::short_weekday(s));
+ parsed.set_weekday(weekday).map_err(|e| (s, e))?;
+ }
+
+ &LongWeekdayName => {
+ let weekday = try_consume!(scan::short_or_long_weekday(s));
+ parsed.set_weekday(weekday).map_err(|e| (s, e))?;
+ }
+
+ &LowerAmPm | &UpperAmPm => {
+ if s.len() < 2 {
+ return Err((s, TOO_SHORT));
+ }
+ let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) {
+ (b'a', b'm') => false,
+ (b'p', b'm') => true,
+ _ => return Err((s, INVALID)),
+ };
+ parsed.set_ampm(ampm).map_err(|e| (s, e))?;
+ s = &s[2..];
+ }
+
+ &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => {
+ if s.starts_with('.') {
+ let nano = try_consume!(scan::nanosecond(&s[1..]));
+ parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+ }
+ }
+
+ &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
+ if s.len() < 3 {
+ return Err((s, TOO_SHORT));
+ }
+ let nano = try_consume!(scan::nanosecond_fixed(s, 3));
+ parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+ }
+
+ &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
+ if s.len() < 6 {
+ return Err((s, TOO_SHORT));
+ }
+ let nano = try_consume!(scan::nanosecond_fixed(s, 6));
+ parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+ }
+
+ &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
+ if s.len() < 9 {
+ return Err((s, TOO_SHORT));
+ }
+ let nano = try_consume!(scan::nanosecond_fixed(s, 9));
+ parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
+ }
+
+ &TimezoneName => {
+ try_consume!(scan::timezone_name_skip(s));
+ }
+
+ &TimezoneOffsetColon | &TimezoneOffset => {
+ let offset = try_consume!(scan::timezone_offset(
+ s.trim_left(),
+ scan::colon_or_space
+ ));
+ parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
+ }
+
+ &TimezoneOffsetColonZ | &TimezoneOffsetZ => {
+ let offset = try_consume!(scan::timezone_offset_zulu(
+ s.trim_left(),
+ scan::colon_or_space
+ ));
+ parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
+ }
+ &Internal(InternalFixed {
+ val: InternalInternal::TimezoneOffsetPermissive,
+ }) => {
+ let offset = try_consume!(scan::timezone_offset_permissive(
+ s.trim_left(),
+ scan::colon_or_space
+ ));
+ parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
+ }
+
+ &RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
+ &RFC3339 => try_consume!(parse_rfc3339(parsed, s)),
+ }
+ }
+
+ Item::Error => {
+ return Err((s, BAD_FORMAT));
+ }
+ }
+ }
+
+ // if there are trailling chars, it is an error
+ if !s.is_empty() {
+ Err((s, TOO_LONG))
+ } else {
+ Ok(s)
+ }
+}
+
+impl str::FromStr for DateTime<FixedOffset> {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
+ const DATE_ITEMS: &'static [Item<'static>] = &[
+ Item::Numeric(Numeric::Year, Pad::Zero),
+ Item::Space(""),
+ Item::Literal("-"),
+ Item::Numeric(Numeric::Month, Pad::Zero),
+ Item::Space(""),
+ Item::Literal("-"),
+ Item::Numeric(Numeric::Day, Pad::Zero),
+ ];
+ const TIME_ITEMS: &'static [Item<'static>] = &[
+ Item::Numeric(Numeric::Hour, Pad::Zero),
+ Item::Space(""),
+ Item::Literal(":"),
+ Item::Numeric(Numeric::Minute, Pad::Zero),
+ Item::Space(""),
+ Item::Literal(":"),
+ Item::Numeric(Numeric::Second, Pad::Zero),
+ Item::Fixed(Fixed::Nanosecond),
+ Item::Space(""),
+ Item::Fixed(Fixed::TimezoneOffsetZ),
+ Item::Space(""),
+ ];
+
+ let mut parsed = Parsed::new();
+ match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) {
+ Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => {
+ if remainder.starts_with('T') || remainder.starts_with(' ') {
+ parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?;
+ } else {
+ Err(INVALID)?;
+ }
+ }
+ Err((_s, e)) => Err(e)?,
+ Ok(_) => Err(NOT_ENOUGH)?,
+ };
+ parsed.to_datetime()
+ }
+}
+
+#[cfg(test)]
+#[test]
+fn test_parse() {
+ use super::IMPOSSIBLE;
+ use super::*;
+
+ // workaround for Rust issue #22255
+ fn parse_all(s: &str, items: &[Item]) -> ParseResult<Parsed> {
+ let mut parsed = Parsed::new();
+ parse(&mut parsed, s, items.iter())?;
+ Ok(parsed)
+ }
+
+ macro_rules! check {
+ ($fmt:expr, $items:expr; $err:tt) => (
+ assert_eq!(parse_all($fmt, &$items), Err($err))
+ );
+ ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] {
+ let mut expected = Parsed::new();
+ $(expected.$k = Some($v);)*
+ assert_eq!(parse_all($fmt, &$items), Ok(expected))
+ });
+ }
+
+ // empty string
+ check!("", []; );
+ check!(" ", []; TOO_LONG);
+ check!("a", []; TOO_LONG);
+
+ // whitespaces
+ check!("", [sp!("")]; );
+ check!(" ", [sp!("")]; );
+ check!("\t", [sp!("")]; );
+ check!(" \n\r \n", [sp!("")]; );
+ check!("a", [sp!("")]; TOO_LONG);
+
+ // literal
+ check!("", [lit!("a")]; TOO_SHORT);
+ check!(" ", [lit!("a")]; INVALID);
+ check!("a", [lit!("a")]; );
+ check!("aa", [lit!("a")]; TOO_LONG);
+ check!("A", [lit!("a")]; INVALID);
+ check!("xy", [lit!("xy")]; );
+ check!("xy", [lit!("x"), lit!("y")]; );
+ check!("x y", [lit!("x"), lit!("y")]; INVALID);
+ check!("xy", [lit!("x"), sp!(""), lit!("y")]; );
+ check!("x y", [lit!("x"), sp!(""), lit!("y")]; );
+
+ // numeric
+ check!("1987", [num!(Year)]; year: 1987);
+ check!("1987 ", [num!(Year)]; TOO_LONG);
+ check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed
+ check!("x123", [num!(Year)]; INVALID);
+ check!("2015", [num!(Year)]; year: 2015);
+ check!("0000", [num!(Year)]; year: 0);
+ check!("9999", [num!(Year)]; year: 9999);
+ check!(" \t987", [num!(Year)]; year: 987);
+ check!("5", [num!(Year)]; year: 5);
+ check!("5\0", [num!(Year)]; TOO_LONG);
+ check!("\05", [num!(Year)]; INVALID);
+ check!("", [num!(Year)]; TOO_SHORT);
+ check!("12345", [num!(Year), lit!("5")]; year: 1234);
+ check!("12345", [nums!(Year), lit!("5")]; year: 1234);
+ check!("12345", [num0!(Year), lit!("5")]; year: 1234);
+ check!("12341234", [num!(Year), num!(Year)]; year: 1234);
+ check!("1234 1234", [num!(Year), num!(Year)]; year: 1234);
+ check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE);
+ check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
+ check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234);
+ check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
+ check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
+
+ // signed numeric
+ check!("-42", [num!(Year)]; year: -42);
+ check!("+42", [num!(Year)]; year: 42);
+ check!("-0042", [num!(Year)]; year: -42);
+ check!("+0042", [num!(Year)]; year: 42);
+ check!("-42195", [num!(Year)]; year: -42195);
+ check!("+42195", [num!(Year)]; year: 42195);
+ check!(" -42195", [num!(Year)]; year: -42195);
+ check!(" +42195", [num!(Year)]; year: 42195);
+ check!(" - 42", [num!(Year)]; INVALID);
+ check!(" + 42", [num!(Year)]; INVALID);
+ check!("-", [num!(Year)]; TOO_SHORT);
+ check!("+", [num!(Year)]; TOO_SHORT);
+
+ // unsigned numeric
+ check!("345", [num!(Ordinal)]; ordinal: 345);
+ check!("+345", [num!(Ordinal)]; INVALID);
+ check!("-345", [num!(Ordinal)]; INVALID);
+ check!(" 345", [num!(Ordinal)]; ordinal: 345);
+ check!(" +345", [num!(Ordinal)]; INVALID);
+ check!(" -345", [num!(Ordinal)]; INVALID);
+
+ // various numeric fields
+ check!("1234 5678",
+ [num!(Year), num!(IsoYear)];
+ year: 1234, isoyear: 5678);
+ check!("12 34 56 78",
+ [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)];
+ year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78);
+ check!("1 2 3 4 5 6",
+ [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek),
+ num!(NumDaysFromSun)];
+ month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat);
+ check!("7 89 01",
+ [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)];
+ weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1);
+ check!("23 45 6 78901234 567890123",
+ [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)];
+ hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234,
+ timestamp: 567_890_123);
+
+ // fixed: month and weekday names
+ check!("apr", [fix!(ShortMonthName)]; month: 4);
+ check!("Apr", [fix!(ShortMonthName)]; month: 4);
+ check!("APR", [fix!(ShortMonthName)]; month: 4);
+ check!("ApR", [fix!(ShortMonthName)]; month: 4);
+ check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed
+ check!("A", [fix!(ShortMonthName)]; TOO_SHORT);
+ check!("Sol", [fix!(ShortMonthName)]; INVALID);
+ check!("Apr", [fix!(LongMonthName)]; month: 4);
+ check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed
+ check!("April", [fix!(LongMonthName)]; month: 4);
+ check!("Aprill", [fix!(LongMonthName)]; TOO_LONG);
+ check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4);
+ check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4);
+ check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack
+ check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
+ check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
+ check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
+ check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
+ check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed
+ check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT);
+ check!("The", [fix!(ShortWeekdayName)]; INVALID);
+ check!("Nop", [fix!(ShortWeekdayName)]; INVALID);
+ check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu);
+ check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed
+ check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto
+ check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu);
+ check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG);
+ check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu);
+ check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu);
+ check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack
+
+ // fixed: am/pm
+ check!("am", [fix!(LowerAmPm)]; hour_div_12: 0);
+ check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1);
+ check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0);
+ check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1);
+ check!("am", [fix!(UpperAmPm)]; hour_div_12: 0);
+ check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1);
+ check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0);
+ check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1);
+ check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0);
+ check!(" Am", [fix!(LowerAmPm)]; INVALID);
+ check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed
+ check!("a", [fix!(LowerAmPm)]; TOO_SHORT);
+ check!("p", [fix!(LowerAmPm)]; TOO_SHORT);
+ check!("x", [fix!(LowerAmPm)]; TOO_SHORT);
+ check!("xx", [fix!(LowerAmPm)]; INVALID);
+ check!("", [fix!(LowerAmPm)]; TOO_SHORT);
+
+ // fixed: dot plus nanoseconds
+ check!("", [fix!(Nanosecond)]; ); // no field set, but not an error
+ check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4`
+ check!("4", [fix!(Nanosecond), num!(Second)]; second: 4);
+ check!(".0", [fix!(Nanosecond)]; nanosecond: 0);
+ check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000);
+ check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000);
+ check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000);
+ check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000);
+ check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803);
+ check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803);
+ check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3);
+ check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0);
+ check!(".", [fix!(Nanosecond)]; TOO_SHORT);
+ check!(".4x", [fix!(Nanosecond)]; TOO_LONG);
+ check!(". 4", [fix!(Nanosecond)]; INVALID);
+ check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming
+
+ // fixed: nanoseconds without the dot
+ check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
+ check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
+ check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
+ check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
+ check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000);
+ check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43);
+ check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG);
+ check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
+ check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID);
+ check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID);
+
+ check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
+ check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
+ check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
+ check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000);
+ check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000);
+ check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0);
+ check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
+ check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID);
+ check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID);
+
+ check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
+ check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
+ check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803);
+ check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3);
+ check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9
+ check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG);
+ check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0);
+ check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID);
+ check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID);
+ check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID);
+
+ // fixed: timezone offsets
+ check!("+00:00", [fix!(TimezoneOffset)]; offset: 0);
+ check!("-00:00", [fix!(TimezoneOffset)]; offset: 0);
+ check!("+00:01", [fix!(TimezoneOffset)]; offset: 60);
+ check!("-00:01", [fix!(TimezoneOffset)]; offset: -60);
+ check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60);
+ check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60);
+ check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60);
+ check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60);
+ check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60);
+ check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60);
+ check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60);
+ check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60);
+ check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60);
+ check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
+ check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
+ check!("#12:34", [fix!(TimezoneOffset)]; INVALID);
+ check!("12:34", [fix!(TimezoneOffset)]; INVALID);
+ check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG);
+ check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60);
+ check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60);
+ check!("", [fix!(TimezoneOffset)]; TOO_SHORT);
+ check!("+", [fix!(TimezoneOffset)]; TOO_SHORT);
+ check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT);
+ check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT);
+ check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT);
+ check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60);
+ check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG);
+ check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5);
+ check!("Z", [fix!(TimezoneOffset)]; INVALID);
+ check!("z", [fix!(TimezoneOffset)]; INVALID);
+ check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0);
+ check!("z", [fix!(TimezoneOffsetZ)]; offset: 0);
+ check!("Y", [fix!(TimezoneOffsetZ)]; INVALID);
+ check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0);
+ check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0);
+ check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60);
+ check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60);
+ check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0);
+ check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0);
+ check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60);
+ check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60);
+ check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5);
+
+ // some practical examples
+ check!("2015-02-04T14:37:05+09:00",
+ [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"),
+ num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)];
+ year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
+ minute: 37, second: 5, offset: 32400);
+ check!("20150204143705567",
+ [num!(Year), num!(Month), num!(Day),
+ num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)];
+ year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
+ minute: 37, second: 5, nanosecond: 567000000);
+ check!("Mon, 10 Jun 2013 09:32:37 GMT",
+ [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "),
+ fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"),
+ num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")];
+ year: 2013, month: 6, day: 10, weekday: Weekday::Mon,
+ hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37);
+ check!("Sun Aug 02 13:39:15 CEST 2020",
+ [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "),
+ num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"),
+ num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)];
+ year: 2020, month: 8, day: 2, weekday: Weekday::Sun,
+ hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15);
+ check!("20060102150405",
+ [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)];
+ year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5);
+ check!("3:14PM",
+ [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)];
+ hour_div_12: 1, hour_mod_12: 3, minute: 14);
+ check!("12345678901234.56789",
+ [num!(Timestamp), lit!("."), num!(Nanosecond)];
+ nanosecond: 56_789, timestamp: 12_345_678_901_234);
+ check!("12345678901234.56789",
+ [num!(Timestamp), fix!(Nanosecond)];
+ nanosecond: 567_890_000, timestamp: 12_345_678_901_234);
+}
+
+#[cfg(test)]
+#[test]
+fn test_rfc2822() {
+ use super::NOT_ENOUGH;
+ use super::*;
+ use offset::FixedOffset;
+ use DateTime;
+
+ // Test data - (input, Ok(expected result after parse and format) or Err(error code))
+ let testdates = [
+ ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case
+ ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace
+ ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero
+ ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week
+ ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month
+ ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second
+ ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")),
+ ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month
+ ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields
+ ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name
+ ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour
+ ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour
+ ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute
+ ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second
+ ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset
+ ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed)
+ ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone
+ ];
+
+ fn rfc2822_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
+ let mut parsed = Parsed::new();
+ parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?;
+ parsed.to_datetime()
+ }
+
+ fn fmt_rfc2822_datetime(dt: DateTime<FixedOffset>) -> String {
+ dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string()
+ }
+
+ // Test against test data above
+ for &(date, checkdate) in testdates.iter() {
+ let d = rfc2822_to_datetime(date); // parse a date
+ let dt = match d {
+ // did we get a value?
+ Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on
+ Err(e) => Err(e), // otherwise keep an error for the comparison
+ };
+ if dt != checkdate.map(|s| s.to_string()) {
+ // check for expected result
+ panic!(
+ "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
+ date, dt, checkdate
+ );
+ }
+ }
+}
+
+#[cfg(test)]
+#[test]
+fn parse_rfc850() {
+ use {TimeZone, Utc};
+
+ static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT";
+
+ let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT";
+ let dt = Utc.ymd(1994, 11, 6).and_hms(8, 49, 37);
+
+ // Check that the format is what we expect
+ assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str);
+
+ // Check that it parses correctly
+ assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT));
+
+ // Check that the rest of the weekdays parse correctly (this test originally failed because
+ // Sunday parsed incorrectly).
+ let testdates = [
+ (Utc.ymd(1994, 11, 7).and_hms(8, 49, 37), "Monday, 07-Nov-94 08:49:37 GMT"),
+ (Utc.ymd(1994, 11, 8).and_hms(8, 49, 37), "Tuesday, 08-Nov-94 08:49:37 GMT"),
+ (Utc.ymd(1994, 11, 9).and_hms(8, 49, 37), "Wednesday, 09-Nov-94 08:49:37 GMT"),
+ (Utc.ymd(1994, 11, 10).and_hms(8, 49, 37), "Thursday, 10-Nov-94 08:49:37 GMT"),
+ (Utc.ymd(1994, 11, 11).and_hms(8, 49, 37), "Friday, 11-Nov-94 08:49:37 GMT"),
+ (Utc.ymd(1994, 11, 12).and_hms(8, 49, 37), "Saturday, 12-Nov-94 08:49:37 GMT"),
+ ];
+
+ for val in &testdates {
+ assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT));
+ }
+}
+
+#[cfg(test)]
+#[test]
+fn test_rfc3339() {
+ use super::*;
+ use offset::FixedOffset;
+ use DateTime;
+
+ // Test data - (input, Ok(expected result after parse and format) or Err(error code))
+ let testdates = [
+ ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case
+ ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day
+ ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")),
+ ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")),
+ ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")),
+ ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")),
+ ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small
+ ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month
+ ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour
+ ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute
+ ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second
+ ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset
+ ];
+
+ fn rfc3339_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
+ let mut parsed = Parsed::new();
+ parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?;
+ parsed.to_datetime()
+ }
+
+ fn fmt_rfc3339_datetime(dt: DateTime<FixedOffset>) -> String {
+ dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string()
+ }
+
+ // Test against test data above
+ for &(date, checkdate) in testdates.iter() {
+ let d = rfc3339_to_datetime(date); // parse a date
+ let dt = match d {
+ // did we get a value?
+ Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on
+ Err(e) => Err(e), // otherwise keep an error for the comparison
+ };
+ if dt != checkdate.map(|s| s.to_string()) {
+ // check for expected result
+ panic!(
+ "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
+ date, dt, checkdate
+ );
+ }
+ }
+}
diff --git a/vendor/chrono/src/format/parsed.rs b/vendor/chrono/src/format/parsed.rs
new file mode 100644
index 000000000..b8ed2d90f
--- /dev/null
+++ b/vendor/chrono/src/format/parsed.rs
@@ -0,0 +1,1283 @@
+// This is a part of Chrono.
+// See README.md and LICENSE.txt for details.
+
+//! A collection of parsed date and time items.
+//! They can be constructed incrementally while being checked for consistency.
+
+use num_traits::ToPrimitive;
+use oldtime::Duration as OldDuration;
+
+use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE};
+use div::div_rem;
+use naive::{NaiveDate, NaiveDateTime, NaiveTime};
+use offset::{FixedOffset, LocalResult, Offset, TimeZone};
+use DateTime;
+use Weekday;
+use {Datelike, Timelike};
+
+/// Parsed parts of date and time. There are two classes of methods:
+///
+/// - `set_*` methods try to set given field(s) while checking for the consistency.
+/// It may or may not check for the range constraint immediately (for efficiency reasons).
+///
+/// - `to_*` methods try to make a concrete date and time value out of set fields.
+/// It fully checks any remaining out-of-range conditions and inconsistent/impossible fields.
+#[allow(missing_copy_implementations)]
+#[derive(Clone, PartialEq, Debug)]
+pub struct Parsed {
+ /// Year.
+ ///
+ /// This can be negative unlike [`year_div_100`](#structfield.year_div_100)
+ /// and [`year_mod_100`](#structfield.year_mod_100) fields.
+ pub year: Option<i32>,
+
+ /// Year divided by 100. Implies that the year is >= 1 BCE when set.
+ ///
+ /// Due to the common usage, if this field is missing but
+ /// [`year_mod_100`](#structfield.year_mod_100) is present,
+ /// it is inferred to 19 when `year_mod_100 >= 70` and 20 otherwise.
+ pub year_div_100: Option<i32>,
+
+ /// Year modulo 100. Implies that the year is >= 1 BCE when set.
+ pub year_mod_100: Option<i32>,
+
+ /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date).
+ ///
+ /// This can be negative unlike [`isoyear_div_100`](#structfield.isoyear_div_100) and
+ /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) fields.
+ pub isoyear: Option<i32>,
+
+ /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), divided by 100.
+ /// Implies that the year is >= 1 BCE when set.
+ ///
+ /// Due to the common usage, if this field is missing but
+ /// [`isoyear_mod_100`](#structfield.isoyear_mod_100) is present,
+ /// it is inferred to 19 when `isoyear_mod_100 >= 70` and 20 otherwise.
+ pub isoyear_div_100: Option<i32>,
+
+ /// Year in the [ISO week date](../naive/struct.NaiveDate.html#week-date), modulo 100.
+ /// Implies that the year is >= 1 BCE when set.
+ pub isoyear_mod_100: Option<i32>,
+
+ /// Month (1--12).
+ pub month: Option<u32>,
+
+ /// Week number, where the week 1 starts at the first Sunday of January
+ /// (0--53, 1--53 or 1--52 depending on the year).
+ pub week_from_sun: Option<u32>,
+
+ /// Week number, where the week 1 starts at the first Monday of January
+ /// (0--53, 1--53 or 1--52 depending on the year).
+ pub week_from_mon: Option<u32>,
+
+ /// [ISO week number](../naive/struct.NaiveDate.html#week-date)
+ /// (1--52 or 1--53 depending on the year).
+ pub isoweek: Option<u32>,
+
+ /// Day of the week.
+ pub weekday: Option<Weekday>,
+
+ /// Day of the year (1--365 or 1--366 depending on the year).
+ pub ordinal: Option<u32>,
+
+ /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month).
+ pub day: Option<u32>,
+
+ /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM.
+ pub hour_div_12: Option<u32>,
+
+ /// Hour number modulo 12 (0--11).
+ pub hour_mod_12: Option<u32>,
+
+ /// Minute number (0--59).
+ pub minute: Option<u32>,
+
+ /// Second number (0--60, accounting for leap seconds).
+ pub second: Option<u32>,
+
+ /// The number of nanoseconds since the whole second (0--999,999,999).
+ pub nanosecond: Option<u32>,
+
+ /// The number of non-leap seconds since the midnight UTC on January 1, 1970.
+ ///
+ /// This can be off by one if [`second`](#structfield.second) is 60 (a leap second).
+ pub timestamp: Option<i64>,
+
+ /// Offset from the local time to UTC, in seconds.
+ pub offset: Option<i32>,
+
+ /// A dummy field to make this type not fully destructible (required for API stability).
+ _dummy: (),
+}
+
+/// Checks if `old` is either empty or has the same value as `new` (i.e. "consistent"),
+/// and if it is empty, set `old` to `new` as well.
+#[inline]
+fn set_if_consistent<T: PartialEq>(old: &mut Option<T>, new: T) -> ParseResult<()> {
+ if let Some(ref old) = *old {
+ if *old == new {
+ Ok(())
+ } else {
+ Err(IMPOSSIBLE)
+ }
+ } else {
+ *old = Some(new);
+ Ok(())
+ }
+}
+
+impl Default for Parsed {
+ fn default() -> Parsed {
+ Parsed {
+ year: None,
+ year_div_100: None,
+ year_mod_100: None,
+ isoyear: None,
+ isoyear_div_100: None,
+ isoyear_mod_100: None,
+ month: None,
+ week_from_sun: None,
+ week_from_mon: None,
+ isoweek: None,
+ weekday: None,
+ ordinal: None,
+ day: None,
+ hour_div_12: None,
+ hour_mod_12: None,
+ minute: None,
+ second: None,
+ nanosecond: None,
+ timestamp: None,
+ offset: None,
+ _dummy: (),
+ }
+ }
+}
+
+impl Parsed {
+ /// Returns the initial value of parsed parts.
+ pub fn new() -> Parsed {
+ Parsed::default()
+ }
+
+ /// Tries to set the [`year`](#structfield.year) field from given value.
+ #[inline]
+ pub fn set_year(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.year, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value.
+ #[inline]
+ pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> {
+ if value < 0 {
+ return Err(OUT_OF_RANGE);
+ }
+ set_if_consistent(&mut self.year_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value.
+ #[inline]
+ pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> {
+ if value < 0 {
+ return Err(OUT_OF_RANGE);
+ }
+ set_if_consistent(&mut self.year_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`isoyear`](#structfield.isoyear) field from given value.
+ #[inline]
+ pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.isoyear, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value.
+ #[inline]
+ pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> {
+ if value < 0 {
+ return Err(OUT_OF_RANGE);
+ }
+ set_if_consistent(&mut self.isoyear_div_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value.
+ #[inline]
+ pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> {
+ if value < 0 {
+ return Err(OUT_OF_RANGE);
+ }
+ set_if_consistent(&mut self.isoyear_mod_100, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`month`](#structfield.month) field from given value.
+ #[inline]
+ pub fn set_month(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.month, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value.
+ #[inline]
+ pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.week_from_sun, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value.
+ #[inline]
+ pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.week_from_mon, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`isoweek`](#structfield.isoweek) field from given value.
+ #[inline]
+ pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.isoweek, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`weekday`](#structfield.weekday) field from given value.
+ #[inline]
+ pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> {
+ set_if_consistent(&mut self.weekday, value)
+ }
+
+ /// Tries to set the [`ordinal`](#structfield.ordinal) field from given value.
+ #[inline]
+ pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.ordinal, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`day`](#structfield.day) field from given value.
+ #[inline]
+ pub fn set_day(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.day, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value.
+ /// (`false` for AM, `true` for PM)
+ #[inline]
+ pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> {
+ set_if_consistent(&mut self.hour_div_12, if value { 1 } else { 0 })
+ }
+
+ /// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from
+ /// given hour number in 12-hour clocks.
+ #[inline]
+ pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> {
+ if value < 1 || value > 12 {
+ return Err(OUT_OF_RANGE);
+ }
+ set_if_consistent(&mut self.hour_mod_12, value as u32 % 12)
+ }
+
+ /// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and
+ /// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value.
+ #[inline]
+ pub fn set_hour(&mut self, value: i64) -> ParseResult<()> {
+ let v = value.to_u32().ok_or(OUT_OF_RANGE)?;
+ set_if_consistent(&mut self.hour_div_12, v / 12)?;
+ set_if_consistent(&mut self.hour_mod_12, v % 12)?;
+ Ok(())
+ }
+
+ /// Tries to set the [`minute`](#structfield.minute) field from given value.
+ #[inline]
+ pub fn set_minute(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.minute, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`second`](#structfield.second) field from given value.
+ #[inline]
+ pub fn set_second(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.second, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value.
+ #[inline]
+ pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.nanosecond, value.to_u32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value.
+ #[inline]
+ pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.timestamp, value)
+ }
+
+ /// Tries to set the [`offset`](#structfield.offset) field from given value.
+ #[inline]
+ pub fn set_offset(&mut self, value: i64) -> ParseResult<()> {
+ set_if_consistent(&mut self.offset, value.to_i32().ok_or(OUT_OF_RANGE)?)
+ }
+
+ /// Returns a parsed naive date out of given fields.
+ ///
+ /// This method is able to determine the date from given subset of fields:
+ ///
+ /// - Year, month, day.
+ /// - Year, day of the year (ordinal).
+ /// - Year, week number counted from Sunday or Monday, day of the week.
+ /// - ISO week date.
+ ///
+ /// Gregorian year and ISO week date year can have their century number (`*_div_100`) omitted,
+ /// the two-digit year is used to guess the century number then.
+ pub fn to_naive_date(&self) -> ParseResult<NaiveDate> {
+ fn resolve_year(
+ y: Option<i32>,
+ q: Option<i32>,
+ r: Option<i32>,
+ ) -> ParseResult<Option<i32>> {
+ match (y, q, r) {
+ // if there is no further information, simply return the given full year.
+ // this is a common case, so let's avoid division here.
+ (y, None, None) => Ok(y),
+
+ // if there is a full year *and* also quotient and/or modulo,
+ // check if present quotient and/or modulo is consistent to the full year.
+ // since the presence of those fields means a positive full year,
+ // we should filter a negative full year first.
+ (Some(y), q, r @ Some(0...99)) | (Some(y), q, r @ None) => {
+ if y < 0 {
+ return Err(OUT_OF_RANGE);
+ }
+ let (q_, r_) = div_rem(y, 100);
+ if q.unwrap_or(q_) == q_ && r.unwrap_or(r_) == r_ {
+ Ok(Some(y))
+ } else {
+ Err(IMPOSSIBLE)
+ }
+ }
+
+ // the full year is missing but we have quotient and modulo.
+ // reconstruct the full year. make sure that the result is always positive.
+ (None, Some(q), Some(r @ 0...99)) => {
+ if q < 0 {
+ return Err(OUT_OF_RANGE);
+ }
+ let y = q.checked_mul(100).and_then(|v| v.checked_add(r));
+ Ok(Some(y.ok_or(OUT_OF_RANGE)?))
+ }
+
+ // we only have modulo. try to interpret a modulo as a conventional two-digit year.
+ // note: we are affected by Rust issue #18060. avoid multiple range patterns.
+ (None, None, Some(r @ 0...99)) => Ok(Some(r + if r < 70 { 2000 } else { 1900 })),
+
+ // otherwise it is an out-of-bound or insufficient condition.
+ (None, Some(_), None) => Err(NOT_ENOUGH),
+ (_, _, Some(_)) => Err(OUT_OF_RANGE),
+ }
+ }
+
+ let given_year = resolve_year(self.year, self.year_div_100, self.year_mod_100)?;
+ let given_isoyear = resolve_year(self.isoyear, self.isoyear_div_100, self.isoyear_mod_100)?;
+
+ // verify the normal year-month-day date.
+ let verify_ymd = |date: NaiveDate| {
+ let year = date.year();
+ let (year_div_100, year_mod_100) = if year >= 0 {
+ let (q, r) = div_rem(year, 100);
+ (Some(q), Some(r))
+ } else {
+ (None, None) // they should be empty to be consistent
+ };
+ let month = date.month();
+ let day = date.day();
+ self.year.unwrap_or(year) == year
+ && self.year_div_100.or(year_div_100) == year_div_100
+ && self.year_mod_100.or(year_mod_100) == year_mod_100
+ && self.month.unwrap_or(month) == month
+ && self.day.unwrap_or(day) == day
+ };
+
+ // verify the ISO week date.
+ let verify_isoweekdate = |date: NaiveDate| {
+ let week = date.iso_week();
+ let isoyear = week.year();
+ let isoweek = week.week();
+ let weekday = date.weekday();
+ let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 {
+ let (q, r) = div_rem(isoyear, 100);
+ (Some(q), Some(r))
+ } else {
+ (None, None) // they should be empty to be consistent
+ };
+ self.isoyear.unwrap_or(isoyear) == isoyear
+ && self.isoyear_div_100.or(isoyear_div_100) == isoyear_div_100
+ && self.isoyear_mod_100.or(isoyear_mod_100) == isoyear_mod_100
+ && self.isoweek.unwrap_or(isoweek) == isoweek
+ && self.weekday.unwrap_or(weekday) == weekday
+ };
+
+ // verify the ordinal and other (non-ISO) week dates.
+ let verify_ordinal = |date: NaiveDate| {
+ let ordinal = date.ordinal();
+ let weekday = date.weekday();
+ let week_from_sun = (ordinal as i32 - weekday.num_days_from_sunday() as i32 + 7) / 7;
+ let week_from_mon = (ordinal as i32 - weekday.num_days_from_monday() as i32 + 7) / 7;
+ self.ordinal.unwrap_or(ordinal) == ordinal
+ && self.week_from_sun.map_or(week_from_sun, |v| v as i32) == week_from_sun
+ && self.week_from_mon.map_or(week_from_mon, |v| v as i32) == week_from_mon
+ };
+
+ // test several possibilities.
+ // tries to construct a full `NaiveDate` as much as possible, then verifies that
+ // it is consistent with other given fields.
+ let (verified, parsed_date) = match (given_year, given_isoyear, self) {
+ (Some(year), _, &Parsed { month: Some(month), day: Some(day), .. }) => {
+ // year, month, day
+ let date = NaiveDate::from_ymd_opt(year, month, day).ok_or(OUT_OF_RANGE)?;
+ (verify_isoweekdate(date) && verify_ordinal(date), date)
+ }
+
+ (Some(year), _, &Parsed { ordinal: Some(ordinal), .. }) => {
+ // year, day of the year
+ let date = NaiveDate::from_yo_opt(year, ordinal).ok_or(OUT_OF_RANGE)?;
+ (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date)
+ }
+
+ (
+ Some(year),
+ _,
+ &Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. },
+ ) => {
+ // year, week (starting at 1st Sunday), day of the week
+ let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?;
+ let firstweek = match newyear.weekday() {
+ Weekday::Sun => 0,
+ Weekday::Mon => 6,
+ Weekday::Tue => 5,
+ Weekday::Wed => 4,
+ Weekday::Thu => 3,
+ Weekday::Fri => 2,
+ Weekday::Sat => 1,
+ };
+
+ // `firstweek+1`-th day of January is the beginning of the week 1.
+ if week_from_sun > 53 {
+ return Err(OUT_OF_RANGE);
+ } // can it overflow?
+ let ndays = firstweek
+ + (week_from_sun as i32 - 1) * 7
+ + weekday.num_days_from_sunday() as i32;
+ let date = newyear
+ .checked_add_signed(OldDuration::days(i64::from(ndays)))
+ .ok_or(OUT_OF_RANGE)?;
+ if date.year() != year {
+ return Err(OUT_OF_RANGE);
+ } // early exit for correct error
+
+ (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date)
+ }
+
+ (
+ Some(year),
+ _,
+ &Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. },
+ ) => {
+ // year, week (starting at 1st Monday), day of the week
+ let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?;
+ let firstweek = match newyear.weekday() {
+ Weekday::Sun => 1,
+ Weekday::Mon => 0,
+ Weekday::Tue => 6,
+ Weekday::Wed => 5,
+ Weekday::Thu => 4,
+ Weekday::Fri => 3,
+ Weekday::Sat => 2,
+ };
+
+ // `firstweek+1`-th day of January is the beginning of the week 1.
+ if week_from_mon > 53 {
+ return Err(OUT_OF_RANGE);
+ } // can it overflow?
+ let ndays = firstweek
+ + (week_from_mon as i32 - 1) * 7
+ + weekday.num_days_from_monday() as i32;
+ let date = newyear
+ .checked_add_signed(OldDuration::days(i64::from(ndays)))
+ .ok_or(OUT_OF_RANGE)?;
+ if date.year() != year {
+ return Err(OUT_OF_RANGE);
+ } // early exit for correct error
+
+ (verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date)
+ }
+
+ (_, Some(isoyear), &Parsed { isoweek: Some(isoweek), weekday: Some(weekday), .. }) => {
+ // ISO year, week, day of the week
+ let date = NaiveDate::from_isoywd_opt(isoyear, isoweek, weekday);
+ let date = date.ok_or(OUT_OF_RANGE)?;
+ (verify_ymd(date) && verify_ordinal(date), date)
+ }
+
+ (_, _, _) => return Err(NOT_ENOUGH),
+ };
+
+ if verified {
+ Ok(parsed_date)
+ } else {
+ Err(IMPOSSIBLE)
+ }
+ }
+
+ /// Returns a parsed naive time out of given fields.
+ ///
+ /// This method is able to determine the time from given subset of fields:
+ ///
+ /// - Hour, minute. (second and nanosecond assumed to be 0)
+ /// - Hour, minute, second. (nanosecond assumed to be 0)
+ /// - Hour, minute, second, nanosecond.
+ ///
+ /// It is able to handle leap seconds when given second is 60.
+ pub fn to_naive_time(&self) -> ParseResult<NaiveTime> {
+ let hour_div_12 = match self.hour_div_12 {
+ Some(v @ 0...1) => v,
+ Some(_) => return Err(OUT_OF_RANGE),
+ None => return Err(NOT_ENOUGH),
+ };
+ let hour_mod_12 = match self.hour_mod_12 {
+ Some(v @ 0...11) => v,
+ Some(_) => return Err(OUT_OF_RANGE),
+ None => return Err(NOT_ENOUGH),
+ };
+ let hour = hour_div_12 * 12 + hour_mod_12;
+
+ let minute = match self.minute {
+ Some(v @ 0...59) => v,
+ Some(_) => return Err(OUT_OF_RANGE),
+ None => return Err(NOT_ENOUGH),
+ };
+
+ // we allow omitting seconds or nanoseconds, but they should be in the range.
+ let (second, mut nano) = match self.second.unwrap_or(0) {
+ v @ 0...59 => (v, 0),
+ 60 => (59, 1_000_000_000),
+ _ => return Err(OUT_OF_RANGE),
+ };
+ nano += match self.nanosecond {
+ Some(v @ 0...999_999_999) if self.second.is_some() => v,
+ Some(0...999_999_999) => return Err(NOT_ENOUGH), // second is missing
+ Some(_) => return Err(OUT_OF_RANGE),
+ None => 0,
+ };
+
+ NaiveTime::from_hms_nano_opt(hour, minute, second, nano).ok_or(OUT_OF_RANGE)
+ }
+
+ /// Returns a parsed naive date and time out of given fields,
+ /// except for the [`offset`](#structfield.offset) field (assumed to have a given value).
+ /// This is required for parsing a local time or other known-timezone inputs.
+ ///
+ /// This method is able to determine the combined date and time
+ /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field.
+ /// Either way those fields have to be consistent to each other.
+ pub fn to_naive_datetime_with_offset(&self, offset: i32) -> ParseResult<NaiveDateTime> {
+ let date = self.to_naive_date();
+ let time = self.to_naive_time();
+ if let (Ok(date), Ok(time)) = (date, time) {
+ let datetime = date.and_time(time);
+
+ // verify the timestamp field if any
+ // the following is safe, `timestamp` is very limited in range
+ let timestamp = datetime.timestamp() - i64::from(offset);
+ if let Some(given_timestamp) = self.timestamp {
+ // if `datetime` represents a leap second, it might be off by one second.
+ if given_timestamp != timestamp
+ && !(datetime.nanosecond() >= 1_000_000_000 && given_timestamp == timestamp + 1)
+ {
+ return Err(IMPOSSIBLE);
+ }
+ }
+
+ Ok(datetime)
+ } else if let Some(timestamp) = self.timestamp {
+ use super::ParseError as PE;
+ use super::ParseErrorKind::{Impossible, OutOfRange};
+
+ // if date and time is problematic already, there is no point proceeding.
+ // we at least try to give a correct error though.
+ match (date, time) {
+ (Err(PE(OutOfRange)), _) | (_, Err(PE(OutOfRange))) => return Err(OUT_OF_RANGE),
+ (Err(PE(Impossible)), _) | (_, Err(PE(Impossible))) => return Err(IMPOSSIBLE),
+ (_, _) => {} // one of them is insufficient
+ }
+
+ // reconstruct date and time fields from timestamp
+ let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?;
+ let datetime = NaiveDateTime::from_timestamp_opt(ts, 0);
+ let mut datetime = datetime.ok_or(OUT_OF_RANGE)?;
+
+ // fill year, ordinal, hour, minute and second fields from timestamp.
+ // if existing fields are consistent, this will allow the full date/time reconstruction.
+ let mut parsed = self.clone();
+ if parsed.second == Some(60) {
+ // `datetime.second()` cannot be 60, so this is the only case for a leap second.
+ match datetime.second() {
+ // it's okay, just do not try to overwrite the existing field.
+ 59 => {}
+ // `datetime` is known to be off by one second.
+ 0 => {
+ datetime -= OldDuration::seconds(1);
+ }
+ // otherwise it is impossible.
+ _ => return Err(IMPOSSIBLE),
+ }
+ // ...and we have the correct candidates for other fields.
+ } else {
+ parsed.set_second(i64::from(datetime.second()))?;
+ }
+ parsed.set_year(i64::from(datetime.year()))?;
+ parsed.set_ordinal(i64::from(datetime.ordinal()))?; // more efficient than ymd
+ parsed.set_hour(i64::from(datetime.hour()))?;
+ parsed.set_minute(i64::from(datetime.minute()))?;
+
+ // validate other fields (e.g. week) and return
+ let date = parsed.to_naive_date()?;
+ let time = parsed.to_naive_time()?;
+ Ok(date.and_time(time))
+ } else {
+ // reproduce the previous error(s)
+ date?;
+ time?;
+ unreachable!()
+ }
+ }
+
+ /// Returns a parsed fixed time zone offset out of given fields.
+ pub fn to_fixed_offset(&self) -> ParseResult<FixedOffset> {
+ self.offset.and_then(FixedOffset::east_opt).ok_or(OUT_OF_RANGE)
+ }
+
+ /// Returns a parsed timezone-aware date and time out of given fields.
+ ///
+ /// This method is able to determine the combined date and time
+ /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field,
+ /// plus a time zone offset.
+ /// Either way those fields have to be consistent to each other.
+ pub fn to_datetime(&self) -> ParseResult<DateTime<FixedOffset>> {
+ let offset = self.offset.ok_or(NOT_ENOUGH)?;
+ let datetime = self.to_naive_datetime_with_offset(offset)?;
+ let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?;
+ match offset.from_local_datetime(&datetime) {
+ LocalResult::None => Err(IMPOSSIBLE),
+ LocalResult::Single(t) => Ok(t),
+ LocalResult::Ambiguous(..) => Err(NOT_ENOUGH),
+ }
+ }
+
+ /// Returns a parsed timezone-aware date and time out of given fields,
+ /// with an additional `TimeZone` used to interpret and validate the local date.
+ ///
+ /// This method is able to determine the combined date and time
+ /// from date and time fields or a single [`timestamp`](#structfield.timestamp) field,
+ /// plus a time zone offset.
+ /// Either way those fields have to be consistent to each other.
+ /// If parsed fields include an UTC offset, it also has to be consistent to
+ /// [`offset`](#structfield.offset).
+ pub fn to_datetime_with_timezone<Tz: TimeZone>(&self, tz: &Tz) -> ParseResult<DateTime<Tz>> {
+ // if we have `timestamp` specified, guess an offset from that.
+ let mut guessed_offset = 0;
+ if let Some(timestamp) = self.timestamp {
+ // make a naive `DateTime` from given timestamp and (if any) nanosecond.
+ // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine.
+ let nanosecond = self.nanosecond.unwrap_or(0);
+ let dt = NaiveDateTime::from_timestamp_opt(timestamp, nanosecond);
+ let dt = dt.ok_or(OUT_OF_RANGE)?;
+ guessed_offset = tz.offset_from_utc_datetime(&dt).fix().local_minus_utc();
+ }
+
+ // checks if the given `DateTime` has a consistent `Offset` with given `self.offset`.
+ let check_offset = |dt: &DateTime<Tz>| {
+ if let Some(offset) = self.offset {
+ dt.offset().fix().local_minus_utc() == offset
+ } else {
+ true
+ }
+ };
+
+ // `guessed_offset` should be correct when `self.timestamp` is given.
+ // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case.
+ let datetime = self.to_naive_datetime_with_offset(guessed_offset)?;
+ match tz.from_local_datetime(&datetime) {
+ LocalResult::None => Err(IMPOSSIBLE),
+ LocalResult::Single(t) => {
+ if check_offset(&t) {
+ Ok(t)
+ } else {
+ Err(IMPOSSIBLE)
+ }
+ }
+ LocalResult::Ambiguous(min, max) => {
+ // try to disambiguate two possible local dates by offset.
+ match (check_offset(&min), check_offset(&max)) {
+ (false, false) => Err(IMPOSSIBLE),
+ (false, true) => Ok(max),
+ (true, false) => Ok(min),
+ (true, true) => Err(NOT_ENOUGH),
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE};
+ use super::Parsed;
+ use naive::{NaiveDate, NaiveTime, MAX_DATE, MIN_DATE};
+ use offset::{FixedOffset, TimeZone, Utc};
+ use Datelike;
+ use Weekday::*;
+
+ #[test]
+ fn test_parsed_set_fields() {
+ // year*, isoyear*
+ let mut p = Parsed::new();
+ assert_eq!(p.set_year(1987), Ok(()));
+ assert_eq!(p.set_year(1986), Err(IMPOSSIBLE));
+ assert_eq!(p.set_year(1988), Err(IMPOSSIBLE));
+ assert_eq!(p.set_year(1987), Ok(()));
+ assert_eq!(p.set_year_div_100(20), Ok(())); // independent to `year`
+ assert_eq!(p.set_year_div_100(21), Err(IMPOSSIBLE));
+ assert_eq!(p.set_year_div_100(19), Err(IMPOSSIBLE));
+ assert_eq!(p.set_year_mod_100(37), Ok(())); // ditto
+ assert_eq!(p.set_year_mod_100(38), Err(IMPOSSIBLE));
+ assert_eq!(p.set_year_mod_100(36), Err(IMPOSSIBLE));
+
+ let mut p = Parsed::new();
+ assert_eq!(p.set_year(0), Ok(()));
+ assert_eq!(p.set_year_div_100(0), Ok(()));
+ assert_eq!(p.set_year_mod_100(0), Ok(()));
+
+ let mut p = Parsed::new();
+ assert_eq!(p.set_year_div_100(-1), Err(OUT_OF_RANGE));
+ assert_eq!(p.set_year_mod_100(-1), Err(OUT_OF_RANGE));
+ assert_eq!(p.set_year(-1), Ok(()));
+ assert_eq!(p.set_year(-2), Err(IMPOSSIBLE));
+ assert_eq!(p.set_year(0), Err(IMPOSSIBLE));
+
+ let mut p = Parsed::new();
+ assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE));
+ assert_eq!(p.set_year_div_100(8), Ok(()));
+ assert_eq!(p.set_year_div_100(0x1_0000_0008), Err(OUT_OF_RANGE));
+
+ // month, week*, isoweek, ordinal, day, minute, second, nanosecond, offset
+ let mut p = Parsed::new();
+ assert_eq!(p.set_month(7), Ok(()));
+ assert_eq!(p.set_month(1), Err(IMPOSSIBLE));
+ assert_eq!(p.set_month(6), Err(IMPOSSIBLE));
+ assert_eq!(p.set_month(8), Err(IMPOSSIBLE));
+ assert_eq!(p.set_month(12), Err(IMPOSSIBLE));
+
+ let mut p = Parsed::new();
+ assert_eq!(p.set_month(8), Ok(()));
+ assert_eq!(p.set_month(0x1_0000_0008), Err(OUT_OF_RANGE));
+
+ // hour
+ let mut p = Parsed::new();
+ assert_eq!(p.set_hour(12), Ok(()));
+ assert_eq!(p.set_hour(11), Err(IMPOSSIBLE));
+ assert_eq!(p.set_hour(13), Err(IMPOSSIBLE));
+ assert_eq!(p.set_hour(12), Ok(()));
+ assert_eq!(p.set_ampm(false), Err(IMPOSSIBLE));
+ assert_eq!(p.set_ampm(true), Ok(()));
+ assert_eq!(p.set_hour12(12), Ok(()));
+ assert_eq!(p.set_hour12(0), Err(OUT_OF_RANGE)); // requires canonical representation
+ assert_eq!(p.set_hour12(1), Err(IMPOSSIBLE));
+ assert_eq!(p.set_hour12(11), Err(IMPOSSIBLE));
+
+ let mut p = Parsed::new();
+ assert_eq!(p.set_ampm(true), Ok(()));
+ assert_eq!(p.set_hour12(7), Ok(()));
+ assert_eq!(p.set_hour(7), Err(IMPOSSIBLE));
+ assert_eq!(p.set_hour(18), Err(IMPOSSIBLE));
+ assert_eq!(p.set_hour(19), Ok(()));
+
+ // timestamp
+ let mut p = Parsed::new();
+ assert_eq!(p.set_timestamp(1_234_567_890), Ok(()));
+ assert_eq!(p.set_timestamp(1_234_567_889), Err(IMPOSSIBLE));
+ assert_eq!(p.set_timestamp(1_234_567_891), Err(IMPOSSIBLE));
+ }
+
+ #[test]
+ fn test_parsed_to_naive_date() {
+ macro_rules! parse {
+ ($($k:ident: $v:expr),*) => (
+ Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_date()
+ )
+ }
+
+ let ymd = |y, m, d| Ok(NaiveDate::from_ymd(y, m, d));
+
+ // ymd: omission of fields
+ assert_eq!(parse!(), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 1984), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 1984, month: 1), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 1984, month: 1, day: 2), ymd(1984, 1, 2));
+ assert_eq!(parse!(year: 1984, day: 2), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year_div_100: 19), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year_div_100: 19, year_mod_100: 84), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 1, day: 2), ymd(1984, 1, 2));
+ assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, day: 2), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year_div_100: 19, month: 1, day: 2), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year_mod_100: 70, month: 1, day: 2), ymd(1970, 1, 2));
+ assert_eq!(parse!(year_mod_100: 69, month: 1, day: 2), ymd(2069, 1, 2));
+
+ // ymd: out-of-range conditions
+ assert_eq!(parse!(year_div_100: 19, year_mod_100: 84, month: 2, day: 29), ymd(1984, 2, 29));
+ assert_eq!(
+ parse!(year_div_100: 19, year_mod_100: 83, month: 2, day: 29),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(year_div_100: 19, year_mod_100: 83, month: 13, day: 1),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 31),
+ ymd(1983, 12, 31)
+ );
+ assert_eq!(
+ parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 32),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(year_div_100: 19, year_mod_100: 83, month: 12, day: 0),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(year_div_100: 19, year_mod_100: 100, month: 1, day: 1),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(parse!(year_div_100: 19, year_mod_100: -1, month: 1, day: 1), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year_div_100: 0, year_mod_100: 0, month: 1, day: 1), ymd(0, 1, 1));
+ assert_eq!(parse!(year_div_100: -1, year_mod_100: 42, month: 1, day: 1), Err(OUT_OF_RANGE));
+ let max_year = MAX_DATE.year();
+ assert_eq!(
+ parse!(year_div_100: max_year / 100,
+ year_mod_100: max_year % 100, month: 1, day: 1),
+ ymd(max_year, 1, 1)
+ );
+ assert_eq!(
+ parse!(year_div_100: (max_year + 1) / 100,
+ year_mod_100: (max_year + 1) % 100, month: 1, day: 1),
+ Err(OUT_OF_RANGE)
+ );
+
+ // ymd: conflicting inputs
+ assert_eq!(parse!(year: 1984, year_div_100: 19, month: 1, day: 1), ymd(1984, 1, 1));
+ assert_eq!(parse!(year: 1984, year_div_100: 20, month: 1, day: 1), Err(IMPOSSIBLE));
+ assert_eq!(parse!(year: 1984, year_mod_100: 84, month: 1, day: 1), ymd(1984, 1, 1));
+ assert_eq!(parse!(year: 1984, year_mod_100: 83, month: 1, day: 1), Err(IMPOSSIBLE));
+ assert_eq!(
+ parse!(year: 1984, year_div_100: 19, year_mod_100: 84, month: 1, day: 1),
+ ymd(1984, 1, 1)
+ );
+ assert_eq!(
+ parse!(year: 1984, year_div_100: 18, year_mod_100: 94, month: 1, day: 1),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(year: 1984, year_div_100: 18, year_mod_100: 184, month: 1, day: 1),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(year: -1, year_div_100: 0, year_mod_100: -1, month: 1, day: 1),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(year: -1, year_div_100: -1, year_mod_100: 99, month: 1, day: 1),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(OUT_OF_RANGE));
+
+ // weekdates
+ assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1));
+ assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1));
+ assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2));
+ assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sun), ymd(2000, 1, 2));
+ assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Mon), ymd(2000, 1, 3));
+ assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Mon), ymd(2000, 1, 3));
+ assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sat), ymd(2000, 1, 8));
+ assert_eq!(parse!(year: 2000, week_from_sun: 1, weekday: Sat), ymd(2000, 1, 8));
+ assert_eq!(parse!(year: 2000, week_from_mon: 1, weekday: Sun), ymd(2000, 1, 9));
+ assert_eq!(parse!(year: 2000, week_from_sun: 2, weekday: Sun), ymd(2000, 1, 9));
+ assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10));
+ assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30));
+ assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31));
+ assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1));
+
+ // weekdates: conflicting inputs
+ assert_eq!(
+ parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sat),
+ ymd(2000, 1, 8)
+ );
+ assert_eq!(
+ parse!(year: 2000, week_from_mon: 1, week_from_sun: 2, weekday: Sun),
+ ymd(2000, 1, 9)
+ );
+ assert_eq!(
+ parse!(year: 2000, week_from_mon: 1, week_from_sun: 1, weekday: Sun),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(year: 2000, week_from_mon: 2, week_from_sun: 2, weekday: Sun),
+ Err(IMPOSSIBLE)
+ );
+
+ // ISO weekdates
+ assert_eq!(parse!(isoyear: 2004, isoweek: 53), Err(NOT_ENOUGH));
+ assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Fri), ymd(2004, 12, 31));
+ assert_eq!(parse!(isoyear: 2004, isoweek: 53, weekday: Sat), ymd(2005, 1, 1));
+ assert_eq!(parse!(isoyear: 2004, isoweek: 0xffffffff, weekday: Sat), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(isoyear: 2005, isoweek: 0, weekday: Thu), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(isoyear: 2005, isoweek: 5, weekday: Thu), ymd(2005, 2, 3));
+ assert_eq!(parse!(isoyear: 2005, weekday: Thu), Err(NOT_ENOUGH));
+
+ // year and ordinal
+ assert_eq!(parse!(ordinal: 123), Err(NOT_ENOUGH));
+ assert_eq!(parse!(year: 2000, ordinal: 0), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2000, ordinal: 1), ymd(2000, 1, 1));
+ assert_eq!(parse!(year: 2000, ordinal: 60), ymd(2000, 2, 29));
+ assert_eq!(parse!(year: 2000, ordinal: 61), ymd(2000, 3, 1));
+ assert_eq!(parse!(year: 2000, ordinal: 366), ymd(2000, 12, 31));
+ assert_eq!(parse!(year: 2000, ordinal: 367), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2000, ordinal: 0xffffffff), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2100, ordinal: 0), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2100, ordinal: 1), ymd(2100, 1, 1));
+ assert_eq!(parse!(year: 2100, ordinal: 59), ymd(2100, 2, 28));
+ assert_eq!(parse!(year: 2100, ordinal: 60), ymd(2100, 3, 1));
+ assert_eq!(parse!(year: 2100, ordinal: 365), ymd(2100, 12, 31));
+ assert_eq!(parse!(year: 2100, ordinal: 366), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(year: 2100, ordinal: 0xffffffff), Err(OUT_OF_RANGE));
+
+ // more complex cases
+ assert_eq!(
+ parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2015, isoweek: 1,
+ week_from_sun: 52, week_from_mon: 52, weekday: Wed),
+ ymd(2014, 12, 31)
+ );
+ assert_eq!(
+ parse!(year: 2014, month: 12, ordinal: 365, isoyear: 2015, isoweek: 1,
+ week_from_sun: 52, week_from_mon: 52),
+ ymd(2014, 12, 31)
+ );
+ assert_eq!(
+ parse!(year: 2014, month: 12, day: 31, ordinal: 365, isoyear: 2014, isoweek: 53,
+ week_from_sun: 52, week_from_mon: 52, weekday: Wed),
+ Err(IMPOSSIBLE)
+ ); // no ISO week date 2014-W53-3
+ assert_eq!(
+ parse!(year: 2012, isoyear: 2015, isoweek: 1,
+ week_from_sun: 52, week_from_mon: 52),
+ Err(NOT_ENOUGH)
+ ); // ambiguous (2014-12-29, 2014-12-30, 2014-12-31)
+ assert_eq!(parse!(year_div_100: 20, isoyear_mod_100: 15, ordinal: 366), Err(NOT_ENOUGH));
+ // technically unique (2014-12-31) but Chrono gives up
+ }
+
+ #[test]
+ fn test_parsed_to_naive_time() {
+ macro_rules! parse {
+ ($($k:ident: $v:expr),*) => (
+ Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_time()
+ )
+ }
+
+ let hms = |h, m, s| Ok(NaiveTime::from_hms(h, m, s));
+ let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano(h, m, s, n));
+
+ // omission of fields
+ assert_eq!(parse!(), Err(NOT_ENOUGH));
+ assert_eq!(parse!(hour_div_12: 0), Err(NOT_ENOUGH));
+ assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1), Err(NOT_ENOUGH));
+ assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23), hms(1, 23, 0));
+ assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45), hms(1, 23, 45));
+ assert_eq!(
+ parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 45,
+ nanosecond: 678_901_234),
+ hmsn(1, 23, 45, 678_901_234)
+ );
+ assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6), hms(23, 45, 6));
+ assert_eq!(parse!(hour_mod_12: 1, minute: 23), Err(NOT_ENOUGH));
+ assert_eq!(
+ parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, nanosecond: 456_789_012),
+ Err(NOT_ENOUGH)
+ );
+
+ // out-of-range conditions
+ assert_eq!(parse!(hour_div_12: 2, hour_mod_12: 0, minute: 0), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(hour_div_12: 1, hour_mod_12: 12, minute: 0), Err(OUT_OF_RANGE));
+ assert_eq!(parse!(hour_div_12: 0, hour_mod_12: 1, minute: 60), Err(OUT_OF_RANGE));
+ assert_eq!(
+ parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 61),
+ Err(OUT_OF_RANGE)
+ );
+ assert_eq!(
+ parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 34,
+ nanosecond: 1_000_000_000),
+ Err(OUT_OF_RANGE)
+ );
+
+ // leap seconds
+ assert_eq!(
+ parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60),
+ hmsn(1, 23, 59, 1_000_000_000)
+ );
+ assert_eq!(
+ parse!(hour_div_12: 0, hour_mod_12: 1, minute: 23, second: 60,
+ nanosecond: 999_999_999),
+ hmsn(1, 23, 59, 1_999_999_999)
+ );
+ }
+
+ #[test]
+ fn test_parsed_to_naive_datetime_with_offset() {
+ macro_rules! parse {
+ (offset = $offset:expr; $($k:ident: $v:expr),*) => (
+ Parsed { $($k: Some($v),)* ..Parsed::new() }.to_naive_datetime_with_offset($offset)
+ );
+ ($($k:ident: $v:expr),*) => (parse!(offset = 0; $($k: $v),*))
+ }
+
+ let ymdhms = |y, m, d, h, n, s| Ok(NaiveDate::from_ymd(y, m, d).and_hms(h, n, s));
+ let ymdhmsn =
+ |y, m, d, h, n, s, nano| Ok(NaiveDate::from_ymd(y, m, d).and_hms_nano(h, n, s, nano));
+
+ // omission of fields
+ assert_eq!(parse!(), Err(NOT_ENOUGH));
+ assert_eq!(
+ parse!(year: 2015, month: 1, day: 30,
+ hour_div_12: 1, hour_mod_12: 2, minute: 38),
+ ymdhms(2015, 1, 30, 14, 38, 0)
+ );
+ assert_eq!(
+ parse!(year: 1997, month: 1, day: 30,
+ hour_div_12: 1, hour_mod_12: 2, minute: 38, second: 5),
+ ymdhms(1997, 1, 30, 14, 38, 5)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 34, hour_div_12: 0, hour_mod_12: 5,
+ minute: 6, second: 7, nanosecond: 890_123_456),
+ ymdhmsn(2012, 2, 3, 5, 6, 7, 890_123_456)
+ );
+ assert_eq!(parse!(timestamp: 0), ymdhms(1970, 1, 1, 0, 0, 0));
+ assert_eq!(parse!(timestamp: 1, nanosecond: 0), ymdhms(1970, 1, 1, 0, 0, 1));
+ assert_eq!(parse!(timestamp: 1, nanosecond: 1), ymdhmsn(1970, 1, 1, 0, 0, 1, 1));
+ assert_eq!(parse!(timestamp: 1_420_000_000), ymdhms(2014, 12, 31, 4, 26, 40));
+ assert_eq!(parse!(timestamp: -0x1_0000_0000), ymdhms(1833, 11, 24, 17, 31, 44));
+
+ // full fields
+ assert_eq!(
+ parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31,
+ ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15,
+ isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed,
+ hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40,
+ nanosecond: 12_345_678, timestamp: 1_420_000_000),
+ ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678)
+ );
+ assert_eq!(
+ parse!(year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31,
+ ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15,
+ isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed,
+ hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40,
+ nanosecond: 12_345_678, timestamp: 1_419_999_999),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(offset = 32400;
+ year: 2014, year_div_100: 20, year_mod_100: 14, month: 12, day: 31,
+ ordinal: 365, isoyear: 2015, isoyear_div_100: 20, isoyear_mod_100: 15,
+ isoweek: 1, week_from_sun: 52, week_from_mon: 52, weekday: Wed,
+ hour_div_12: 0, hour_mod_12: 4, minute: 26, second: 40,
+ nanosecond: 12_345_678, timestamp: 1_419_967_600),
+ ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678)
+ );
+
+ // more timestamps
+ let max_days_from_year_1970 =
+ MAX_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1));
+ let year_0_from_year_1970 =
+ NaiveDate::from_ymd(0, 1, 1).signed_duration_since(NaiveDate::from_ymd(1970, 1, 1));
+ let min_days_from_year_1970 =
+ MIN_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1));
+ assert_eq!(
+ parse!(timestamp: min_days_from_year_1970.num_seconds()),
+ ymdhms(MIN_DATE.year(), 1, 1, 0, 0, 0)
+ );
+ assert_eq!(
+ parse!(timestamp: year_0_from_year_1970.num_seconds()),
+ ymdhms(0, 1, 1, 0, 0, 0)
+ );
+ assert_eq!(
+ parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399),
+ ymdhms(MAX_DATE.year(), 12, 31, 23, 59, 59)
+ );
+
+ // leap seconds #1: partial fields
+ assert_eq!(parse!(second: 59, timestamp: 1_341_100_798), Err(IMPOSSIBLE));
+ assert_eq!(parse!(second: 59, timestamp: 1_341_100_799), ymdhms(2012, 6, 30, 23, 59, 59));
+ assert_eq!(parse!(second: 59, timestamp: 1_341_100_800), Err(IMPOSSIBLE));
+ assert_eq!(
+ parse!(second: 60, timestamp: 1_341_100_799),
+ ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000)
+ );
+ assert_eq!(
+ parse!(second: 60, timestamp: 1_341_100_800),
+ ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000)
+ );
+ assert_eq!(parse!(second: 0, timestamp: 1_341_100_800), ymdhms(2012, 7, 1, 0, 0, 0));
+ assert_eq!(parse!(second: 1, timestamp: 1_341_100_800), Err(IMPOSSIBLE));
+ assert_eq!(parse!(second: 60, timestamp: 1_341_100_801), Err(IMPOSSIBLE));
+
+ // leap seconds #2: full fields
+ // we need to have separate tests for them since it uses another control flow.
+ assert_eq!(
+ parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11,
+ minute: 59, second: 59, timestamp: 1_341_100_798),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11,
+ minute: 59, second: 59, timestamp: 1_341_100_799),
+ ymdhms(2012, 6, 30, 23, 59, 59)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11,
+ minute: 59, second: 59, timestamp: 1_341_100_800),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11,
+ minute: 59, second: 60, timestamp: 1_341_100_799),
+ ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11,
+ minute: 59, second: 60, timestamp: 1_341_100_800),
+ ymdhmsn(2012, 6, 30, 23, 59, 59, 1_000_000_000)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0,
+ minute: 0, second: 0, timestamp: 1_341_100_800),
+ ymdhms(2012, 7, 1, 0, 0, 0)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 183, hour_div_12: 0, hour_mod_12: 0,
+ minute: 0, second: 1, timestamp: 1_341_100_800),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(year: 2012, ordinal: 182, hour_div_12: 1, hour_mod_12: 11,
+ minute: 59, second: 60, timestamp: 1_341_100_801),
+ Err(IMPOSSIBLE)
+ );
+
+ // error codes
+ assert_eq!(
+ parse!(year: 2015, month: 1, day: 20, weekday: Tue,
+ hour_div_12: 2, hour_mod_12: 1, minute: 35, second: 20),
+ Err(OUT_OF_RANGE)
+ ); // `hour_div_12` is out of range
+ }
+
+ #[test]
+ fn test_parsed_to_datetime() {
+ macro_rules! parse {
+ ($($k:ident: $v:expr),*) => (
+ Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime()
+ )
+ }
+
+ let ymdhmsn = |y, m, d, h, n, s, nano, off| {
+ Ok(FixedOffset::east(off).ymd(y, m, d).and_hms_nano(h, n, s, nano))
+ };
+
+ assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH));
+ assert_eq!(
+ parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4,
+ minute: 26, second: 40, nanosecond: 12_345_678),
+ Err(NOT_ENOUGH)
+ );
+ assert_eq!(
+ parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 0),
+ ymdhmsn(2014, 12, 31, 4, 26, 40, 12_345_678, 0)
+ );
+ assert_eq!(
+ parse!(year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400),
+ ymdhmsn(2014, 12, 31, 13, 26, 40, 12_345_678, 32400)
+ );
+ assert_eq!(
+ parse!(year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 1,
+ minute: 42, second: 4, nanosecond: 12_345_678, offset: -9876),
+ ymdhmsn(2014, 12, 31, 1, 42, 4, 12_345_678, -9876)
+ );
+ assert_eq!(
+ parse!(year: 2015, ordinal: 1, hour_div_12: 0, hour_mod_12: 4,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 86_400),
+ Err(OUT_OF_RANGE)
+ ); // `FixedOffset` does not support such huge offset
+ }
+
+ #[test]
+ fn test_parsed_to_datetime_with_timezone() {
+ macro_rules! parse {
+ ($tz:expr; $($k:ident: $v:expr),*) => (
+ Parsed { $($k: Some($v),)* ..Parsed::new() }.to_datetime_with_timezone(&$tz)
+ )
+ }
+
+ // single result from ymdhms
+ assert_eq!(
+ parse!(Utc;
+ year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 0),
+ Ok(Utc.ymd(2014, 12, 31).and_hms_nano(4, 26, 40, 12_345_678))
+ );
+ assert_eq!(
+ parse!(Utc;
+ year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(FixedOffset::east(32400);
+ year: 2014, ordinal: 365, hour_div_12: 0, hour_mod_12: 4,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 0),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(FixedOffset::east(32400);
+ year: 2014, ordinal: 365, hour_div_12: 1, hour_mod_12: 1,
+ minute: 26, second: 40, nanosecond: 12_345_678, offset: 32400),
+ Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms_nano(13, 26, 40, 12_345_678))
+ );
+
+ // single result from timestamp
+ assert_eq!(
+ parse!(Utc; timestamp: 1_420_000_000, offset: 0),
+ Ok(Utc.ymd(2014, 12, 31).and_hms(4, 26, 40))
+ );
+ assert_eq!(parse!(Utc; timestamp: 1_420_000_000, offset: 32400), Err(IMPOSSIBLE));
+ assert_eq!(
+ parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 0),
+ Err(IMPOSSIBLE)
+ );
+ assert_eq!(
+ parse!(FixedOffset::east(32400); timestamp: 1_420_000_000, offset: 32400),
+ Ok(FixedOffset::east(32400).ymd(2014, 12, 31).and_hms(13, 26, 40))
+ );
+
+ // TODO test with a variable time zone (for None and Ambiguous cases)
+ }
+}
diff --git a/vendor/chrono/src/format/scan.rs b/vendor/chrono/src/format/scan.rs
new file mode 100644
index 000000000..0efb1ee3d
--- /dev/null
+++ b/vendor/chrono/src/format/scan.rs
@@ -0,0 +1,350 @@
+// This is a part of Chrono.
+// See README.md and LICENSE.txt for details.
+
+/*!
+ * Various scanning routines for the parser.
+ */
+
+#![allow(deprecated)]
+
+use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT};
+use Weekday;
+
+/// Returns true when two slices are equal case-insensitively (in ASCII).
+/// Assumes that the `pattern` is already converted to lower case.
+fn equals(s: &str, pattern: &str) -> bool {
+ let mut xs = s.as_bytes().iter().map(|&c| match c {
+ b'A'...b'Z' => c + 32,
+ _ => c,
+ });
+ let mut ys = pattern.as_bytes().iter().cloned();
+ loop {
+ match (xs.next(), ys.next()) {
+ (None, None) => return true,
+ (None, _) | (_, None) => return false,
+ (Some(x), Some(y)) if x != y => return false,
+ _ => (),
+ }
+ }
+}
+
+/// Tries to parse the non-negative number from `min` to `max` digits.
+///
+/// The absence of digits at all is an unconditional error.
+/// More than `max` digits are consumed up to the first `max` digits.
+/// Any number that does not fit in `i64` is an error.
+#[inline]
+pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
+ assert!(min <= max);
+
+ // We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
+ // the first non-numeric byte, which may be another ascii character or beginning of multi-byte
+ // UTF-8 character.
+ let bytes = s.as_bytes();
+ if bytes.len() < min {
+ return Err(TOO_SHORT);
+ }
+
+ let mut n = 0i64;
+ for (i, c) in bytes.iter().take(max).cloned().enumerate() {
+ // cloned() = copied()
+ if c < b'0' || b'9' < c {
+ if i < min {
+ return Err(INVALID);
+ } else {
+ return Ok((&s[i..], n));
+ }
+ }
+
+ n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) {
+ Some(n) => n,
+ None => return Err(OUT_OF_RANGE),
+ };
+ }
+
+ Ok((&s[::core::cmp::min(max, bytes.len())..], n))
+}
+
+/// Tries to consume at least one digits as a fractional second.
+/// Returns the number of whole nanoseconds (0--999,999,999).
+pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
+ // record the number of digits consumed for later scaling.
+ let origlen = s.len();
+ let (s, v) = number(s, 1, 9)?;
+ let consumed = origlen - s.len();
+
+ // scale the number accordingly.
+ static SCALE: [i64; 10] =
+ [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
+ let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?;
+
+ // if there are more than 9 digits, skip next digits.
+ let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
+
+ Ok((s, v))
+}
+
+/// Tries to consume a fixed number of digits as a fractional second.
+/// Returns the number of whole nanoseconds (0--999,999,999).
+pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
+ // record the number of digits consumed for later scaling.
+ let (s, v) = number(s, digits, digits)?;
+
+ // scale the number accordingly.
+ static SCALE: [i64; 10] =
+ [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
+ let v = v.checked_mul(SCALE[digits]).ok_or(OUT_OF_RANGE)?;
+
+ Ok((s, v))
+}
+
+/// Tries to parse the month index (0 through 11) with the first three ASCII letters.
+pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
+ if s.len() < 3 {
+ return Err(TOO_SHORT);
+ }
+ let buf = s.as_bytes();
+ let month0 = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) {
+ (b'j', b'a', b'n') => 0,
+ (b'f', b'e', b'b') => 1,
+ (b'm', b'a', b'r') => 2,
+ (b'a', b'p', b'r') => 3,
+ (b'm', b'a', b'y') => 4,
+ (b'j', b'u', b'n') => 5,
+ (b'j', b'u', b'l') => 6,
+ (b'a', b'u', b'g') => 7,
+ (b's', b'e', b'p') => 8,
+ (b'o', b'c', b't') => 9,
+ (b'n', b'o', b'v') => 10,
+ (b'd', b'e', b'c') => 11,
+ _ => return Err(INVALID),
+ };
+ Ok((&s[3..], month0))
+}
+
+/// Tries to parse the weekday with the first three ASCII letters.
+pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
+ if s.len() < 3 {
+ return Err(TOO_SHORT);
+ }
+ let buf = s.as_bytes();
+ let weekday = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) {
+ (b'm', b'o', b'n') => Weekday::Mon,
+ (b't', b'u', b'e') => Weekday::Tue,
+ (b'w', b'e', b'd') => Weekday::Wed,
+ (b't', b'h', b'u') => Weekday::Thu,
+ (b'f', b'r', b'i') => Weekday::Fri,
+ (b's', b'a', b't') => Weekday::Sat,
+ (b's', b'u', b'n') => Weekday::Sun,
+ _ => return Err(INVALID),
+ };
+ Ok((&s[3..], weekday))
+}
+
+/// Tries to parse the month index (0 through 11) with short or long month names.
+/// It prefers long month names to short month names when both are possible.
+pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
+ // lowercased month names, minus first three chars
+ static LONG_MONTH_SUFFIXES: [&'static str; 12] =
+ ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"];
+
+ let (mut s, month0) = short_month0(s)?;
+
+ // tries to consume the suffix if possible
+ let suffix = LONG_MONTH_SUFFIXES[month0 as usize];
+ if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
+ s = &s[suffix.len()..];
+ }
+
+ Ok((s, month0))
+}
+
+/// Tries to parse the weekday with short or long weekday names.
+/// It prefers long weekday names to short weekday names when both are possible.
+pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
+ // lowercased weekday names, minus first three chars
+ static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] =
+ ["day", "sday", "nesday", "rsday", "day", "urday", "day"];
+
+ let (mut s, weekday) = short_weekday(s)?;
+
+ // tries to consume the suffix if possible
+ let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize];
+ if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
+ s = &s[suffix.len()..];
+ }
+
+ Ok((s, weekday))
+}
+
+/// Tries to consume exactly one given character.
+pub fn char(s: &str, c1: u8) -> ParseResult<&str> {
+ match s.as_bytes().first() {
+ Some(&c) if c == c1 => Ok(&s[1..]),
+ Some(_) => Err(INVALID),
+ None => Err(TOO_SHORT),
+ }
+}
+
+/// Tries to consume one or more whitespace.
+pub fn space(s: &str) -> ParseResult<&str> {
+ let s_ = s.trim_left();
+ if s_.len() < s.len() {
+ Ok(s_)
+ } else if s.is_empty() {
+ Err(TOO_SHORT)
+ } else {
+ Err(INVALID)
+ }
+}
+
+/// Consumes any number (including zero) of colon or spaces.
+pub fn colon_or_space(s: &str) -> ParseResult<&str> {
+ Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace()))
+}
+
+/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
+///
+/// The additional `colon` may be used to parse a mandatory or optional `:`
+/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails.
+pub fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)>
+where
+ F: FnMut(&str) -> ParseResult<&str>,
+{
+ timezone_offset_internal(s, consume_colon, false)
+}
+
+fn timezone_offset_internal<F>(
+ mut s: &str,
+ mut consume_colon: F,
+ allow_missing_minutes: bool,
+) -> ParseResult<(&str, i32)>
+where
+ F: FnMut(&str) -> ParseResult<&str>,
+{
+ fn digits(s: &str) -> ParseResult<(u8, u8)> {
+ let b = s.as_bytes();
+ if b.len() < 2 {
+ Err(TOO_SHORT)
+ } else {
+ Ok((b[0], b[1]))
+ }
+ }
+ let negative = match s.as_bytes().first() {
+ Some(&b'+') => false,
+ Some(&b'-') => true,
+ Some(_) => return Err(INVALID),
+ None => return Err(TOO_SHORT),
+ };
+ s = &s[1..];
+
+ // hours (00--99)
+ let hours = match digits(s)? {
+ (h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
+ _ => return Err(INVALID),
+ };
+ s = &s[2..];
+
+ // colons (and possibly other separators)
+ s = consume_colon(s)?;
+
+ // minutes (00--59)
+ // if the next two items are digits then we have to add minutes
+ let minutes = if let Ok(ds) = digits(s) {
+ match ds {
+ (m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
+ (b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE),
+ _ => return Err(INVALID),
+ }
+ } else if allow_missing_minutes {
+ 0
+ } else {
+ return Err(TOO_SHORT);
+ };
+ s = match s.len() {
+ len if len >= 2 => &s[2..],
+ len if len == 0 => s,
+ _ => return Err(TOO_SHORT),
+ };
+
+ let seconds = hours * 3600 + minutes * 60;
+ Ok((s, if negative { -seconds } else { seconds }))
+}
+
+/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`.
+pub fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
+where
+ F: FnMut(&str) -> ParseResult<&str>,
+{
+ let bytes = s.as_bytes();
+ match bytes.first() {
+ Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
+ Some(&b'u') | Some(&b'U') => {
+ if bytes.len() >= 3 {
+ let (b, c) = (bytes[1], bytes[2]);
+ match (b | 32, c | 32) {
+ (b't', b'c') => Ok((&s[3..], 0)),
+ _ => Err(INVALID),
+ }
+ } else {
+ Err(INVALID)
+ }
+ }
+ _ => timezone_offset(s, colon),
+ }
+}
+
+/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as
+/// `+00:00`, and allows missing minutes entirely.
+pub fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
+where
+ F: FnMut(&str) -> ParseResult<&str>,
+{
+ match s.as_bytes().first() {
+ Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
+ _ => timezone_offset_internal(s, colon, true),
+ }
+}
+
+/// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones.
+/// May return `None` which indicates an insufficient offset data (i.e. `-0000`).
+pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> {
+ // tries to parse legacy time zone names
+ let upto = s
+ .as_bytes()
+ .iter()
+ .position(|&c| match c {
+ b'a'...b'z' | b'A'...b'Z' => false,
+ _ => true,
+ })
+ .unwrap_or_else(|| s.len());
+ if upto > 0 {
+ let name = &s[..upto];
+ let s = &s[upto..];
+ let offset_hours = |o| Ok((s, Some(o * 3600)));
+ if equals(name, "gmt") || equals(name, "ut") {
+ offset_hours(0)
+ } else if equals(name, "edt") {
+ offset_hours(-4)
+ } else if equals(name, "est") || equals(name, "cdt") {
+ offset_hours(-5)
+ } else if equals(name, "cst") || equals(name, "mdt") {
+ offset_hours(-6)
+ } else if equals(name, "mst") || equals(name, "pdt") {
+ offset_hours(-7)
+ } else if equals(name, "pst") {
+ offset_hours(-8)
+ } else {
+ Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000
+ }
+ } else {
+ let (s_, offset) = timezone_offset(s, |s| Ok(s))?;
+ Ok((s_, Some(offset)))
+ }
+}
+
+/// Tries to consume everyting until next whitespace-like symbol.
+/// Does not provide any offset information from the consumed data.
+pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> {
+ Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ()))
+}
diff --git a/vendor/chrono/src/format/strftime.rs b/vendor/chrono/src/format/strftime.rs
new file mode 100644
index 000000000..93820a232
--- /dev/null
+++ b/vendor/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"
+ );
+}