diff --git a/src/items/builder.rs b/src/items/builder.rs index 89d7e83..7fd6826 100644 --- a/src/items/builder.rs +++ b/src/items/builder.rs @@ -148,20 +148,16 @@ impl DateTimeBuilder { self.set_time(time) } - fn build_from_timestamp(ts: epoch::Timestamp, tz: jiff::tz::TimeZone) -> Option { - Some( - jiff::Timestamp::new(ts.second, ts.nanosecond as i32) - .ok()? - .to_zoned(tz), - ) - } - pub(super) fn build(self) -> Option { let base = self.base.unwrap_or(Zoned::now()); // If a timestamp is set, we use it to build the `Zoned` object. if let Some(ts) = self.timestamp { - return Self::build_from_timestamp(ts, base.offset().to_time_zone()); + return Some( + jiff::Timestamp::try_from(ts) + .ok()? + .to_zoned(base.offset().to_time_zone()), + ); } // If any of the following items are set, we truncate the time portion diff --git a/src/items/epoch.rs b/src/items/epoch.rs index b116e10..21a5c20 100644 --- a/src/items/epoch.rs +++ b/src/items/epoch.rs @@ -32,14 +32,26 @@ use super::primitive::{dec_uint, s}; /// - Negative timestamps are represented by a negative `second` value and a /// positive `nanosecond` value. #[derive(Debug, PartialEq)] -pub(crate) struct Timestamp { - pub(crate) second: i64, - pub(crate) nanosecond: u32, +pub(super) struct Timestamp { + second: i64, + nanosecond: u32, +} + +impl TryFrom for jiff::Timestamp { + type Error = &'static str; + + fn try_from(ts: Timestamp) -> Result { + jiff::Timestamp::new( + ts.second, + i32::try_from(ts.nanosecond).map_err(|_| "nanosecond value exceeds i32::MAX")?, + ) + .map_err(|_| "timestamp value is out of valid range") + } } /// Parse a timestamp in the form of `@1234567890` or `@-1234567890.12345` or /// `@1234567890,12345`. -pub(crate) fn parse(input: &mut &str) -> ModalResult { +pub(super) fn parse(input: &mut &str) -> ModalResult { (s("@"), opt(s(one_of(['-', '+']))), sec_and_nsec) .verify_map(|(_, sign, (sec, nsec))| { let sec = i64::try_from(sec).ok()?; @@ -78,84 +90,46 @@ pub(super) fn sec_and_nsec(input: &mut &str) -> ModalResult<(u64, u32)> { mod tests { use super::*; - #[test] - fn sec_and_nsec_test() { - let mut input = "1234567890"; - assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 0)); - - let mut input = "1234567890.12345"; - assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 123450000)); - - let mut input = "1234567890,12345"; - assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 123450000)); + fn ts(second: i64, nanosecond: u32) -> Timestamp { + Timestamp { second, nanosecond } + } - let mut input = "1234567890.1234567890123"; - assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 123456789)); + #[test] + fn parse_sec_and_nsec() { + for (input, expected) in [ + ("1234567890", (1234567890, 0)), // only seconds + ("1234567890.12345", (1234567890, 123450000)), // seconds and nanoseconds, '.' as floating point + ("1234567890,12345", (1234567890, 123450000)), // seconds and nanoseconds, ',' as floating point + ("1234567890.1234567890123", (1234567890, 123456789)), // nanoseconds with more than 9 digits, truncated + ] { + let mut s = input; + assert_eq!(sec_and_nsec(&mut s).unwrap(), expected, "{input}"); + } + + for input in [ + ".1234567890", // invalid: no leading seconds + "-1234567890", // invalid: negative input not allowed + ] { + let mut s = input; + assert!(sec_and_nsec(&mut s).is_err(), "{input}"); + } } #[test] fn timestamp() { - let mut input = "@1234567890"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: 1234567890, - nanosecond: 0, - } - ); - - let mut input = "@ 1234567890"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: 1234567890, - nanosecond: 0, - } - ); - - let mut input = "@ -1234567890"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: -1234567890, - nanosecond: 0, - } - ); - - let mut input = "@ - 1234567890"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: -1234567890, - nanosecond: 0, - } - ); - - let mut input = "@1234567890.12345"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: 1234567890, - nanosecond: 123450000, - } - ); - - let mut input = "@1234567890,12345"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: 1234567890, - nanosecond: 123450000, - } - ); - - let mut input = "@-1234567890.12345"; - assert_eq!( - parse(&mut input).unwrap(), - Timestamp { - second: -1234567891, - nanosecond: 876550000, - } - ); + for (input, expected) in [ + ("@1234567890", ts(1234567890, 0)), // positive seconds, no nanoseconds + ("@ 1234567890", ts(1234567890, 0)), // space after '@', positive seconds, no nanoseconds + ("@-1234567890", ts(-1234567890, 0)), // negative seconds, no nanoseconds + ("@ -1234567890", ts(-1234567890, 0)), // space after '@', negative seconds, no nanoseconds + ("@ - 1234567890", ts(-1234567890, 0)), // space after '@' and after '-', negative seconds, no nanoseconds + ("@1234567890.12345", ts(1234567890, 123450000)), // positive seconds with nanoseconds, '.' as floating point + ("@1234567890,12345", ts(1234567890, 123450000)), // positive seconds with nanoseconds, ',' as floating point + ("@-1234567890.12345", ts(-1234567891, 876550000)), // negative seconds with nanoseconds, '.' as floating point + ("@1234567890.1234567890123", ts(1234567890, 123456789)), // nanoseconds with more than 9 digits, truncated + ] { + let mut s = input; + assert_eq!(parse(&mut s).unwrap(), expected, "{input}"); + } } }