diff --git a/src/date.rs b/src/date.rs index ea9a9f4bd0..3c63b3314b 100644 --- a/src/date.rs +++ b/src/date.rs @@ -21,6 +21,7 @@ use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; use crate::time_delta::TimeDelta; use crate::DateTime; +use crate::ParseError; use crate::{Datelike, Weekday}; /// ISO 8601 calendar date with time zone. @@ -329,8 +330,8 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) + pub fn format<'a>(&self, fmt: &'a str) -> Result>, ParseError> { + Ok(self.format_with_items(StrftimeItems::new(fmt)?)) } /// Formats the date with the specified formatting items and locale. @@ -365,8 +366,8 @@ where &self, fmt: &'a str, locale: Locale, - ) -> DelayedFormat> { - self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + ) -> Result>, ParseError> { + Ok(self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale)?, locale)) } } diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 2c1aaf34a3..a888178fcb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -578,7 +578,7 @@ impl DateTime { /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parse(&mut parsed, s, StrftimeItems::new(fmt)?)?; parsed.to_datetime() } } @@ -753,14 +753,14 @@ where /// use chrono::prelude::*; /// /// let date_time: DateTime = Utc.with_ymd_and_hms(2017, 04, 02, 12, 50, 32).unwrap(); - /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); + /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M").unwrap()); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) + pub fn format<'a>(&self, fmt: &'a str) -> Result>, ParseError> { + Ok(self.format_with_items(StrftimeItems::new(fmt)?)) } /// Formats the combined date and time with the specified formatting items and locale. @@ -798,8 +798,8 @@ where &self, fmt: &'a str, locale: Locale, - ) -> DelayedFormat> { - self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) + ) -> Result>, ParseError> { + Ok(self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale)?, locale)) } } @@ -1331,3 +1331,8 @@ fn test_decodable_json( assert!(utc_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); assert!(fixed_from_str(r#""2014-07-32T12:34:06Z""#).is_err()); } + +#[test] +fn test_invalid_format() { + assert!(Utc::now().format("%").is_err()); +} diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 37f42c23d1..042857c153 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -388,7 +388,10 @@ fn test_to_string_round_trip_with_local() { fn test_datetime_format_with_local() { // if we are not around the year boundary, local and UTC date should have the same year let dt = Local::now().with_month(5).unwrap(); - assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&Utc).format("%Y").to_string()); + assert_eq!( + dt.format("%Y").unwrap().to_string(), + dt.with_timezone(&Utc).format("%Y").unwrap().to_string() + ); } #[test] @@ -579,25 +582,25 @@ fn test_datetime_format_alignment() { let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap(); // Item::Literal - let percent = datetime.format("%%"); + let percent = datetime.format("%%").unwrap(); assert_eq!(" %", format!("{:>3}", percent)); assert_eq!("% ", format!("{:<3}", percent)); assert_eq!(" % ", format!("{:^3}", percent)); // Item::Numeric - let year = datetime.format("%Y"); + let year = datetime.format("%Y").unwrap(); assert_eq!(" 2007", format!("{:>6}", year)); assert_eq!("2007 ", format!("{:<6}", year)); assert_eq!(" 2007 ", format!("{:^6}", year)); // Item::Fixed - let tz = datetime.format("%Z"); + let tz = datetime.format("%Z").unwrap(); assert_eq!(" UTC", format!("{:>5}", tz)); assert_eq!("UTC ", format!("{:<5}", tz)); assert_eq!(" UTC ", format!("{:^5}", tz)); // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] - let ymd = datetime.format("%Y %B %d"); + let ymd = datetime.format("%Y %B %d").unwrap(); let ymd_formatted = "2007 January 02"; assert_eq!(format!(" {}", ymd_formatted), format!("{:>17}", ymd)); assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd)); diff --git a/src/format/mod.rs b/src/format/mod.rs index 6b95e6172e..e9db2e8479 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -22,7 +22,7 @@ //! //! 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")); +//! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S").unwrap()); //! assert_eq!(formatted, "2020-11-10 00:01:32"); //! //! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?; diff --git a/src/format/parse.rs b/src/format/parse.rs index 69204d2e95..a5c886a103 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -895,7 +895,7 @@ fn parse_rfc850() { 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); + assert_eq!(dt.format(RFC850_FMT).unwrap().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)); diff --git a/src/format/strftime.rs b/src/format/strftime.rs index f57ca46175..8ab67d996b 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -174,6 +174,8 @@ Notes: China Daylight Time. */ +use crate::format::{ParseError, BAD_FORMAT}; + #[cfg(feature = "unstable-locales")] extern crate alloc; @@ -184,11 +186,6 @@ use alloc::vec::Vec; 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: &[Item<'static>] = &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; static D_T_FMT: &[Item<'static>] = &[ @@ -211,306 +208,263 @@ static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(" /// 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>, + //The format items + items: Vec>, } 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")] - #[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(); + pub fn new(s: &'a str) -> Result, ParseError> { + let items = Self::parse_format(s, D_FMT.to_vec(), T_FMT.to_vec(), D_T_FMT.to_vec())?; - StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt } + Ok(StrftimeItems { items }) } - #[cfg(not(feature = "unstable-locales"))] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &[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: &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..]; + fn pad(item: Item, pad_override: Option) -> Result { + if let Some(new_pad) = pad_override { + match item { + Item::Numeric(ref kind, _pad) => Ok(Item::Numeric(kind.clone(), new_pad)), + _ => Err(BAD_FORMAT), } - return Some(item); + } else { + Ok(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 + fn parse_format( + s: &'a str, + d_fmt: Vec>, + t_fmt: Vec>, + d_t_fmt: Vec>, + ) -> Result>, ParseError> { + let mut items: Vec = Vec::new(); + + let mut remainder = s; + + while !remainder.is_empty() { + let char = remainder.chars().next(); + match char { + Some('%') => { + remainder = &remainder[1..]; + + macro_rules! next { + () => { + match remainder.chars().next() { + Some(x) => { + remainder = &remainder[x.len_utf8()..]; + x + } + None => return Err(BAD_FORMAT), } - 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 - }) - } + 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 Err(BAD_FORMAT); + } - macro_rules! recons_from_slice { - ($slice:expr) => {{ - #[cfg(feature = "unstable-locales")] - { - self.recons.clear(); - self.recons.extend_from_slice(&$slice[1..]); + match spec { + 'A' => items.push(fix!(LongWeekdayName)), + 'B' => items.push(fix!(LongMonthName)), + 'C' => items.push(Self::pad(num0!(YearDiv100), pad_override)?), + 'D' => { + items.extend_from_slice(&[ + num0!(Month), + lit!("/"), + num0!(Day), + lit!("/"), + num0!(YearMod100), + ]); } - #[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) + 'F' => items.extend_from_slice(&[ + num0!(Year), + lit!("-"), + num0!(Month), + lit!("-"), + num0!(Day), + ]), + 'G' => items.push(Self::pad(num0!(IsoYear), pad_override)?), + 'H' => items.push(Self::pad(num0!(Hour), pad_override)?), + 'I' => items.push(Self::pad(num0!(Hour12), pad_override)?), + 'M' => items.push(Self::pad(num0!(Minute), pad_override)?), + 'P' => items.push(fix!(LowerAmPm)), + 'R' => items.extend_from_slice(&[num0!(Hour), lit!(":"), num0!(Minute)]), + 'S' => items.push(Self::pad(num0!(Second), pad_override)?), + 'T' => items.extend_from_slice(&[ + num0!(Hour), + lit!(":"), + num0!(Minute), + lit!(":"), + num0!(Second), + ]), + 'U' => items.push(Self::pad(num0!(WeekFromSun), pad_override)?), + 'V' => items.push(Self::pad(num0!(IsoWeek), pad_override)?), + 'W' => items.push(Self::pad(num0!(WeekFromMon), pad_override)?), + 'X' => items.extend_from_slice(&t_fmt), + 'Y' => items.push(Self::pad(num0!(Year), pad_override)?), + 'Z' => items.push(fix!(TimezoneName)), + 'a' => items.push(fix!(ShortWeekdayName)), + 'b' | 'h' => items.push(fix!(ShortMonthName)), + 'c' => items.extend_from_slice(&d_t_fmt), + 'd' => items.push(Self::pad(num0!(Day), pad_override)?), + 'e' => items.push(Self::pad(nums!(Day), pad_override)?), + 'f' => items.push(Self::pad(num0!(Nanosecond), pad_override)?), + 'g' => items.push(Self::pad(num0!(IsoYearMod100), pad_override)?), + 'j' => items.push(Self::pad(num0!(Ordinal), pad_override)?), + 'k' => items.push(Self::pad(nums!(Hour), pad_override)?), + 'l' => items.push(Self::pad(nums!(Hour12), pad_override)?), + 'm' => items.push(Self::pad(num0!(Month), pad_override)?), + 'n' => items.push(sp!("\n")), + 'p' => items.push(fix!(UpperAmPm)), + 'r' => items.extend_from_slice(&[ + num0!(Hour12), + lit!(":"), + num0!(Minute), + lit!(":"), + num0!(Second), + sp!(" "), + fix!(UpperAmPm), + ]), + 's' => items.push(Self::pad(num!(Timestamp), pad_override)?), + 't' => items.push(sp!("\t")), + 'u' => items.push(Self::pad(num!(WeekdayFromMon), pad_override)?), + 'v' => items.extend_from_slice(&[ + nums!(Day), + lit!("-"), + fix!(ShortMonthName), + lit!("-"), + num0!(Year), + ]), + 'w' => items.push(Self::pad(num!(NumDaysFromSun), pad_override)?), + 'x' => items.extend_from_slice(&d_fmt), + 'y' => items.push(Self::pad(num0!(YearMod100), pad_override)?), + 'z' => { + if is_alternate { + items.push(internal_fix!(TimezoneOffsetPermissive)) + } else { + items.push(fix!(TimezoneOffset)) + } } - } - '+' => fix!(RFC3339), - ':' => { - 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 + '+' => items.push(fix!(RFC3339)), + ':' => { + if remainder.starts_with("::z") { + remainder = &remainder[3..]; + items.push(fix!(TimezoneOffsetTripleColon)) + } else if remainder.starts_with(":z") { + remainder = &remainder[2..]; + items.push(fix!(TimezoneOffsetDoubleColon)) + } else if remainder.starts_with('z') { + remainder = &remainder[1..]; + items.push(fix!(TimezoneOffsetColon)) + } else { + return Err(BAD_FORMAT); + } } - } - '.' => match next!() { + '.' => match next!() { + '3' => match next!() { + 'f' => items.push(fix!(Nanosecond3)), + _ => return Err(BAD_FORMAT), + }, + '6' => match next!() { + 'f' => items.push(fix!(Nanosecond6)), + _ => return Err(BAD_FORMAT), + }, + '9' => match next!() { + 'f' => items.push(fix!(Nanosecond9)), + _ => return Err(BAD_FORMAT), + }, + 'f' => items.push(fix!(Nanosecond)), + _ => return Err(BAD_FORMAT), + }, '3' => match next!() { - 'f' => fix!(Nanosecond3), - _ => Item::Error, + 'f' => items.push(internal_fix!(Nanosecond3NoDot)), + _ => return Err(BAD_FORMAT), }, '6' => match next!() { - 'f' => fix!(Nanosecond6), - _ => Item::Error, + 'f' => items.push(internal_fix!(Nanosecond6NoDot)), + _ => return Err(BAD_FORMAT), }, '9' => match next!() { - 'f' => fix!(Nanosecond9), - _ => Item::Error, + 'f' => items.push(internal_fix!(Nanosecond9NoDot)), + _ => return Err(BAD_FORMAT), }, - '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) + '%' => items.push(lit!("%")), + _ => return Err(BAD_FORMAT), + }; } - } - // 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(self.remainder.len()); - assert!(nextspec > 0); - let item = sp!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) - } + // the next item is space + Some(c) if c.is_whitespace() => { + // `%` is not a whitespace, so `c != '%'` is redundant + let nextspec = + remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); + assert!(nextspec > 0); + items.push(sp!(&remainder[..nextspec])); + remainder = &remainder[nextspec..]; + } - // the next item is literal - _ => { - let nextspec = self - .remainder - .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or(self.remainder.len()); - assert!(nextspec > 0); - let item = lit!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + // the next item is literal + _ => { + let nextspec = remainder + .find(|c: char| c.is_whitespace() || c == '%') + .unwrap_or(remainder.len()); + assert!(nextspec > 0); + items.push(lit!(&remainder[..nextspec])); + remainder = &remainder[nextspec..]; + } } } + + //reverse items so they are consumed in the correct order + items.reverse(); + Ok(items) + } + + /// 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) -> Result, ParseError> { + 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(); + + let items = Self::parse_format(s, d_fmt, t_fmt, d_t_fmt)?; + + Ok(StrftimeItems { items }) } } +const HAVE_ALTERNATES: &str = "z"; + +impl<'a> Iterator for StrftimeItems<'a> { + type Item = Item<'a>; + + fn next(&mut self) -> Option> { + self.items.pop() + } +} +/* #[cfg(test)] #[test] fn test_strftime_items() { - fn parse_and_collect(s: &str) -> Vec> { + use core::assert_matches; + + fn parse_and_collect(s: &str) -> Result>, String> { // 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_else(|| vec![Item::Error]) + let items = StrftimeItems::new(s)?; + Ok(items.into_iter().collect::>()) } - assert_eq!(parse_and_collect(""), []); + assert_matches!(parse_and_collect(""), Ok([])); assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); assert_eq!( @@ -551,7 +505,7 @@ fn test_strftime_items() { 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] @@ -570,69 +524,72 @@ fn test_strftime_docs() { .unwrap(); // 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"); + assert_eq!(dt.format("%Y").unwrap().to_string(), "2001"); + assert_eq!(dt.format("%C").unwrap().to_string(), "20"); + assert_eq!(dt.format("%y").unwrap().to_string(), "01"); + assert_eq!(dt.format("%m").unwrap().to_string(), "07"); + assert_eq!(dt.format("%b").unwrap().to_string(), "Jul"); + assert_eq!(dt.format("%B").unwrap().to_string(), "July"); + assert_eq!(dt.format("%h").unwrap().to_string(), "Jul"); + assert_eq!(dt.format("%d").unwrap().to_string(), "08"); + assert_eq!(dt.format("%e").unwrap().to_string(), " 8"); + assert_eq!(dt.format("%e").unwrap().to_string(), dt.format("%_d").unwrap().to_string()); + assert_eq!(dt.format("%a").unwrap().to_string(), "Sun"); + assert_eq!(dt.format("%A").unwrap().to_string(), "Sunday"); + assert_eq!(dt.format("%w").unwrap().to_string(), "0"); + assert_eq!(dt.format("%u").unwrap().to_string(), "7"); + assert_eq!(dt.format("%U").unwrap().to_string(), "28"); + assert_eq!(dt.format("%W").unwrap().to_string(), "27"); + assert_eq!(dt.format("%G").unwrap().to_string(), "2001"); + assert_eq!(dt.format("%g").unwrap().to_string(), "01"); + assert_eq!(dt.format("%V").unwrap().to_string(), "27"); + assert_eq!(dt.format("%j").unwrap().to_string(), "189"); + assert_eq!(dt.format("%D").unwrap().to_string(), "07/08/01"); + assert_eq!(dt.format("%x").unwrap().to_string(), "07/08/01"); + assert_eq!(dt.format("%F").unwrap().to_string(), "2001-07-08"); + assert_eq!(dt.format("%v").unwrap().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"); + assert_eq!(dt.format("%H").unwrap().to_string(), "00"); + assert_eq!(dt.format("%k").unwrap().to_string(), " 0"); + assert_eq!(dt.format("%k").unwrap().to_string(), dt.format("%_H").unwrap().to_string()); + assert_eq!(dt.format("%I").unwrap().to_string(), "12"); + assert_eq!(dt.format("%l").unwrap().to_string(), "12"); + assert_eq!(dt.format("%l").unwrap().to_string(), dt.format("%_I").unwrap().to_string()); + assert_eq!(dt.format("%P").unwrap().to_string(), "am"); + assert_eq!(dt.format("%p").unwrap().to_string(), "AM"); + assert_eq!(dt.format("%M").unwrap().to_string(), "34"); + assert_eq!(dt.format("%S").unwrap().to_string(), "60"); + assert_eq!(dt.format("%f").unwrap().to_string(), "026490708"); + assert_eq!(dt.format("%.f").unwrap().to_string(), ".026490708"); + assert_eq!( + dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").unwrap().to_string(), + ".026490" + ); + assert_eq!(dt.format("%.3f").unwrap().to_string(), ".026"); + assert_eq!(dt.format("%.6f").unwrap().to_string(), ".026490"); + assert_eq!(dt.format("%.9f").unwrap().to_string(), ".026490708"); + assert_eq!(dt.format("%3f").unwrap().to_string(), "026"); + assert_eq!(dt.format("%6f").unwrap().to_string(), "026490"); + assert_eq!(dt.format("%9f").unwrap().to_string(), "026490708"); + assert_eq!(dt.format("%R").unwrap().to_string(), "00:34"); + assert_eq!(dt.format("%T").unwrap().to_string(), "00:34:60"); + assert_eq!(dt.format("%X").unwrap().to_string(), "00:34:60"); + assert_eq!(dt.format("%r").unwrap().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"); - assert_eq!(dt.format("%::z").to_string(), "+09:30:00"); - assert_eq!(dt.format("%:::z").to_string(), "+09"); + //assert_eq!(dt.format("%Z").unwrap().to_string(), "ACST"); + assert_eq!(dt.format("%z").unwrap().to_string(), "+0930"); + assert_eq!(dt.format("%:z").unwrap().to_string(), "+09:30"); + assert_eq!(dt.format("%::z").unwrap().to_string(), "+09:30:00"); + assert_eq!(dt.format("%:::z").unwrap().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.format("%c").unwrap().to_string(), "Sun Jul 8 00:34:60 2001"); + assert_eq!(dt.format("%+").unwrap().to_string(), "2001-07-08T00:34:60.026490708+09:30"); assert_eq!( - dt.with_timezone(&Utc).format("%+").to_string(), + dt.with_timezone(&Utc).format("%+").unwrap().to_string(), "2001-07-07T15:04:60.026490708+00:00" ); assert_eq!( @@ -649,15 +606,15 @@ fn test_strftime_docs() { ); assert_eq!( - dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), + dt.with_nanosecond(1_026_490_000).unwrap().format("%+").unwrap().to_string(), "2001-07-08T00:34:60.026490+09:30" ); - assert_eq!(dt.format("%s").to_string(), "994518299"); + assert_eq!(dt.format("%s").unwrap().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(), "%"); + assert_eq!(dt.format("%t").unwrap().to_string(), "\t"); + assert_eq!(dt.format("%n").unwrap().to_string(), "\n"); + assert_eq!(dt.format("%%").unwrap().to_string(), "%"); } #[cfg(feature = "unstable-locales")] @@ -672,27 +629,27 @@ fn test_strftime_docs_localized() { .unwrap(); // 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"); + assert_eq!(dt.format_localized("%b", Locale::fr_BE).unwrap().to_string(), "jui"); + assert_eq!(dt.format_localized("%B", Locale::fr_BE).unwrap().to_string(), "juillet"); + assert_eq!(dt.format_localized("%h", Locale::fr_BE).unwrap().to_string(), "jui"); + assert_eq!(dt.format_localized("%a", Locale::fr_BE).unwrap().to_string(), "dim"); + assert_eq!(dt.format_localized("%A", Locale::fr_BE).unwrap().to_string(), "dimanche"); + assert_eq!(dt.format_localized("%D", Locale::fr_BE).unwrap().to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::fr_BE).unwrap().to_string(), "08/07/01"); + assert_eq!(dt.format_localized("%F", Locale::fr_BE).unwrap().to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::fr_BE).unwrap().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 "); + assert_eq!(dt.format_localized("%P", Locale::fr_BE).unwrap().to_string(), ""); + assert_eq!(dt.format_localized("%p", Locale::fr_BE).unwrap().to_string(), ""); + assert_eq!(dt.format_localized("%R", Locale::fr_BE).unwrap().to_string(), "00:34"); + assert_eq!(dt.format_localized("%T", Locale::fr_BE).unwrap().to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%X", Locale::fr_BE).unwrap().to_string(), "00:34:60"); + assert_eq!(dt.format_localized("%r", Locale::fr_BE).unwrap().to_string(), "12:34:60 "); // date & time specifiers assert_eq!( - dt.format_localized("%c", Locale::fr_BE).to_string(), + dt.format_localized("%c", Locale::fr_BE).unwrap().to_string(), "dim 08 jui 2001 00:34:60 +09:30" ); } diff --git a/src/lib.rs b/src/lib.rs index 44303bde77..5a56919d55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,11 +208,11 @@ //! # #[cfg(feature = "unstable-locales")] //! # fn test() { //! let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(); -//! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); -//! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014"); -//! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(), "vendredi 28 novembre 2014, 12:00:09"); +//! assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").unwrap().to_string(), "2014-11-28 12:00:09"); +//! assert_eq!(dt.format("%a %b %e %T %Y").unwrap().to_string(), "Fri Nov 28 12:00:09 2014"); +//! assert_eq!(dt.format_localized("%A %e %B %Y, %T", Locale::fr_BE).unwrap().to_string(), "vendredi 28 novembre 2014, 12:00:09"); //! -//! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string()); +//! assert_eq!(dt.format("%a %b %e %T %Y").unwrap().to_string(), dt.format("%c").unwrap().to_string()); //! assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC"); //! assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000"); //! assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00"); @@ -329,7 +329,7 @@ //! //! assert_eq!(Utc.ymd_opt(2014, 11, 28).unwrap().weekday(), Weekday::Fri); //! assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None); -//! assert_eq!(NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().and_local_timezone(Utc).unwrap().format("%H%M%S").to_string(), +//! assert_eq!(NaiveDate::from_ymd_opt(2014, 11, 28).unwrap().and_hms_milli_opt(7, 8, 9, 10).unwrap().and_local_timezone(Utc).unwrap().format("%H%M%S").unwrap().to_string(), //! "070809"); //! ``` //! diff --git a/src/naive/date.rs b/src/naive/date.rs index eb6287f971..0b8b8908a8 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -521,7 +521,7 @@ impl NaiveDate { /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parse(&mut parsed, s, StrftimeItems::new(fmt)?)?; parsed.to_naive_date() } @@ -1077,10 +1077,10 @@ impl NaiveDate { /// use chrono::NaiveDate; /// use chrono::format::strftime::StrftimeItems; /// - /// let fmt = StrftimeItems::new("%Y-%m-%d"); + /// let fmt = StrftimeItems::new("%Y-%m-%d").unwrap(); /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(d.format_with_items(fmt.clone()).to_string(), "2015-09-05"); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); + /// assert_eq!(d.format("%Y-%m-%d").unwrap().to_string(), "2015-09-05"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -1088,7 +1088,7 @@ impl NaiveDate { /// ``` /// # use chrono::NaiveDate; /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%Y-%m-%d").clone(); + /// # let fmt = StrftimeItems::new("%Y-%m-%d").unwrap().clone(); /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` @@ -1123,8 +1123,8 @@ impl NaiveDate { /// use chrono::NaiveDate; /// /// let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05"); - /// assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015"); + /// assert_eq!(d.format("%Y-%m-%d").unwrap().to_string(), "2015-09-05"); + /// assert_eq!(d.format("%A, %-d %B, %C%y").unwrap().to_string(), "Saturday, 5 September, 2015"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -1132,14 +1132,14 @@ impl NaiveDate { /// ``` /// # use chrono::NaiveDate; /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); - /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); - /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); + /// assert_eq!(format!("{}", d.format("%Y-%m-%d").unwrap()), "2015-09-05"); + /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y").unwrap()), "Saturday, 5 September, 2015"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) + pub fn format<'a>(&self, fmt: &'a str) -> Result>, ParseError> { + Ok(self.format_with_items(StrftimeItems::new(fmt)?)) } /// Returns an iterator that steps by days across all representable dates. @@ -2754,43 +2754,78 @@ mod tests { #[test] fn test_date_format() { let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); - assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); - assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March"); - assert_eq!(d.format("%d,%e").to_string(), "04, 4"); - assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09"); - assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7"); - assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year - assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12"); - assert_eq!(d.format("%F").to_string(), "2012-03-04"); - assert_eq!(d.format("%v").to_string(), " 4-Mar-2012"); - assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + assert_eq!(d.format("%Y,%C,%y,%G,%g").unwrap().to_string(), "2012,20,12,2012,12"); + assert_eq!(d.format("%m,%b,%h,%B").unwrap().to_string(), "03,Mar,Mar,March"); + assert_eq!(d.format("%d,%e").unwrap().to_string(), "04, 4"); + assert_eq!(d.format("%U,%W,%V").unwrap().to_string(), "10,09,09"); + assert_eq!(d.format("%a,%A,%w,%u").unwrap().to_string(), "Sun,Sunday,0,7"); + assert_eq!(d.format("%j").unwrap().to_string(), "064"); // since 2012 is a leap year + assert_eq!(d.format("%D,%x").unwrap().to_string(), "03/04/12,03/04/12"); + assert_eq!(d.format("%F").unwrap().to_string(), "2012-03-04"); + assert_eq!(d.format("%v").unwrap().to_string(), " 4-Mar-2012"); + assert_eq!(d.format("%t%n%%%n%t").unwrap().to_string(), "\t\n%\n\t"); // non-four-digit years assert_eq!( - NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(), + NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").unwrap().to_string(), "+12345" ); - assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234"); - assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123"); - assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012"); - assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001"); - assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000"); - assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001"); - assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012"); - assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123"); - assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234"); assert_eq!( - NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(), + NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "1234" + ); + assert_eq!( + NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "0123" + ); + assert_eq!( + NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "0012" + ); + assert_eq!( + NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "0001" + ); + assert_eq!( + NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "0000" + ); + assert_eq!( + NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "-0001" + ); + assert_eq!( + NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "-0012" + ); + assert_eq!( + NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "-0123" + ); + assert_eq!( + NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").unwrap().to_string(), + "-1234" + ); + assert_eq!( + NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").unwrap().to_string(), "-12345" ); // corner cases assert_eq!( - NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(), + NaiveDate::from_ymd_opt(2007, 12, 31) + .unwrap() + .format("%G,%g,%U,%W,%V") + .unwrap() + .to_string(), "2008,08,53,53,01" ); assert_eq!( - NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(), + NaiveDate::from_ymd_opt(2010, 1, 3) + .unwrap() + .format("%G,%g,%U,%W,%V") + .unwrap() + .to_string(), "2009,09,01,00,53" ); } diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index a8a5bf3cf1..faf869d8f9 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -262,7 +262,7 @@ impl NaiveDateTime { /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parse(&mut parsed, s, StrftimeItems::new(fmt)?)?; parsed.to_naive_datetime_with_offset(0) // no offset adjustment } @@ -759,10 +759,10 @@ impl NaiveDateTime { /// use chrono::NaiveDate; /// use chrono::format::strftime::StrftimeItems; /// - /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S"); + /// let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").unwrap(); /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(dt.format_with_items(fmt.clone()).to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").unwrap().to_string(), "2015-09-05 23:56:04"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -770,7 +770,7 @@ impl NaiveDateTime { /// ``` /// # use chrono::NaiveDate; /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").clone(); + /// # let fmt = StrftimeItems::new("%Y-%m-%d %H:%M:%S").unwrap().clone(); /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` @@ -805,8 +805,8 @@ impl NaiveDateTime { /// use chrono::NaiveDate; /// /// let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); - /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04"); - /// assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5"); + /// assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").unwrap().to_string(), "2015-09-05 23:56:04"); + /// assert_eq!(dt.format("around %l %p on %b %-d").unwrap().to_string(), "around 11 PM on Sep 5"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -814,14 +814,14 @@ impl NaiveDateTime { /// ``` /// # use chrono::NaiveDate; /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); - /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); - /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); + /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S").unwrap()), "2015-09-05 23:56:04"); + /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d").unwrap()), "around 11 PM on Sep 5"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) + pub fn format<'a>(&self, fmt: &'a str) -> Result>, ParseError> { + Ok(self.format_with_items(StrftimeItems::new(fmt)?)) } /// Converts the `NaiveDateTime` into the timezone-aware `DateTime` diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 773e1f38c2..395c199d50 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -215,15 +215,15 @@ fn test_datetime_parse_from_str() { #[test] fn test_datetime_format() { let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); - assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); - assert_eq!(dt.format("%s").to_string(), "1283929614"); - assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + assert_eq!(dt.format("%c").unwrap().to_string(), "Wed Sep 8 07:06:54 2010"); + assert_eq!(dt.format("%s").unwrap().to_string(), "1283929614"); + assert_eq!(dt.format("%t%n%%%n%t").unwrap().to_string(), "\t\n%\n\t"); // a horror of leap second: coming near to you. let dt = NaiveDate::from_ymd_opt(2012, 6, 30).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap(); - assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012"); - assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional. + assert_eq!(dt.format("%c").unwrap().to_string(), "Sat Jun 30 23:59:60 2012"); + assert_eq!(dt.format("%s").unwrap().to_string(), "1341100799"); // not 1341100800, it's intentional. } #[test] diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 501b08c769..ab9229426c 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -157,11 +157,11 @@ mod tests { assert_eq!(minweek.year(), internals::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); - assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); + assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").unwrap().to_string()); assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); - assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); + assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").unwrap().to_string()); } } diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 21780ca3d7..503972c96b 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -465,7 +465,7 @@ impl NaiveTime { /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parse(&mut parsed, s, StrftimeItems::new(fmt)?)?; parsed.to_naive_time() } @@ -674,10 +674,10 @@ impl NaiveTime { /// use chrono::NaiveTime; /// use chrono::format::strftime::StrftimeItems; /// - /// let fmt = StrftimeItems::new("%H:%M:%S"); + /// let fmt = StrftimeItems::new("%H:%M:%S").unwrap(); /// let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(t.format_with_items(fmt.clone()).to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S").unwrap().to_string(), "23:56:04"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -685,7 +685,7 @@ impl NaiveTime { /// ``` /// # use chrono::NaiveTime; /// # use chrono::format::strftime::StrftimeItems; - /// # let fmt = StrftimeItems::new("%H:%M:%S").clone(); + /// # let fmt = StrftimeItems::new("%H:%M:%S").unwrap().clone(); /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` @@ -720,9 +720,9 @@ impl NaiveTime { /// use chrono::NaiveTime; /// /// let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(t.format("%H:%M:%S").to_string(), "23:56:04"); - /// assert_eq!(t.format("%H:%M:%S%.6f").to_string(), "23:56:04.012345"); - /// assert_eq!(t.format("%-I:%M %p").to_string(), "11:56 PM"); + /// assert_eq!(t.format("%H:%M:%S").unwrap().to_string(), "23:56:04"); + /// assert_eq!(t.format("%H:%M:%S%.6f").unwrap().to_string(), "23:56:04.012345"); + /// assert_eq!(t.format("%-I:%M %p").unwrap().to_string(), "11:56 PM"); /// ``` /// /// The resulting `DelayedFormat` can be formatted directly via the `Display` trait. @@ -730,15 +730,15 @@ impl NaiveTime { /// ``` /// # use chrono::NaiveTime; /// # let t = NaiveTime::from_hms_nano_opt(23, 56, 4, 12_345_678).unwrap(); - /// assert_eq!(format!("{}", t.format("%H:%M:%S")), "23:56:04"); - /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); - /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); + /// assert_eq!(format!("{}", t.format("%H:%M:%S").unwrap()), "23:56:04"); + /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f").unwrap()), "23:56:04.012345"); + /// assert_eq!(format!("{}", t.format("%-I:%M %p").unwrap()), "11:56 PM"); /// ``` #[cfg(any(feature = "alloc", feature = "std", test))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] - pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { - self.format_with_items(StrftimeItems::new(fmt)) + pub fn format<'a>(&self, fmt: &'a str) -> Result>, ParseError> { + Ok(self.format_with_items(StrftimeItems::new(fmt)?)) } /// Returns a triple of the hour, minute and second numbers. @@ -802,7 +802,7 @@ impl Timelike for NaiveTime { /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.second(), 59); - /// assert_eq!(leap.format("%H:%M:%S").to_string(), "23:59:60"); + /// assert_eq!(leap.format("%H:%M:%S").unwrap().to_string(), "23:59:60"); /// ``` #[inline] fn second(&self) -> u32 { @@ -830,7 +830,7 @@ impl Timelike for NaiveTime { /// # use chrono::{NaiveTime, Timelike}; /// let leap = NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap(); /// assert_eq!(leap.nanosecond(), 1_000_000_000); - /// assert_eq!(leap.format("%H:%M:%S%.9f").to_string(), "23:59:60.000000000"); + /// assert_eq!(leap.format("%H:%M:%S%.9f").unwrap().to_string(), "23:59:60.000000000"); /// ``` #[inline] fn nanosecond(&self) -> u32 { diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index da4d41fc78..81e6918f7a 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -287,31 +287,34 @@ fn test_time_parse_from_str() { #[test] fn test_time_format() { let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); - assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); - assert_eq!(t.format("%M").to_string(), "05"); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432"); - assert_eq!(t.format("%R").to_string(), "03:05"); - assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07"); - assert_eq!(t.format("%r").to_string(), "03:05:07 AM"); - assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t"); + assert_eq!(t.format("%H,%k,%I,%l,%P,%p").unwrap().to_string(), "03, 3,03, 3,am,AM"); + assert_eq!(t.format("%M").unwrap().to_string(), "05"); + assert_eq!(t.format("%S,%f,%.f").unwrap().to_string(), "07,098765432,.098765432"); + assert_eq!(t.format("%.3f,%.6f,%.9f").unwrap().to_string(), ".098,.098765,.098765432"); + assert_eq!(t.format("%R").unwrap().to_string(), "03:05"); + assert_eq!(t.format("%T,%X").unwrap().to_string(), "03:05:07,03:05:07"); + assert_eq!(t.format("%r").unwrap().to_string(), "03:05:07 AM"); + assert_eq!(t.format("%t%n%%%n%t").unwrap().to_string(), "\t\n%\n\t"); let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap(); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000"); + assert_eq!(t.format("%S,%f,%.f").unwrap().to_string(), "07,432100000,.432100"); + assert_eq!(t.format("%.3f,%.6f,%.9f").unwrap().to_string(), ".432,.432100,.432100000"); let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap(); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000"); + assert_eq!(t.format("%S,%f,%.f").unwrap().to_string(), "07,210000000,.210"); + assert_eq!(t.format("%.3f,%.6f,%.9f").unwrap().to_string(), ".210,.210000,.210000000"); let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap(); - assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,"); - assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000"); + assert_eq!(t.format("%S,%f,%.f").unwrap().to_string(), "07,000000000,"); + assert_eq!(t.format("%.3f,%.6f,%.9f").unwrap().to_string(), ".000,.000000,.000000000"); // corner cases - assert_eq!(NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(), "01:57:09 PM"); assert_eq!( - NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(), + NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").unwrap().to_string(), + "01:57:09 PM" + ); + assert_eq!( + NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").unwrap().to_string(), "23:59:60" ); } diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 09d0714e98..87d1e56802 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -435,7 +435,7 @@ pub trait TimeZone: Sized + Clone { /// parsed [`FixedOffset`]. fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { let mut parsed = Parsed::new(); - parse(&mut parsed, s, StrftimeItems::new(fmt))?; + parse(&mut parsed, s, StrftimeItems::new(fmt)?)?; parsed.to_datetime_with_timezone(self) }