diff --git a/gix-date/src/parse.rs b/gix-date/src/parse.rs deleted file mode 100644 index 7f71c95180c..00000000000 --- a/gix-date/src/parse.rs +++ /dev/null @@ -1,439 +0,0 @@ -use std::str::FromStr; - -use smallvec::SmallVec; - -use crate::Time; - -#[derive(thiserror::Error, Debug, Clone)] -#[allow(missing_docs)] -pub enum Error { - #[error("Could not convert a duration into a date")] - RelativeTimeConversion, - #[error("Date string can not be parsed")] - InvalidDateString { input: String }, - #[error("The heat-death of the universe happens before this date")] - InvalidDate(#[from] std::num::TryFromIntError), - #[error("Current time is missing but required to handle relative dates.")] - MissingCurrentTime, -} - -/// A container for just enough bytes to hold the largest-possible [`time`](Time) instance. -/// It's used in conjunction with -#[derive(Default, Clone)] -pub struct TimeBuf { - buf: SmallVec<[u8; Time::MAX.size()]>, -} - -impl TimeBuf { - /// Represent this instance as standard string, serialized in a format compatible with - /// signature fields in Git commits, also known as anything parseable as [raw format](function::parse_header()). - pub fn as_str(&self) -> &str { - // SAFETY: We know that serialized times are pure ASCII, a subset of UTF-8. - // `buf` and `len` are written only by time-serialization code. - let time_bytes = self.buf.as_slice(); - #[allow(unsafe_code)] - unsafe { - std::str::from_utf8_unchecked(time_bytes) - } - } - - /// Clear the previous content. - fn clear(&mut self) { - self.buf.clear(); - } -} - -impl std::io::Write for TimeBuf { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.buf.write(buf) - } - - fn flush(&mut self) -> std::io::Result<()> { - self.buf.flush() - } -} - -impl Time { - /// Serialize this instance into `buf`, exactly as it would appear in the header of a Git commit, - /// and return `buf` as `&str` for easy consumption. - pub fn to_str<'a>(&self, buf: &'a mut TimeBuf) -> &'a str { - buf.clear(); - self.write_to(buf) - .expect("write to memory of just the right size cannot fail"); - buf.as_str() - } -} - -impl FromStr for Time { - type Err = Error; - - fn from_str(s: &str) -> Result { - crate::parse_header(s).ok_or_else(|| Error::InvalidDateString { input: s.into() }) - } -} - -pub(crate) mod function { - use std::{str::FromStr, time::SystemTime}; - - use jiff::{civil::Date, fmt::rfc2822, tz::TimeZone, Zoned}; - - use crate::{ - parse::{relative, Error}, - time::format::{DEFAULT, GITOXIDE, ISO8601, ISO8601_STRICT, SHORT}, - OffsetInSeconds, SecondsSinceUnixEpoch, Time, - }; - - /// Parse `input` as any time that Git can parse when inputting a date. - /// - /// ## Examples - /// - /// ### 1. SHORT Format - /// - /// * `2018-12-24` - /// * `1970-01-01` - /// * `1950-12-31` - /// * `2024-12-31` - /// - /// ### 2. RFC2822 Format - /// - /// * `Thu, 18 Aug 2022 12:45:06 +0800` - /// * `Mon Oct 27 10:30:00 2023 -0800` - /// - /// ### 3. GIT_RFC2822 Format - /// - /// * `Thu, 8 Aug 2022 12:45:06 +0800` - /// * `Mon Oct 27 10:30:00 2023 -0800` (Note the single-digit day) - /// - /// ### 4. ISO8601 Format - /// - /// * `2022-08-17 22:04:58 +0200` - /// * `1970-01-01 00:00:00 -0500` - /// - /// ### 5. ISO8601_STRICT Format - /// - /// * `2022-08-17T21:43:13+08:00` - /// - /// ### 6. UNIX Timestamp (Seconds Since Epoch) - /// - /// * `123456789` - /// * `0` (January 1, 1970 UTC) - /// * `-1000` - /// * `1700000000` - /// - /// ### 7. Commit Header Format - /// - /// * `1745582210 +0200` - /// * `1660874655 +0800` - /// * `-1660874655 +0800` - /// - /// See also the [`parse_header()`]. - /// - /// ### 8. GITOXIDE Format - /// - /// * `Thu Sep 04 2022 10:45:06 -0400` - /// * `Mon Oct 27 2023 10:30:00 +0000` - /// - /// ### 9. DEFAULT Format - /// - /// * `Thu Sep 4 10:45:06 2022 -0400` - /// * `Mon Oct 27 10:30:00 2023 +0000` - /// - /// ### 10. Relative Dates (e.g., "2 minutes ago", "1 hour from now") - /// - /// These dates are parsed *relative to a `now` timestamp*. The examples depend entirely on the value of `now`. - /// If `now` is October 27, 2023 at 10:00:00 UTC: - /// * `2 minutes ago` (October 27, 2023 at 09:58:00 UTC) - /// * `3 hours ago` (October 27, 2023 at 07:00:00 UTC) - pub fn parse(input: &str, now: Option) -> Result { - Ok(if let Ok(val) = Date::strptime(SHORT.0, input) { - let val = val - .to_zoned(TimeZone::UTC) - .map_err(|_| Error::InvalidDateString { input: input.into() })?; - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Ok(val) = rfc2822_relaxed(input) { - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Ok(val) = strptime_relaxed(ISO8601.0, input) { - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Ok(val) = strptime_relaxed(ISO8601_STRICT.0, input) { - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Ok(val) = strptime_relaxed(GITOXIDE.0, input) { - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Ok(val) = strptime_relaxed(DEFAULT.0, input) { - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Ok(val) = SecondsSinceUnixEpoch::from_str(input) { - // Format::Unix - Time::new(val, 0) - } else if let Some(val) = relative::parse(input, now).transpose()? { - Time::new(val.timestamp().as_second(), val.offset().seconds()) - } else if let Some(val) = parse_raw(input) { - // Format::Raw - val - } else { - return Err(Error::InvalidDateString { input: input.into() }); - }) - } - - /// Unlike [`parse()`] which handles all kinds of input, this function only parses the commit-header format - /// like `1745582210 +0200`. - /// - /// Note that failure to parse the time zone isn't fatal, instead it will default to `0`. To know if - /// the time is wonky, serialize the return value to see if it matches the `input.` - pub fn parse_header(input: &str) -> Option