diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1d0cc85094..185cb6f3ee 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -23,7 +23,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] use crate::format::Locale; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] @@ -623,6 +623,40 @@ impl DateTime { parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_datetime() } + + /// Parses a string from a user-specified format into a `DateTime` value, and a + /// slice with the remaining portion of the string. + /// + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_and_remainder`] for a version that does not + /// require a timezone in `s`. The returned [`DateTime`] value will have a [`FixedOffset`] + /// reflecting the parsed timezone. + /// + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{DateTime, FixedOffset, TimeZone, NaiveDate}; + /// let (datetime, remainder) = DateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 +0200 trailing text", "%Y-%m-%d %H:%M:%S %z").unwrap(); + /// assert_eq!( + /// datetime, + /// FixedOffset::east_opt(2*3600).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>( + s: &'a str, + fmt: &str, + ) -> ParseResult<(DateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_datetime().map(|d| (d, remainder)) + } } impl DateTime diff --git a/src/format/mod.rs b/src/format/mod.rs index bc10d4e913..baabdd6740 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -57,7 +57,7 @@ use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; #[cfg(feature = "unstable-locales")] pub(crate) mod locales; -pub use parse::parse; +pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; /// L10n locales. #[cfg(feature = "unstable-locales")] diff --git a/src/format/parse.rs b/src/format/parse.rs index c68d725447..4c79f4a5bc 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -252,6 +252,36 @@ where parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e) } +/// Tries to parse given string into `parsed` with given formatting items. +/// Returns `Ok` with a slice of the unparsed remainder. +/// +/// This particular date and time parser is: +/// +/// - Greedy. It will consume the longest possible prefix. +/// For example, `April` is always consumed entirely when the long month name is requested; +/// it equally accepts `Apr`, but prefers the longer prefix in this case. +/// +/// - Padding-agnostic (for numeric items). +/// The [`Pad`](./enum.Pad.html) field is completely ignored, +/// so one can prepend any number of zeroes before numbers. +/// +/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`. +pub fn parse_and_remainder<'a, 'b, I, B>( + parsed: &mut Parsed, + s: &'b str, + items: I, +) -> ParseResult<&'b str> +where + I: Iterator, + B: Borrow>, +{ + match parse_internal(parsed, s, items) { + Ok(s) => Ok(s), + Err((s, ParseError(ParseErrorKind::TooLong))) => Ok(s), + Err((_s, e)) => Err(e), + } +} + fn parse_internal<'a, 'b, I, B>( parsed: &mut Parsed, mut s: &'b str, diff --git a/src/naive/date.rs b/src/naive/date.rs index 4c7f383f7e..a174cae84a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -18,8 +18,10 @@ use pure_rust_locales::Locale; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; -use crate::format::{Item, Numeric, Pad}; +use crate::format::{ + parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult, + Parsed, StrftimeItems, +}; use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; @@ -546,6 +548,28 @@ impl NaiveDate { parsed.to_naive_date() } + /// Parses a string from a user-specified format into a new `NaiveDate` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](../format/strftime/index.html) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate}; + /// let (date, remainder) = NaiveDate::parse_and_remainder( + /// "2015-02-18 trailing text", "%Y-%m-%d").unwrap(); + /// assert_eq!(date, NaiveDate::from_ymd_opt(2015, 2, 18).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDate, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_date().map(|d| (d, remainder)) + } + /// Add a duration in [`Months`] to the date /// /// If the day would be out of range for the resulting month, use the last day for that month. diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 477f8d51a0..4a4ac8221e 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -15,7 +15,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; +use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime}; use crate::offset::Utc; @@ -300,6 +300,31 @@ impl NaiveDateTime { parsed.to_naive_datetime_with_offset(0) // no offset adjustment } + /// Parses a string with the specified format string and returns a new `NaiveDateTime`, and a + /// slice with the remaining portion of the string. + /// See the [`format::strftime` module](../format/strftime/index.html) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveDate, NaiveDateTime}; + /// let (datetime, remainder) = NaiveDateTime::parse_and_remainder( + /// "2015-02-18 23:16:09 trailing text", "%Y-%m-%d %H:%M:%S").unwrap(); + /// assert_eq!( + /// datetime, + /// NaiveDate::from_ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap() + /// ); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveDateTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_datetime_with_offset(0).map(|d| (d, remainder)) // no offset adjustment + } + /// Retrieves a date component. /// /// # Example diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index af0a632b47..880617e511 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -13,8 +13,10 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; -use crate::format::{parse, write_hundreds, ParseError, ParseResult, Parsed, StrftimeItems}; -use crate::format::{Fixed, Item, Numeric, Pad}; +use crate::format::{ + parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, + Parsed, StrftimeItems, +}; use crate::oldtime::Duration as OldDuration; use crate::Timelike; @@ -482,6 +484,28 @@ impl NaiveTime { parsed.to_naive_time() } + /// Parses a string from a user-specified format into a new `NaiveTime` value, and a slice with + /// the remaining portion of the string. + /// See the [`format::strftime` module](../format/strftime/index.html) + /// on the supported escape sequences. + /// + /// Similar to [`parse_from_str`](#method.parse_from_str). + /// + /// # Example + /// + /// ```rust + /// # use chrono::{NaiveTime}; + /// let (time, remainder) = NaiveTime::parse_and_remainder( + /// "3h4m33s trailing text", "%-Hh%-Mm%-Ss").unwrap(); + /// assert_eq!(time, NaiveTime::from_hms_opt(3, 4, 33).unwrap()); + /// assert_eq!(remainder, " trailing text"); + /// ``` + pub fn parse_and_remainder<'a>(s: &'a str, fmt: &str) -> ParseResult<(NaiveTime, &'a str)> { + let mut parsed = Parsed::new(); + let remainder = parse_and_remainder(&mut parsed, s, StrftimeItems::new(fmt))?; + parsed.to_naive_time().map(|t| (t, remainder)) + } + /// Adds given `Duration` to the current time, /// and also returns the number of *seconds* /// in the integral number of days ignored from the addition.