From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/chrono/src/format/locales.rs | 33 + vendor/chrono/src/format/mod.rs | 938 +++++++++++++++++++++++++ vendor/chrono/src/format/parse.rs | 934 +++++++++++++++++++++++++ vendor/chrono/src/format/parsed.rs | 1283 ++++++++++++++++++++++++++++++++++ vendor/chrono/src/format/scan.rs | 350 ++++++++++ vendor/chrono/src/format/strftime.rs | 649 +++++++++++++++++ 6 files changed, 4187 insertions(+) create mode 100644 vendor/chrono/src/format/locales.rs create mode 100644 vendor/chrono/src/format/mod.rs create mode 100644 vendor/chrono/src/format/parse.rs create mode 100644 vendor/chrono/src/format/parsed.rs create mode 100644 vendor/chrono/src/format/scan.rs create mode 100644 vendor/chrono/src/format/strftime.rs (limited to 'vendor/chrono/src/format') 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, "") + } +} + +/// 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), + /// 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), + /// 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`. +pub type ParseResult = Result; + +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, +) -> 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 + Clone, + B: Borrow>, +{ + 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 { + /// The date view, if any. + date: Option, + /// The time view, if any. + time: Option, + /// 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, +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { + /// Makes a new `DelayedFormat` value out of local date and time. + pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { + 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( + date: Option, + time: Option, + offset: &Off, + items: I, + ) -> DelayedFormat + 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, + time: Option, + items: I, + locale: Locale, + ) -> DelayedFormat { + 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( + date: Option, + time: Option, + offset: &Off, + items: I, + locale: Locale, + ) -> DelayedFormat + 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 + Clone, B: Borrow>> fmt::Display for DelayedFormat { + 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::(), Ok(Weekday::Sun)); +/// assert!("any day".parse::().is_err()); +/// ~~~~ +/// +/// The parsing is case-insensitive. +/// +/// ~~~~ +/// # use chrono::Weekday; +/// assert_eq!("mON".parse::(), 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::().is_err()); +/// ~~~~ +impl FromStr for Weekday { + type Err = ParseWeekdayError; + + fn from_str(s: &str) -> Result { + 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 + Clone, + B: Borrow>, +{ + 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::(), Ok(Month::January)); +/// assert!("any day".parse::().is_err()); +/// ~~~~ +/// +/// The parsing is case-insensitive. +/// +/// ~~~~ +/// # use chrono::Month; +/// assert_eq!("fEbruARy".parse::(), 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::().is_err()); +/// assert!("Augustin".parse::().is_err()); +/// ~~~~ +impl FromStr for Month { + type Err = ParseMonthError; + + fn from_str(s: &str) -> Result { + 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, + B: Borrow>, +{ + 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, + B: Borrow>, +{ + 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 { + type Err = ParseError; + + fn from_str(s: &str) -> ParseResult> { + 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 { + 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> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; + parsed.to_datetime() + } + + fn fmt_rfc2822_datetime(dt: DateTime) -> 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> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; + parsed.to_datetime() + } + + fn fmt_rfc3339_datetime(dt: DateTime) -> 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, + + /// 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, + + /// Year modulo 100. Implies that the year is >= 1 BCE when set. + pub year_mod_100: Option, + + /// 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, + + /// 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, + + /// 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, + + /// Month (1--12). + pub month: Option, + + /// 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, + + /// 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, + + /// [ISO week number](../naive/struct.NaiveDate.html#week-date) + /// (1--52 or 1--53 depending on the year). + pub isoweek: Option, + + /// Day of the week. + pub weekday: Option, + + /// Day of the year (1--365 or 1--366 depending on the year). + pub ordinal: Option, + + /// Day of the month (1--28, 1--29, 1--30 or 1--31 depending on the month). + pub day: Option, + + /// Hour number divided by 12 (0--1). 0 indicates AM and 1 indicates PM. + pub hour_div_12: Option, + + /// Hour number modulo 12 (0--11). + pub hour_mod_12: Option, + + /// Minute number (0--59). + pub minute: Option, + + /// Second number (0--60, accounting for leap seconds). + pub second: Option, + + /// The number of nanoseconds since the whole second (0--999,999,999). + pub nanosecond: Option, + + /// 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, + + /// Offset from the local time to UTC, in seconds. + pub offset: Option, + + /// 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(old: &mut Option, 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 { + fn resolve_year( + y: Option, + q: Option, + r: Option, + ) -> ParseResult> { + 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 { + 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 { + 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 { + 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> { + 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(&self, tz: &Tz) -> ParseResult> { + // 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| { + 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(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( + 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(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(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)> { + // 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. +
+
+ 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`: +
+ 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. +
+
+ 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 `.`. +
+
+ 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. +
+
+ 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. +
+
+ 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>; +#[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> { + // 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> { + // 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::>>().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" + ); +} -- cgit v1.2.3