From c944b9c39650b16c7cc4b240b1dda6e813e7486d Mon Sep 17 00:00:00 2001
From: Paul Dicker <pitdicker@gmail.com>
Date: Wed, 5 Apr 2023 11:33:02 +0200
Subject: [PATCH] Add parse_and_remainder methods

---
 src/datetime/mod.rs       | 36 +++++++++++++++++++++++++++++++++++-
 src/format/mod.rs         |  2 +-
 src/format/parse.rs       | 30 ++++++++++++++++++++++++++++++
 src/naive/date.rs         | 28 ++++++++++++++++++++++++++--
 src/naive/datetime/mod.rs | 27 ++++++++++++++++++++++++++-
 src/naive/time/mod.rs     | 28 ++++++++++++++++++++++++++--
 6 files changed, 144 insertions(+), 7 deletions(-)

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<FixedOffset> {
         parse(&mut parsed, s, StrftimeItems::new(fmt))?;
         parsed.to_datetime()
     }
+
+    /// Parses a string from a user-specified format into a `DateTime<FixedOffset>` 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<FixedOffset>, &'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<Tz: TimeZone> DateTime<Tz>
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<Item = B>,
+    B: Borrow<Item<'a>>,
+{
+    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.