From 840c28ee4e422b4bc4c3fe5df17ce42da8cfb86c Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 26 Jun 2024 13:41:55 +0100 Subject: [PATCH 1/2] raise `DateNotExact` error for millisecond timestamps --- src/date.rs | 15 ++++++--------- src/datetime.rs | 4 +--- tests/main.rs | 12 +++++++++++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/date.rs b/src/date.rs index ccd6df7..c84a87b 100644 --- a/src/date.rs +++ b/src/date.rs @@ -206,13 +206,10 @@ impl Date { /// assert_eq!(d.to_string(), "2022-06-07"); /// ``` pub fn from_timestamp(timestamp: i64, require_exact: bool) -> Result { - let (timestamp_second, _) = Self::timestamp_watershed(timestamp)?; - let d = Self::from_timestamp_calc(timestamp_second)?; - if require_exact { - let time_second = timestamp_second.rem_euclid(86_400); - if time_second != 0 { - return Err(ParseError::DateNotExact); - } + let (timestamp_second, micros) = Self::timestamp_watershed(timestamp)?; + let (d, seconds) = Self::from_timestamp_calc(timestamp_second)?; + if require_exact && (seconds != 0 || micros != 0) { + return Err(ParseError::DateNotExact); } Ok(d) } @@ -287,7 +284,7 @@ impl Date { Ok((seconds, microseconds as u32)) } - pub(crate) fn from_timestamp_calc(timestamp_second: i64) -> Result { + pub(crate) fn from_timestamp_calc(timestamp_second: i64) -> Result<(Self, u32), ParseError> { if timestamp_second < UNIX_1600 { return Err(ParseError::DateTooSmall); } @@ -312,7 +309,7 @@ impl Date { true => leap_year_month_day(ordinal_day), false => common_year_month_day(ordinal_day), }; - Ok(Self { year, month, day }) + Ok((Self { year, month, day }, (timestamp_second.rem_euclid(86_400)) as u32)) } /// Parse a date from bytes, no check is performed for extract characters at the end of the string diff --git a/src/datetime.rs b/src/datetime.rs index c5de2ba..b37cf5c 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -400,9 +400,7 @@ impl DateTime { .ok_or(ParseError::TimeTooLarge)?; total_microsecond %= 1_000_000; } - let date = Date::from_timestamp_calc(second)?; - // rem_euclid since if `timestamp_second = -100`, we want `time_second = 86300` (e.g. `86400 - 100`) - let time_second = second.rem_euclid(86_400) as u32; + let (date, time_second) = Date::from_timestamp_calc(second)?; Ok(Self { date, time: Time::from_timestamp_with_config(time_second, total_microsecond, config)?, diff --git a/tests/main.rs b/tests/main.rs index f9ecc6c..97a7c2d 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -230,7 +230,7 @@ fn date_comparison() { } #[test] -fn date_timestamp() { +fn date_timestamp_exact() { let d = Date::from_timestamp(1_654_560_000, true).unwrap(); assert_eq!(d.to_string(), "2022-06-07"); assert_eq!(d.timestamp(), 1_654_560_000); @@ -239,6 +239,16 @@ fn date_timestamp() { Ok(d) => panic!("unexpectedly valid, {d}"), Err(e) => assert_eq!(e, ParseError::DateNotExact), } + + // milliseconds + let d = Date::from_timestamp(1_654_560_000_000, true).unwrap(); + assert_eq!(d.to_string(), "2022-06-07"); + assert_eq!(d.timestamp(), 1_654_560_000); + + match Date::from_timestamp(1_654_560_000_001, true) { + Ok(d) => panic!("unexpectedly valid, {d}"), + Err(e) => assert_eq!(e, ParseError::DateNotExact), + } } macro_rules! date_from_timestamp { From fd014c7e25822c848a4065b884dfdf7f76cd963a Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 26 Jun 2024 13:48:22 +0100 Subject: [PATCH 2/2] adjust variable names --- src/date.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/date.rs b/src/date.rs index c84a87b..0c02716 100644 --- a/src/date.rs +++ b/src/date.rs @@ -206,9 +206,9 @@ impl Date { /// assert_eq!(d.to_string(), "2022-06-07"); /// ``` pub fn from_timestamp(timestamp: i64, require_exact: bool) -> Result { - let (timestamp_second, micros) = Self::timestamp_watershed(timestamp)?; - let (d, seconds) = Self::from_timestamp_calc(timestamp_second)?; - if require_exact && (seconds != 0 || micros != 0) { + let (seconds, microseconds) = Self::timestamp_watershed(timestamp)?; + let (d, remaining_seconds) = Self::from_timestamp_calc(seconds)?; + if require_exact && (remaining_seconds != 0 || microseconds != 0) { return Err(ParseError::DateNotExact); } Ok(d)