From e1338617a35991983f8f7cd06b7689e8ec8451e8 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Thu, 13 Jul 2023 12:27:29 -0500 Subject: [PATCH] Add a TimeConfigBuilder to avoid further breaking changes (#46) --- Cargo.toml | 2 +- README.md | 31 ++++++++++++++++++++ src/datetime.rs | 23 ++++++++------- src/duration.rs | 10 +++---- src/lib.rs | 2 +- src/time.rs | 55 +++++++++++++++++++++++++++++----- tests/main.rs | 78 ++++++++++++++++++++----------------------------- 7 files changed, 128 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f0097f..fd55d1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "speedate" authors = ["Samuel Colvin "] -version = "0.11.0" +version = "0.12.0" edition = "2021" description = "Fast and simple datetime, date, time and duration parsing" readme = "README.md" diff --git a/README.md b/README.md index 8cbd4b7..2177d0a 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,37 @@ fn main() { } ``` +To control the specifics of time parsing you can use provide a `TimeConfig`: + +```rust +use speedate::{DateTime, Date, Time, TimeConfig}; + +fn main() { + let dt = DateTime::parse_bytes_with_config( + "1689102037.5586429".as_bytes(), + &TimeConfig::builder().unix_timestamp_offset(Some(0)).build(), + ).unwrap(); + assert_eq!( + dt, + DateTime { + date: Date { + year: 2023, + month: 7, + day: 11, + }, + time: Time { + hour: 19, + minute: 0, + second: 37, + microsecond: 558643, + tz_offset: Some(0), + }, + } + ); + assert_eq!(dt.to_string(), "2023-07-11T19:00:37.558643Z"); +} +``` + ## Performance **speedate** is significantly faster than diff --git a/src/datetime.rs b/src/datetime.rs index dccac89..ab46210 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,5 +1,6 @@ use crate::numbers::{float_parse_bytes, IntFloat}; -use crate::{Date, ParseError, Time, TimeConfig}; +use crate::TimeConfigBuilder; +use crate::{time::TimeConfig, Date, ParseError, Time}; use std::cmp::Ordering; use std::fmt; use std::time::SystemTime; @@ -236,7 +237,7 @@ impl DateTime { /// assert_eq!(dt.to_string(), "2022-01-01T12:13:14Z"); /// ``` pub fn parse_bytes_rfc3339(bytes: &[u8]) -> Result { - DateTime::parse_bytes_rfc3339_with_config(bytes, &TimeConfig::default()) + DateTime::parse_bytes_rfc3339_with_config(bytes, &TimeConfigBuilder::new().build()) } /// Same as `parse_bytes_rfc3339` with with a `TimeConfig` parameter. @@ -249,9 +250,9 @@ impl DateTime { /// # Examples /// /// ``` - /// use speedate::{DateTime, Date, Time, TimeConfig}; + /// use speedate::{DateTime, Date, Time, TimeConfigBuilder}; /// - /// let dt = DateTime::parse_bytes_rfc3339_with_config(b"2022-01-01T12:13:14Z", &TimeConfig::default()).unwrap(); + /// let dt = DateTime::parse_bytes_rfc3339_with_config(b"2022-01-01T12:13:14Z", &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!( /// dt, /// DateTime { @@ -305,7 +306,7 @@ impl DateTime { /// assert_eq!(dt.to_string(), "2022-01-01T12:13:14"); /// ``` pub fn parse_bytes(bytes: &[u8]) -> Result { - DateTime::parse_bytes_with_config(bytes, &TimeConfig::default()) + DateTime::parse_bytes_with_config(bytes, &TimeConfigBuilder::new().build()) } /// Same as `DateTime::parse_bytes` but supporting TimeConfig @@ -318,9 +319,9 @@ impl DateTime { /// # Examples /// /// ``` - /// use speedate::{DateTime, Date, Time, TimeConfig}; + /// use speedate::{DateTime, Date, Time, TimeConfigBuilder}; /// - /// let dt = DateTime::parse_bytes_with_config(b"2022-01-01T12:13:14Z", &TimeConfig::default()).unwrap(); + /// let dt = DateTime::parse_bytes_with_config(b"2022-01-01T12:13:14Z", &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!(dt.to_string(), "2022-01-01T12:13:14Z"); /// ``` pub fn parse_bytes_with_config(bytes: &[u8], config: &TimeConfig) -> Result { @@ -364,12 +365,12 @@ impl DateTime { /// # Examples /// /// ``` - /// use speedate::{DateTime, TimeConfig}; + /// use speedate::{DateTime, TimeConfigBuilder}; /// - /// let d = DateTime::from_timestamp_with_config(1_654_619_320, 123, &TimeConfig::default()).unwrap(); + /// let d = DateTime::from_timestamp_with_config(1_654_619_320, 123, &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!(d.to_string(), "2022-06-07T16:28:40.000123"); /// - /// let d = DateTime::from_timestamp_with_config(1_654_619_320_123, 123_000, &TimeConfig::default()).unwrap(); + /// let d = DateTime::from_timestamp_with_config(1_654_619_320_123, 123_000, &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!(d.to_string(), "2022-06-07T16:28:40.246"); /// ``` pub fn from_timestamp_with_config( @@ -431,7 +432,7 @@ impl DateTime { /// assert_eq!(d.to_string(), "2022-06-07T16:28:40.246"); /// ``` pub fn from_timestamp(timestamp: i64, timestamp_microsecond: u32) -> Result { - Self::from_timestamp_with_config(timestamp, timestamp_microsecond, &TimeConfig::default()) + Self::from_timestamp_with_config(timestamp, timestamp_microsecond, &TimeConfigBuilder::new().build()) } /// Create a datetime from the system time. This method uses [std::time::SystemTime] to get diff --git a/src/duration.rs b/src/duration.rs index 40bc237..548c93a 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use std::fmt; -use crate::{ParseError, Time, TimeConfig}; +use crate::{time::TimeConfig, ParseError, Time, TimeConfigBuilder}; /// A Duration /// @@ -231,7 +231,7 @@ impl Duration { /// ``` #[inline] pub fn parse_bytes(bytes: &[u8]) -> Result { - Duration::parse_bytes_with_config(bytes, &TimeConfig::default()) + Duration::parse_bytes_with_config(bytes, &TimeConfigBuilder::new().build()) } /// Same as `Duration::parse_bytes` but with a TimeConfig component. @@ -244,9 +244,9 @@ impl Duration { /// # Examples /// /// ``` - /// use speedate::{Duration, TimeConfig}; + /// use speedate::{Duration, TimeConfigBuilder}; /// - /// let d = Duration::parse_bytes_with_config(b"P1Y", &TimeConfig::default()).unwrap(); + /// let d = Duration::parse_bytes_with_config(b"P1Y", &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!( /// d, /// Duration { @@ -454,7 +454,7 @@ impl Duration { match bytes.get(position).copied() { Some(_) => { - let t = Time::parse_bytes_offset(bytes, position, &TimeConfig::default())?; + let t = Time::parse_bytes_offset(bytes, position, &TimeConfigBuilder::new().build())?; Ok(Self { positive: false, // is set above diff --git a/src/lib.rs b/src/lib.rs index 4dcb202..5017613 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ mod time; pub use date::Date; pub use datetime::DateTime; pub use duration::Duration; -pub use time::{MicrosecondsPrecisionOverflowBehavior, Time, TimeConfig}; +pub use time::{MicrosecondsPrecisionOverflowBehavior, Time, TimeConfig, TimeConfigBuilder}; pub use numbers::{float_parse_bytes, float_parse_str, int_parse_bytes, int_parse_str, IntFloat}; diff --git a/src/time.rs b/src/time.rs index 14cb9f0..3922d55 100644 --- a/src/time.rs +++ b/src/time.rs @@ -186,7 +186,7 @@ impl Time { /// ``` #[inline] pub fn parse_bytes(bytes: &[u8]) -> Result { - Self::parse_bytes_offset(bytes, 0, &TimeConfig::default()) + Self::parse_bytes_offset(bytes, 0, &TimeConfigBuilder::new().build()) } /// Same as `Time::parse_bytes` but with a `TimeConfig`. @@ -199,9 +199,9 @@ impl Time { /// # Examples /// /// ``` - /// use speedate::{Time, TimeConfig}; + /// use speedate::{Time, TimeConfigBuilder}; /// - /// let d = Time::parse_bytes_with_config(b"12:13:14.123456", &TimeConfig::default()).unwrap(); + /// let d = Time::parse_bytes_with_config(b"12:13:14.123456", &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!( /// d, /// Time { @@ -237,7 +237,11 @@ impl Time { /// assert_eq!(d.to_string(), "01:02:20.000123"); /// ``` pub fn from_timestamp(timestamp_second: u32, timestamp_microsecond: u32) -> Result { - Time::from_timestamp_with_config(timestamp_second, timestamp_microsecond, &TimeConfig::default()) + Time::from_timestamp_with_config( + timestamp_second, + timestamp_microsecond, + &TimeConfigBuilder::new().build(), + ) } /// Like `from_timestamp` but with a `TimeConfig` @@ -253,9 +257,9 @@ impl Time { /// # Examples /// /// ``` - /// use speedate::{Time, TimeConfig}; + /// use speedate::{Time, TimeConfigBuilder}; /// - /// let d = Time::from_timestamp_with_config(3740, 123, &TimeConfig::default()).unwrap(); + /// let d = Time::from_timestamp_with_config(3740, 123, &TimeConfigBuilder::new().build()).unwrap(); /// assert_eq!(d.to_string(), "01:02:20.000123"); /// ``` pub fn from_timestamp_with_config( @@ -555,7 +559,7 @@ impl PureTime { } } -#[derive(Debug, Clone, Default, Copy)] +#[derive(Debug, Clone, Default, Copy, PartialEq)] pub enum MicrosecondsPrecisionOverflowBehavior { Truncate, #[default] @@ -573,8 +577,43 @@ impl TryFrom<&str> for MicrosecondsPrecisionOverflowBehavior { } } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct TimeConfig { pub microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior, pub unix_timestamp_offset: Option, } + +impl TimeConfig { + pub fn builder() -> TimeConfigBuilder { + TimeConfigBuilder::new() + } +} + +#[derive(Debug, Clone, Default)] +pub struct TimeConfigBuilder { + microseconds_precision_overflow_behavior: Option, + unix_timestamp_offset: Option, +} + +impl TimeConfigBuilder { + pub fn new() -> Self { + Self::default() + } + pub fn microseconds_precision_overflow_behavior( + mut self, + microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior, + ) -> Self { + self.microseconds_precision_overflow_behavior = Some(microseconds_precision_overflow_behavior); + self + } + pub fn unix_timestamp_offset(mut self, unix_timestamp_offset: Option) -> Self { + self.unix_timestamp_offset = unix_timestamp_offset; + self + } + pub fn build(self) -> TimeConfig { + TimeConfig { + microseconds_precision_overflow_behavior: self.microseconds_precision_overflow_behavior.unwrap_or_default(), + unix_timestamp_offset: self.unix_timestamp_offset, + } + } +} diff --git a/tests/main.rs b/tests/main.rs index cfa1904..1834839 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -6,7 +6,7 @@ use strum::EnumMessage; use speedate::{ float_parse_str, int_parse_str, Date, DateTime, Duration, MicrosecondsPrecisionOverflowBehavior, ParseError, Time, - TimeConfig, + TimeConfig, TimeConfigBuilder, }; /// macro for expected values @@ -1250,10 +1250,9 @@ float_err_tests! { fn test_time_parse_truncate_seconds() { let time = Time::parse_bytes_with_config( "12:13:12.123456789".as_bytes(), - &TimeConfig { - microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior::Truncate, - ..Default::default() - }, + &(TimeConfigBuilder::new() + .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate) + .build()), ) .unwrap(); assert_eq!(time.to_string(), "12:13:12.123456"); @@ -1263,10 +1262,9 @@ fn test_time_parse_truncate_seconds() { fn test_datetime_parse_truncate_seconds() { let time = DateTime::parse_bytes_with_config( "2020-01-01T12:13:12.123456789".as_bytes(), - &TimeConfig { - microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior::Truncate, - ..Default::default() - }, + &(TimeConfigBuilder::new() + .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate) + .build()), ) .unwrap(); assert_eq!(time.to_string(), "2020-01-01T12:13:12.123456"); @@ -1276,10 +1274,9 @@ fn test_datetime_parse_truncate_seconds() { fn test_duration_parse_truncate_seconds() { let time = Duration::parse_bytes_with_config( "00:00:00.1234567".as_bytes(), - &TimeConfig { - microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior::Truncate, - ..Default::default() - }, + &(TimeConfigBuilder::new() + .microseconds_precision_overflow_behavior(MicrosecondsPrecisionOverflowBehavior::Truncate) + .build()), ) .unwrap(); assert_eq!(time.to_string(), "PT0.123456S"); @@ -1289,10 +1286,7 @@ fn test_duration_parse_truncate_seconds() { fn test_time_parse_bytes_does_not_add_offset_for_rfc3339() { let time = Time::parse_bytes_with_config( "12:13:12".as_bytes(), - &TimeConfig { - unix_timestamp_offset: Some(0), - ..Default::default() - }, + &(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build()), ) .unwrap(); assert_eq!(time.to_string(), "12:13:12"); @@ -1302,10 +1296,7 @@ fn test_time_parse_bytes_does_not_add_offset_for_rfc3339() { fn test_datetime_parse_bytes_does_not_add_offset_for_rfc3339() { let time = DateTime::parse_bytes_with_config( "2020-01-01T12:13:12".as_bytes(), - &TimeConfig { - unix_timestamp_offset: Some(0), - ..Default::default() - }, + &(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build()), ) .unwrap(); assert_eq!(time.to_string(), "2020-01-01T12:13:12"); @@ -1315,10 +1306,7 @@ fn test_datetime_parse_bytes_does_not_add_offset_for_rfc3339() { fn test_datetime_parse_unix_timestamp_from_bytes_with_utc_offset() { let time = DateTime::parse_bytes_with_config( "1689102037.5586429".as_bytes(), - &TimeConfig { - unix_timestamp_offset: Some(0), - ..Default::default() - }, + &(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build()), ) .unwrap(); assert_eq!(time.to_string(), "2023-07-11T19:00:37.558643Z"); @@ -1328,10 +1316,7 @@ fn test_datetime_parse_unix_timestamp_from_bytes_with_utc_offset() { fn test_datetime_parse_unix_timestamp_from_bytes_as_naive() { let time = DateTime::parse_bytes_with_config( "1689102037.5586429".as_bytes(), - &TimeConfig { - unix_timestamp_offset: None, - ..Default::default() - }, + &(TimeConfigBuilder::new().unix_timestamp_offset(None).build()), ) .unwrap(); assert_eq!(time.to_string(), "2023-07-11T19:00:37.558643"); @@ -1339,28 +1324,27 @@ fn test_datetime_parse_unix_timestamp_from_bytes_as_naive() { #[test] fn test_time_parse_unix_timestamp_from_bytes_with_utc_offset() { - let time = Time::from_timestamp_with_config( - 1, - 2, - &TimeConfig { - unix_timestamp_offset: Some(0), - ..Default::default() - }, - ) - .unwrap(); + let time = + Time::from_timestamp_with_config(1, 2, &(TimeConfigBuilder::new().unix_timestamp_offset(Some(0)).build())) + .unwrap(); assert_eq!(time.to_string(), "00:00:01.000002Z"); } #[test] fn test_time_parse_unix_timestamp_from_bytes_as_naive() { - let time = Time::from_timestamp_with_config( - 1, - 2, - &TimeConfig { - unix_timestamp_offset: None, - ..Default::default() - }, - ) - .unwrap(); + let time = Time::from_timestamp_with_config(1, 2, &(TimeConfigBuilder::new().unix_timestamp_offset(None).build())) + .unwrap(); assert_eq!(time.to_string(), "00:00:01.000002"); } + +#[test] +fn test_time_config_builder() { + assert_eq!( + TimeConfigBuilder::new().build(), + TimeConfig { + microseconds_precision_overflow_behavior: MicrosecondsPrecisionOverflowBehavior::Error, + unix_timestamp_offset: None, + } + ); + assert_eq!(TimeConfigBuilder::new().build(), TimeConfig::builder().build()); +}