diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/chrono/src/format | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/chrono/src/format')
-rw-r--r-- | vendor/chrono/src/format/mod.rs | 483 | ||||
-rw-r--r-- | vendor/chrono/src/format/parse.rs | 97 | ||||
-rw-r--r-- | vendor/chrono/src/format/parsed.rs | 160 | ||||
-rw-r--r-- | vendor/chrono/src/format/scan.rs | 121 | ||||
-rw-r--r-- | vendor/chrono/src/format/strftime.rs | 177 |
5 files changed, 678 insertions, 360 deletions
diff --git a/vendor/chrono/src/format/mod.rs b/vendor/chrono/src/format/mod.rs index a641f196d..c05ba4d04 100644 --- a/vendor/chrono/src/format/mod.rs +++ b/vendor/chrono/src/format/mod.rs @@ -12,10 +12,26 @@ //! 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). +//! currently Chrono supports a built-in syntax closely resembling +//! C's `strftime` format. The available options can be found [here](./strftime/index.html). +//! +//! # Example +//! ```rust +//! # use std::error::Error; +//! use chrono::prelude::*; +//! +//! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap(); +//! +//! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S")); +//! assert_eq!(formatted, "2020-11-10 00:01:32"); +//! +//! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?; +//! assert_eq!(parsed, date_time); +//! # Ok::<(), chrono::ParseError>(()) +//! ``` -#![allow(ellipsis_inclusive_range_patterns)] +#[cfg(feature = "alloc")] +extern crate alloc; #[cfg(feature = "alloc")] use alloc::boxed::Box; @@ -24,38 +40,40 @@ use alloc::string::{String, ToString}; #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; use core::fmt; +use core::fmt::Write; use core::str::FromStr; #[cfg(any(feature = "std", test))] use std::error::Error; #[cfg(any(feature = "alloc", feature = "std", test))] -use naive::{NaiveDate, NaiveTime}; +use crate::naive::{NaiveDate, NaiveTime}; #[cfg(any(feature = "alloc", feature = "std", test))] -use offset::{FixedOffset, Offset}; +use crate::offset::{FixedOffset, Offset}; #[cfg(any(feature = "alloc", feature = "std", test))] -use {Datelike, Timelike}; -use {Month, ParseMonthError, ParseWeekdayError, Weekday}; +use crate::{Datelike, Timelike}; +use crate::{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; +pub use parse::parse; +pub use parsed::Parsed; /// L10n locales. #[cfg(feature = "unstable-locales")] pub use pure_rust_locales::Locale; +pub use strftime::StrftimeItems; #[cfg(not(feature = "unstable-locales"))] +#[allow(dead_code)] #[derive(Debug)] struct Locale; /// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] enum Void {} /// Padding characters for numeric items. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Pad { /// No padding. None, @@ -78,10 +96,10 @@ pub enum Pad { /// 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)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Numeric { /// Full Gregorian year (FW=4, PW=∞). - /// May accept years before 1 BCE or after 9999 CE, given an initial sign. + /// 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, @@ -134,24 +152,11 @@ pub enum Numeric { } /// An opaque type representing numeric item types for internal uses only. +#[derive(Clone, Eq, Hash, PartialEq)] pub struct InternalNumeric { _dummy: Void, } -impl Clone for InternalNumeric { - fn clone(&self) -> Self { - match self._dummy {} - } -} - -impl PartialEq for InternalNumeric { - fn eq(&self, _other: &InternalNumeric) -> bool { - match self._dummy {} - } -} - -impl Eq for InternalNumeric {} - impl fmt::Debug for InternalNumeric { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "<InternalNumeric>") @@ -162,7 +167,7 @@ impl fmt::Debug for InternalNumeric { /// /// 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)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Fixed { /// Abbreviated month names. /// @@ -208,6 +213,18 @@ pub enum Fixed { /// 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 with seconds (`+09:00:00` or `-04:00:00` or `+00: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:00` to `+24:00:00`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetDoubleColon, + /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). + /// + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// The offset is limited from `-24` to `+24`, + /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. + TimezoneOffsetTripleColon, /// 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, @@ -234,12 +251,12 @@ pub enum Fixed { } /// An opaque type representing fixed-format item types for internal uses only. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InternalFixed { val: InternalInternal, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum InternalInternal { /// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but /// allows missing minutes (per [ISO 8601][iso8601]). @@ -258,18 +275,29 @@ enum InternalInternal { Nanosecond9NoDot, } +#[cfg(any(feature = "alloc", feature = "std", test))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum Colons { + None, + Single, + Double, + Triple, +} + /// A single formatting item. This is used for both formatting and parsing. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Hash)] 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))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedLiteral(Box<str>), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedSpace(Box<str>), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. @@ -317,12 +345,19 @@ macro_rules! internal_fix { } /// An error from the `parse` function. -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ParseError(ParseErrorKind); +impl ParseError { + /// The category of parse error + pub const fn kind(&self) -> ParseErrorKind { + self.0 + } +} + /// The category of parse error -#[derive(Debug, Clone, PartialEq, Eq, Copy)] -enum ParseErrorKind { +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +pub enum ParseErrorKind { /// Given field is out of permitted range. OutOfRange, @@ -350,6 +385,10 @@ enum ParseErrorKind { /// There was an error on the formatting string, or there were non-supported formating items. BadFormat, + + // TODO: Change this to `#[non_exhaustive]` (on the enum) when MSRV is increased + #[doc(hidden)] + __Nonexhaustive, } /// Same as `Result<T, ParseError>`. @@ -365,11 +404,13 @@ impl fmt::Display for ParseError { ParseErrorKind::TooShort => write!(f, "premature end of input"), ParseErrorKind::TooLong => write!(f, "trailing input"), ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"), + _ => unreachable!(), } } } #[cfg(any(feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ParseError { #[allow(deprecated)] fn description(&self) -> &str { @@ -386,14 +427,72 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); +#[cfg(any(feature = "alloc", feature = "std", test))] +struct Locales { + short_months: &'static [&'static str], + long_months: &'static [&'static str], + short_weekdays: &'static [&'static str], + long_weekdays: &'static [&'static str], + am_pm: &'static [&'static str], +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl Locales { + fn new(_locale: Option<Locale>) -> Self { + #[cfg(feature = "unstable-locales")] + { + let locale = _locale.unwrap_or(Locale::POSIX); + Self { + short_months: locales::short_months(locale), + long_months: locales::long_months(locale), + short_weekdays: locales::short_weekdays(locale), + long_weekdays: locales::long_weekdays(locale), + am_pm: locales::am_pm(locale), + } + } + #[cfg(not(feature = "unstable-locales"))] + Self { + short_months: &[ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ], + long_months: &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + long_weekdays: &[ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + am_pm: &["AM", "PM"], + } + } +} + /// Formats single formatting item #[cfg(any(feature = "alloc", feature = "std", test))] -pub fn format_item<'a>( +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, - item: &Item<'a>, + item: &Item<'_>, ) -> fmt::Result { let mut result = String::new(); format_inner(&mut result, date, time, off, item, None)?; @@ -401,54 +500,17 @@ pub fn format_item<'a>( } #[cfg(any(feature = "alloc", feature = "std", test))] -fn format_inner<'a>( +fn format_inner( result: &mut String, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, - item: &Item<'a>, - _locale: Option<Locale>, + item: &Item<'_>, + locale: Option<Locale>, ) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - let locale = _locale.unwrap_or(Locale::POSIX); - let am_pm = locales::am_pm(locale); - ( - locales::short_months(locale), - locales::long_months(locale), - locales::short_weekdays(locale), - locales::long_weekdays(locale), - am_pm, - &[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()], - ) - }; - #[cfg(not(feature = "unstable-locales"))] - let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = { - ( - &["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - &["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - &["AM", "PM"], - &["am", "pm"], - ) - }; + let locale = Locales::new(locale); - use core::fmt::Write; - use div::{div_floor, mod_floor}; + use num_integer::{div_floor, mod_floor}; match *item { Item::Literal(s) | Item::Space(s) => result.push_str(s), @@ -458,12 +520,8 @@ fn format_inner<'a>( 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 week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); let (width, v) = match *spec { Year => (4, date.map(|d| i64::from(d.year()))), @@ -501,7 +559,7 @@ fn format_inner<'a>( }; if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { // non-four-digit years require an explicit sign as per ISO 8601 match *pad { Pad::None => write!(result, "{:+}", v), @@ -523,60 +581,41 @@ fn format_inner<'a>( 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]); + result.push_str(locale.short_months[d.month0() as usize]); Ok(()) }), LongMonthName => date.map(|d| { - result.push_str(long_months[d.month0() as usize]); + result.push_str(locale.long_months[d.month0() as usize]); Ok(()) }), ShortWeekdayName => date.map(|d| { - result - .push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str( + locale.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]); + result.push_str( + locale.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() - }); + let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + for char in ampm.chars() { + result.extend(char.to_lowercase()) } Ok(()) }), UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] }); + result.push_str(if t.hour12().0 { + locale.am_pm[1] + } else { + locale.am_pm[0] + }); Ok(()) }), Nanosecond => time.map(|t| { @@ -618,21 +657,23 @@ fn format_inner<'a>( let nano = t.nanosecond() % 1_000_000_000; write!(result, "{:09}", nano) }), - TimezoneName => off.map(|&(ref name, _)| { + TimezoneName => off.map(|(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)) - } + TimezoneOffsetColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), + TimezoneOffsetDoubleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), + TimezoneOffsetTripleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), + TimezoneOffsetColonZ => off + .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, false)) + off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) } TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, false)) + off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) } Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { panic!("Do not try to write %#z it is undefined") @@ -641,19 +682,7 @@ fn format_inner<'a>( // 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)) + Some(write_rfc2822_inner(result, d, t, off, locale)) } else { None } @@ -662,10 +691,7 @@ fn format_inner<'a>( // 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)) + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) } else { None } @@ -683,9 +709,114 @@ fn format_inner<'a>( Ok(()) } +/// 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. +#[cfg(any(feature = "alloc", feature = "std", test))] +fn write_local_minus_utc( + result: &mut String, + off: FixedOffset, + allow_zulu: bool, + colon_type: Colons, +) -> fmt::Result { + let off = off.local_minus_utc(); + if allow_zulu && off == 0 { + result.push('Z'); + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + result.push(sign); + + write_hundreds(result, (off / 3600) as u8)?; + + match colon_type { + Colons::None => write_hundreds(result, (off / 60 % 60) as u8), + Colons::Single => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8) + } + Colons::Double => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8)?; + result.push(':'); + write_hundreds(result, (off % 60) as u8) + } + Colons::Triple => Ok(()), + } +} + +/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` +#[cfg(any(feature = "alloc", feature = "std", test))] +pub(crate) fn write_rfc3339( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + // reuse `Debug` impls which already print ISO 8601 format. + // this is faster in this way. + write!(result, "{:?}", dt)?; + write_local_minus_utc(result, off, false, Colons::Single) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +pub(crate) fn write_rfc2822( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +fn write_rfc2822_inner( + result: &mut String, + d: &NaiveDate, + t: &NaiveTime, + off: FixedOffset, + locale: Locales, +) -> fmt::Result { + let year = d.year(); + // RFC2822 is only defined on years 0 through 9999 + if !(0..=9999).contains(&year) { + return Err(fmt::Error); + } + + result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str(", "); + write_hundreds(result, d.day() as u8)?; + result.push(' '); + result.push_str(locale.short_months[d.month0() as usize]); + result.push(' '); + write_hundreds(result, (year / 100) as u8)?; + write_hundreds(result, (year % 100) as u8)?; + result.push(' '); + write_hundreds(result, t.hour() as u8)?; + result.push(':'); + write_hundreds(result, t.minute() as u8)?; + result.push(':'); + let sec = t.second() + t.nanosecond() / 1_000_000_000; + write_hundreds(result, sec as u8)?; + result.push(' '); + write_local_minus_utc(result, off, false, Colons::None) +} + +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { + if n >= 100 { + return Err(fmt::Error); + } + + let tens = b'0' + n / 10; + let ones = b'0' + n % 10; + w.write_char(tens as char)?; + w.write_char(ones as char) +} + /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -715,6 +846,7 @@ 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))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[derive(Debug)] pub struct DelayedFormat<I> { /// The date view, if any. @@ -726,6 +858,9 @@ pub struct DelayedFormat<I> { /// An iterator returning formatting items. items: I, /// Locale used for text. + // TODO: Only used with the locale feature. We should make this property + // only present when the feature is enabled. + #[cfg(feature = "unstable-locales")] locale: Option<Locale>, } @@ -733,7 +868,14 @@ pub struct DelayedFormat<I> { impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> { /// Makes a new `DelayedFormat` value out of local date and time. pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> { - DelayedFormat { date: date, time: time, off: None, items: items, locale: None } + DelayedFormat { + date, + time, + off: None, + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } } /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. @@ -748,27 +890,30 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> { { let name_and_diff = (offset.to_string(), offset.fix()); DelayedFormat { - date: date, - time: time, + date, + time, off: Some(name_and_diff), - items: items, + items, + #[cfg(feature = "unstable-locales")] locale: None, } } /// Makes a new `DelayedFormat` value out of local date and time and locale. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn new_with_locale( date: Option<NaiveDate>, time: Option<NaiveTime>, items: I, locale: Locale, ) -> DelayedFormat<I> { - DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) } + DelayedFormat { date, time, off: None, items, locale: Some(locale) } } /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn new_with_offset_and_locale<Off>( date: Option<NaiveDate>, time: Option<NaiveTime>, @@ -780,13 +925,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> { 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), - } + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } } } @@ -817,26 +956,26 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for De /// /// # Example /// -/// ~~~~ +/// ``` /// use chrono::Weekday; /// /// assert_eq!("Sunday".parse::<Weekday>(), Ok(Weekday::Sun)); /// assert!("any day".parse::<Weekday>().is_err()); -/// ~~~~ +/// ``` /// /// The parsing is case-insensitive. /// -/// ~~~~ +/// ``` /// # use chrono::Weekday; /// assert_eq!("mON".parse::<Weekday>(), Ok(Weekday::Mon)); -/// ~~~~ +/// ``` /// /// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted. /// -/// ~~~~ +/// ``` /// # use chrono::Weekday; /// assert!("thurs".parse::<Weekday>().is_err()); -/// ~~~~ +/// ``` impl FromStr for Weekday { type Err = ParseWeekdayError; @@ -851,6 +990,7 @@ impl FromStr for Weekday { /// Formats single formatting item #[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_item_localized<'a>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -867,6 +1007,7 @@ pub fn format_item_localized<'a>( /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. #[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] pub fn format_localized<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -890,27 +1031,27 @@ where /// /// # Example /// -/// ~~~~ +/// ``` /// use chrono::Month; /// /// assert_eq!("January".parse::<Month>(), Ok(Month::January)); /// assert!("any day".parse::<Month>().is_err()); -/// ~~~~ +/// ``` /// /// The parsing is case-insensitive. /// -/// ~~~~ +/// ``` /// # use chrono::Month; /// assert_eq!("fEbruARy".parse::<Month>(), Ok(Month::February)); -/// ~~~~ +/// ``` /// /// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted. /// -/// ~~~~ +/// ``` /// # use chrono::Month; /// assert!("septem".parse::<Month>().is_err()); /// assert!("Augustin".parse::<Month>().is_err()); -/// ~~~~ +/// ``` impl FromStr for Month { type Err = ParseMonthError; diff --git a/vendor/chrono/src/format/parse.rs b/vendor/chrono/src/format/parse.rs index 2fce8277b..69204d2e9 100644 --- a/vendor/chrono/src/format/parse.rs +++ b/vendor/chrono/src/format/parse.rs @@ -14,7 +14,7 @@ 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}; +use crate::{DateTime, FixedOffset, Weekday}; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { @@ -53,7 +53,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // an adapted RFC 2822 syntax from Section 3.3 and 4.3: // - // date-time = [ day-of-week "," ] date 1*S time *S + // c-char = <any char except '(', ')' and '\\'> + // c-escape = "\" <any char> + // comment = "(" *(comment / c-char / c-escape) ")" *S + // date-time = [ day-of-week "," ] date 1*S time *S *comment // day-of-week = *S day-name *S // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // date = day month year @@ -79,9 +82,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // // - 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. + // white space characters (denoted here to `S`). For comments, we accept + // any text within parentheses while respecting escaped parentheses. + // 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 @@ -117,10 +121,10 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st let mut year = try_consume!(scan::number(s, 2, usize::MAX)); let yearlen = prevlen - s.len(); match (yearlen, year) { - (2, 0...49) => { + (2, 0..=49) => { year += 2000; } // 47 -> 2047, 05 -> 2005 - (2, 50...99) => { + (2, 50..=99) => { year += 1900; } // 79 -> 1979 (3, _) => { @@ -145,6 +149,11 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_offset(i64::from(offset))?; } + // optional comments + while let Ok((s_out, ())) = scan::comment_2822(s) { + s = s_out; + } + Ok((s, ())) } @@ -411,7 +420,10 @@ where try_consume!(scan::timezone_name_skip(s)); } - &TimezoneOffsetColon | &TimezoneOffset => { + &TimezoneOffsetColon + | &TimezoneOffsetDoubleColon + | &TimezoneOffsetTripleColon + | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( s.trim_left(), scan::colon_or_space @@ -455,11 +467,22 @@ where } } +/// Accepts a relaxed form of RFC3339. +/// A space or a 'T' are acepted as the separator between the date and time +/// parts. Additional spaces are allowed between each component. +/// +/// All of these examples are equivalent: +/// ``` +/// # use chrono::{DateTime, offset::FixedOffset}; +/// "2012-12-12T12:12:12Z".parse::<DateTime<FixedOffset>>(); +/// "2012-12-12 12:12:12Z".parse::<DateTime<FixedOffset>>(); +/// "2012- 12-12T12: 12:12Z".parse::<DateTime<FixedOffset>>(); +/// ``` impl str::FromStr for DateTime<FixedOffset> { type Err = ParseError; fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> { - const DATE_ITEMS: &'static [Item<'static>] = &[ + const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), Item::Space(""), Item::Literal("-"), @@ -468,7 +491,7 @@ impl str::FromStr for DateTime<FixedOffset> { Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; - const TIME_ITEMS: &'static [Item<'static>] = &[ + const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), Item::Space(""), Item::Literal(":"), @@ -488,11 +511,11 @@ impl str::FromStr for DateTime<FixedOffset> { if remainder.starts_with('T') || remainder.starts_with(' ') { parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?; } else { - Err(INVALID)?; + return Err(INVALID); } } - Err((_s, e)) => Err(e)?, - Ok(_) => Err(NOT_ENOUGH)?, + Err((_s, e)) => return Err(e), + Ok(_) => return Err(NOT_ENOUGH), }; parsed.to_datetime() } @@ -557,7 +580,7 @@ fn test_parse() { 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!("\x005", [num!(Year)]; INVALID); check!("", [num!(Year)]; TOO_SHORT); check!("12345", [num!(Year), lit!("5")]; year: 1234); check!("12345", [nums!(Year), lit!("5")]; year: 1234); @@ -798,14 +821,25 @@ fn test_parse() { fn test_rfc2822() { use super::NOT_ENOUGH; use super::*; - use offset::FixedOffset; - use DateTime; + use crate::offset::FixedOffset; + use crate::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 + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ( + r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // complex trailing comment + (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + ( + "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // multiple comments + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment ("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 @@ -853,12 +887,12 @@ fn test_rfc2822() { #[cfg(test)] #[test] fn parse_rfc850() { - use {TimeZone, Utc}; + use crate::{TimeZone, Utc}; - static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT"; + static RFC850_FMT: &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); + let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); @@ -869,12 +903,21 @@ fn parse_rfc850() { // 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"), + (Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), "Monday, 07-Nov-94 08:49:37 GMT"), + (Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), "Tuesday, 08-Nov-94 08:49:37 GMT"), + ( + Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), + "Wednesday, 09-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), + "Thursday, 10-Nov-94 08:49:37 GMT", + ), + (Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), "Friday, 11-Nov-94 08:49:37 GMT"), + ( + Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), + "Saturday, 12-Nov-94 08:49:37 GMT", + ), ]; for val in &testdates { @@ -886,8 +929,8 @@ fn parse_rfc850() { #[test] fn test_rfc3339() { use super::*; - use offset::FixedOffset; - use DateTime; + use crate::offset::FixedOffset; + use crate::DateTime; // Test data - (input, Ok(expected result after parse and format) or Err(error code)) let testdates = [ diff --git a/vendor/chrono/src/format/parsed.rs b/vendor/chrono/src/format/parsed.rs index b8ed2d90f..6cc29e9d4 100644 --- a/vendor/chrono/src/format/parsed.rs +++ b/vendor/chrono/src/format/parsed.rs @@ -4,16 +4,16 @@ //! A collection of parsed date and time items. //! They can be constructed incrementally while being checked for consistency. +use num_integer::div_rem; 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}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; +use crate::oldtime::Duration as OldDuration; +use crate::DateTime; +use crate::Weekday; +use crate::{Datelike, Timelike}; /// Parsed parts of date and time. There are two classes of methods: /// @@ -22,8 +22,7 @@ use {Datelike, Timelike}; /// /// - `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)] +#[derive(Clone, PartialEq, Eq, Debug, Default, Hash)] pub struct Parsed { /// Year. /// @@ -126,34 +125,6 @@ fn set_if_consistent<T: PartialEq>(old: &mut Option<T>, new: T) -> ParseResult<( } } -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 { @@ -254,14 +225,14 @@ impl Parsed { /// (`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 }) + set_if_consistent(&mut self.hour_div_12, u32::from(value)) } /// 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 { + if !(1..=12).contains(&value) { return Err(OUT_OF_RANGE); } set_if_consistent(&mut self.hour_mod_12, value as u32 % 12) @@ -333,7 +304,7 @@ impl Parsed { // 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) => { + (Some(y), q, r @ Some(0..=99)) | (Some(y), q, r @ None) => { if y < 0 { return Err(OUT_OF_RANGE); } @@ -347,7 +318,7 @@ impl Parsed { // 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)) => { + (None, Some(q), Some(r @ 0..=99)) => { if q < 0 { return Err(OUT_OF_RANGE); } @@ -357,7 +328,7 @@ impl Parsed { // 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 })), + (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), @@ -408,9 +379,8 @@ impl Parsed { // 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; + let week_from_sun = date.weeks_from(Weekday::Sun); + let week_from_mon = date.weeks_from(Weekday::Mon); 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 @@ -528,32 +498,32 @@ impl Parsed { /// It is able to handle leap seconds when given second is 60. pub fn to_naive_time(&self) -> ParseResult<NaiveTime> { let hour_div_12 = match self.hour_div_12 { - Some(v @ 0...1) => v, + Some(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(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(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), + 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(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, }; @@ -655,6 +625,12 @@ impl Parsed { 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)?; + + // this is used to prevent an overflow when calling FixedOffset::from_local_datetime + datetime + .checked_sub_signed(OldDuration::seconds(i64::from(offset.local_minus_utc()))) + .ok_or(OUT_OF_RANGE)?; + match offset.from_local_datetime(&datetime) { LocalResult::None => Err(IMPOSSIBLE), LocalResult::Single(t) => Ok(t), @@ -721,10 +697,10 @@ impl Parsed { 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::*; + use crate::naive::{NaiveDate, NaiveTime}; + use crate::offset::{FixedOffset, TimeZone, Utc}; + use crate::Datelike; + use crate::Weekday::*; #[test] fn test_parsed_set_fields() { @@ -805,7 +781,7 @@ mod tests { ) } - let ymd = |y, m, d| Ok(NaiveDate::from_ymd(y, m, d)); + let ymd = |y, m, d| Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap()); // ymd: omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -851,7 +827,7 @@ mod tests { 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(); + let max_year = NaiveDate::MAX.year(); assert_eq!( parse!(year_div_100: max_year / 100, year_mod_100: max_year % 100, month: 1, day: 1), @@ -992,8 +968,8 @@ mod tests { ) } - 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)); + let hms = |h, m, s| Ok(NaiveTime::from_hms_opt(h, m, s).unwrap()); + let hmsn = |h, m, s, n| Ok(NaiveTime::from_hms_nano_opt(h, m, s, n).unwrap()); // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1048,9 +1024,12 @@ mod tests { ($($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)); + let ymdhms = |y, m, d, h, n, s| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap()) + }; + let ymdhmsn = |y, m, d, h, n, s, nano| { + Ok(NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_nano_opt(h, n, s, nano).unwrap()) + }; // omission of fields assert_eq!(parse!(), Err(NOT_ENOUGH)); @@ -1104,14 +1083,15 @@ mod tests { // 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)); + NaiveDate::MAX.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); + let year_0_from_year_1970 = NaiveDate::from_ymd_opt(0, 1, 1) + .unwrap() + .signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let min_days_from_year_1970 = - MIN_DATE.signed_duration_since(NaiveDate::from_ymd(1970, 1, 1)); + NaiveDate::MIN.signed_duration_since(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); assert_eq!( parse!(timestamp: min_days_from_year_1970.num_seconds()), - ymdhms(MIN_DATE.year(), 1, 1, 0, 0, 0) + ymdhms(NaiveDate::MIN.year(), 1, 1, 0, 0, 0) ); assert_eq!( parse!(timestamp: year_0_from_year_1970.num_seconds()), @@ -1119,7 +1099,7 @@ mod tests { ); assert_eq!( parse!(timestamp: max_days_from_year_1970.num_seconds() + 86399), - ymdhms(MAX_DATE.year(), 12, 31, 23, 59, 59) + ymdhms(NaiveDate::MAX.year(), 12, 31, 23, 59, 59) ); // leap seconds #1: partial fields @@ -1198,7 +1178,15 @@ mod tests { } 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)) + Ok(FixedOffset::east_opt(off) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(y, m, d) + .unwrap() + .and_hms_nano_opt(h, n, s, nano) + .unwrap(), + ) + .unwrap()) }; assert_eq!(parse!(offset: 0), Err(NOT_ENOUGH)); @@ -1242,7 +1230,14 @@ mod tests { 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)) + Ok(Utc + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(4, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); assert_eq!( parse!(Utc; @@ -1251,31 +1246,42 @@ mod tests { Err(IMPOSSIBLE) ); assert_eq!( - parse!(FixedOffset::east(32400); + parse!(FixedOffset::east_opt(32400).unwrap(); 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); + parse!(FixedOffset::east_opt(32400).unwrap(); 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)) + Ok(FixedOffset::east_opt(32400) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2014, 12, 31) + .unwrap() + .and_hms_nano_opt(13, 26, 40, 12_345_678) + .unwrap() + ) + .unwrap()) ); // 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)) + Ok(Utc.with_ymd_and_hms(2014, 12, 31, 4, 26, 40).unwrap()) ); 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), + parse!(FixedOffset::east_opt(32400).unwrap(); 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)) + parse!(FixedOffset::east_opt(32400).unwrap(); timestamp: 1_420_000_000, offset: 32400), + Ok(FixedOffset::east_opt(32400) + .unwrap() + .with_ymd_and_hms(2014, 12, 31, 13, 26, 40) + .unwrap()) ); // 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 index 0efb1ee3d..263fec556 100644 --- a/vendor/chrono/src/format/scan.rs +++ b/vendor/chrono/src/format/scan.rs @@ -8,13 +8,13 @@ #![allow(deprecated)] use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; -use Weekday; +use crate::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, + b'A'..=b'Z' => c + 32, _ => c, }); let mut ys = pattern.as_bytes().iter().cloned(); @@ -34,7 +34,7 @@ fn equals(s: &str, pattern: &str) -> bool { /// 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)> { +pub(super) 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 @@ -48,7 +48,7 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { let mut n = 0i64; for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied() - if c < b'0' || b'9' < c { + if !(b'0'..=b'9').contains(&c) { if i < min { return Err(INVALID); } else { @@ -62,12 +62,12 @@ pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> { }; } - Ok((&s[::core::cmp::min(max, bytes.len())..], n)) + 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)> { +pub(super) 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)?; @@ -79,14 +79,14 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { 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'); + let s = s.trim_left_matches(|c: char| ('0'..='9').contains(&c)); 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)> { +pub(super) 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)?; @@ -99,7 +99,7 @@ pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { } /// Tries to parse the month index (0 through 11) with the first three ASCII letters. -pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { +pub(super) fn short_month0(s: &str) -> ParseResult<(&str, u8)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -123,7 +123,7 @@ pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { } /// Tries to parse the weekday with the first three ASCII letters. -pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { +pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { if s.len() < 3 { return Err(TOO_SHORT); } @@ -143,9 +143,9 @@ pub fn short_weekday(s: &str) -> ParseResult<(&str, 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)> { +pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars - static LONG_MONTH_SUFFIXES: [&'static str; 12] = + static LONG_MONTH_SUFFIXES: [&str; 12] = ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; let (mut s, month0) = short_month0(s)?; @@ -161,9 +161,9 @@ pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// 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)> { +pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars - static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] = + static LONG_WEEKDAY_SUFFIXES: [&str; 7] = ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; let (mut s, weekday) = short_weekday(s)?; @@ -178,7 +178,7 @@ pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { } /// Tries to consume exactly one given character. -pub fn char(s: &str, c1: u8) -> ParseResult<&str> { +pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> { match s.as_bytes().first() { Some(&c) if c == c1 => Ok(&s[1..]), Some(_) => Err(INVALID), @@ -187,7 +187,7 @@ pub fn char(s: &str, c1: u8) -> ParseResult<&str> { } /// Tries to consume one or more whitespace. -pub fn space(s: &str) -> ParseResult<&str> { +pub(super) fn space(s: &str) -> ParseResult<&str> { let s_ = s.trim_left(); if s_.len() < s.len() { Ok(s_) @@ -199,7 +199,7 @@ pub fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub fn colon_or_space(s: &str) -> ParseResult<&str> { +pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) } @@ -207,7 +207,7 @@ pub fn colon_or_space(s: &str) -> ParseResult<&str> { /// /// The additional `colon` may be used to parse a mandatory or optional `:` /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -240,7 +240,7 @@ where // 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')), + (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..]; @@ -252,8 +252,8 @@ where // 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), + (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 { @@ -272,7 +272,7 @@ where } /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -296,7 +296,7 @@ where /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as /// `+00:00`, and allows missing minutes entirely. -pub fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)> +pub(super) fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { @@ -308,16 +308,16 @@ where /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. /// May return `None` which indicates an insufficient offset data (i.e. `-0000`). -pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> { +pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> { // tries to parse legacy time zone names let upto = s .as_bytes() .iter() .position(|&c| match c { - b'a'...b'z' | b'A'...b'Z' => false, + b'a'..=b'z' | b'A'..=b'Z' => false, _ => true, }) - .unwrap_or_else(|| s.len()); + .unwrap_or(s.len()); if upto > 0 { let name = &s[..upto]; let s = &s[upto..]; @@ -343,8 +343,73 @@ pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> { } } -/// Tries to consume everyting until next whitespace-like symbol. +/// Tries to consume everything until next whitespace-like symbol. /// Does not provide any offset information from the consumed data. -pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { +pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) } + +/// Tries to consume an RFC2822 comment including preceding ` `. +/// +/// Returns the remaining string after the closing parenthesis. +pub(super) fn comment_2822(s: &str) -> ParseResult<(&str, ())> { + use CommentState::*; + + let s = s.trim_start(); + + let mut state = Start; + for (i, c) in s.bytes().enumerate() { + state = match (state, c) { + (Start, b'(') => Next(1), + (Next(1), b')') => return Ok((&s[i + 1..], ())), + (Next(depth), b'\\') => Escape(depth), + (Next(depth), b'(') => Next(depth + 1), + (Next(depth), b')') => Next(depth - 1), + (Next(depth), _) | (Escape(depth), _) => Next(depth), + _ => return Err(INVALID), + }; + } + + Err(TOO_SHORT) +} + +enum CommentState { + Start, + Next(usize), + Escape(usize), +} + +#[cfg(test)] +#[test] +fn test_rfc2822_comments() { + let testdata = [ + ("", Err(TOO_SHORT)), + (" ", Err(TOO_SHORT)), + ("x", Err(INVALID)), + ("(", Err(TOO_SHORT)), + ("()", Ok("")), + (" \r\n\t()", Ok("")), + ("() ", Ok(" ")), + ("()z", Ok("z")), + ("(x)", Ok("")), + ("(())", Ok("")), + ("((()))", Ok("")), + ("(x(x(x)x)x)", Ok("")), + ("( x ( x ( x ) x ) x )", Ok("")), + (r"(\)", Err(TOO_SHORT)), + (r"(\()", Ok("")), + (r"(\))", Ok("")), + (r"(\\)", Ok("")), + ("(()())", Ok("")), + ("( x ( x ) x ( x ) x )", Ok("")), + ]; + + for (test_in, expected) in testdata.iter() { + let actual = comment_2822(test_in).map(|(s, _)| s); + assert_eq!( + *expected, actual, + "{:?} expected to produce {:?}, but produced {:?}.", + test_in, expected, actual + ); + } +} diff --git a/vendor/chrono/src/format/strftime.rs b/vendor/chrono/src/format/strftime.rs index 93820a232..dcaabe49f 100644 --- a/vendor/chrono/src/format/strftime.rs +++ b/vendor/chrono/src/format/strftime.rs @@ -11,9 +11,9 @@ 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] | +| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. Note: years before 1 BCE or after 9999 CE, require an initial sign (+/-).| +| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] | +| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] | | | | | | `%m` | `07` | Month number (01--12), zero-padded to 2 digits. | | `%b` | `Jul` | Abbreviated month name. Always 3 letters. | @@ -28,12 +28,12 @@ The following specifiers are available both to formatting and 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] | +| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^2] | | `%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] | +| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^3] | +| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^3] | +| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^3] | | | | | | `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. | | | | | @@ -52,15 +52,15 @@ The following specifiers are available both to formatting and parsing. | `%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] | +| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] | +| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^7] | +| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^7] | +| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^7] | +| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^7] | +| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^7] | +| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^7] | +| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^7] | +| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^7] | | | | | | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | @@ -68,16 +68,18 @@ The following specifiers are available both to formatting and parsing. | `%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` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^8] | | `%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:30:00`| Offset from the local time to UTC with seconds. | +|`%:::z`| `+09` | Offset from the local time to UTC without minutes. | | `%#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] | +| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^5] | | | | | -| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]| +| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]| | | | | | | | **SPECIAL SPECIFIERS:** | | `%t` | | Literal tab (`\t`). | @@ -95,38 +97,42 @@ Modifier | Description Notes: -[^1]: `%Y`: - Negative years are allowed in formatting but not in parsing. - -[^2]: `%C`, `%y`: +[^1]: `%C`, `%y`: This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively. -[^3]: `%U`: +[^2]: `%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`: +[^3]: `%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`: +[^4]: `%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 +[^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional digits for seconds and colons in the time zone offset. <br> <br> + This format also supports having a `Z` or `UTC` in place of `%:z`. They + are equivalent to `+00:00`. + <br> + <br> + Note that all `T`, `Z`, and `UTC` are parsed case-insensitively. + <br> + <br> The typical `strftime` implementations have different (and locale-dependent) formats for this specifier. While Chrono's format for `%+` is far more stable, it is best to avoid this specifier if you want to control the exact output. -[^7]: `%s`: +[^6]: `%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`: +[^7]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: <br> The default `%f` is right-aligned and always zero-padded to 9 digits for the compatibility with glibc and others, @@ -157,7 +163,7 @@ Notes: and parsing `07`, `070000` etc. will yield the same. Note that they can read nothing if the fractional part is zero. -[^9]: `%Z`: +[^8]: `%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. @@ -169,6 +175,12 @@ Notes: */ #[cfg(feature = "unstable-locales")] +extern crate alloc; + +#[cfg(feature = "unstable-locales")] +use alloc::vec::Vec; + +#[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; @@ -177,9 +189,9 @@ type Fmt<'a> = Vec<Item<'a>>; #[cfg(not(feature = "unstable-locales"))] type Fmt<'a> = &'static [Item<'static>]; -static D_FMT: &'static [Item<'static>] = +static D_FMT: &[Item<'static>] = &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; -static D_T_FMT: &'static [Item<'static>] = &[ +static D_T_FMT: &[Item<'static>] = &[ fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), @@ -194,8 +206,7 @@ static D_T_FMT: &'static [Item<'static>] = &[ sp!(" "), num0!(Year), ]; -static T_FMT: &'static [Item<'static>] = - &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; +static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; /// Parsing iterator for `strftime`-like format strings. #[derive(Clone, Debug)] @@ -222,23 +233,18 @@ impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(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, - } + StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt } } #[cfg(not(feature = "unstable-locales"))] fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &'static [Item<'static>; 0] = &[]; + static FMT_NONE: &[Item<'static>; 0] = &[]; StrftimeItems { remainder: s, @@ -261,7 +267,7 @@ impl<'a> StrftimeItems<'a> { } } -const HAVE_ALTERNATES: &'static str = "z"; +const HAVE_ALTERNATES: &str = "z"; impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; @@ -407,10 +413,20 @@ impl<'a> Iterator for StrftimeItems<'a> { } } '+' => fix!(RFC3339), - ':' => match next!() { - 'z' => fix!(TimezoneOffsetColon), - _ => Item::Error, - }, + ':' => { + if self.remainder.starts_with("::z") { + self.remainder = &self.remainder[3..]; + fix!(TimezoneOffsetTripleColon) + } else if self.remainder.starts_with(":z") { + self.remainder = &self.remainder[2..]; + fix!(TimezoneOffsetDoubleColon) + } else if self.remainder.starts_with('z') { + self.remainder = &self.remainder[1..]; + fix!(TimezoneOffsetColon) + } else { + Item::Error + } + } '.' => match next!() { '3' => match next!() { 'f' => fix!(Nanosecond3), @@ -462,7 +478,7 @@ impl<'a> Iterator for StrftimeItems<'a> { let nextspec = self .remainder .find(|c: char| !c.is_whitespace()) - .unwrap_or_else(|| self.remainder.len()); + .unwrap_or(self.remainder.len()); assert!(nextspec > 0); let item = sp!(&self.remainder[..nextspec]); self.remainder = &self.remainder[nextspec..]; @@ -474,7 +490,7 @@ impl<'a> Iterator for StrftimeItems<'a> { let nextspec = self .remainder .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or_else(|| self.remainder.len()); + .unwrap_or(self.remainder.len()); assert!(nextspec > 0); let item = lit!(&self.remainder[..nextspec]); self.remainder = &self.remainder[nextspec..]; @@ -487,11 +503,11 @@ impl<'a> Iterator for StrftimeItems<'a> { #[cfg(test)] #[test] fn test_strftime_items() { - fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> { + fn parse_and_collect(s: &str) -> Vec<Item<'_>> { // map any error into `[Item::Error]`. useful for easy testing. let items = StrftimeItems::new(s); let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); - items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error]) + items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error]) } assert_eq!(parse_and_collect(""), []); @@ -540,9 +556,18 @@ fn test_strftime_items() { #[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); + use crate::NaiveDate; + use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; + + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); // date specifiers assert_eq!(dt.format("%Y").to_string(), "2001"); @@ -559,7 +584,7 @@ fn test_strftime_docs() { 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("%U").to_string(), "27"); assert_eq!(dt.format("%W").to_string(), "27"); assert_eq!(dt.format("%G").to_string(), "2001"); assert_eq!(dt.format("%g").to_string(), "01"); @@ -599,10 +624,30 @@ fn test_strftime_docs() { //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"); + assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); + assert_eq!(dt.format("%:::z").to_string(), "+09"); // 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_timezone(&Utc).format("%+").to_string(), + "2001-07-07T15:04:60.026490708+00:00" + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() + ); + assert_eq!( + dt.with_timezone(&Utc), + DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() + ); + assert_eq!( dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), "2001-07-08T00:34:60.026490+09:30" @@ -618,9 +663,14 @@ fn test_strftime_docs() { #[cfg(feature = "unstable-locales")] #[test] fn test_strftime_docs_localized() { - use {FixedOffset, TimeZone}; + use crate::{FixedOffset, NaiveDate, TimeZone}; - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); + let dt = FixedOffset::east_opt(34200).unwrap().ymd_opt(2001, 7, 8).unwrap().and_hms_nano( + 0, + 34, + 59, + 1_026_490_708, + ); // date specifiers assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); @@ -646,4 +696,17 @@ fn test_strftime_docs_localized() { dt.format_localized("%c", Locale::fr_BE).to_string(), "dim 08 jui 2001 00:34:60 +09:30" ); + + let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); + + // date specifiers + assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); + assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); + assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); + assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); + assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); + assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); + assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); } |