diff --git a/CHANGELOG.md b/CHANGELOG.md index 145bf4f..1ec0800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ +## [0.11.0](https://github.com/Blobfolio/utc2k/releases/tag/v0.11.0) - 2024-10-25 + +### New + +* `FmtUtc2k::MIN` +* `FmtUtc2k::MAX` +* `Utc2k::MIN` +* `Utc2k::MAX` + +### Fixed + +* Clamp `utc2k::unixtime` to supported min/max range in case the system clock is the right kind of wrong + +### Changed + +* Make `Utc2k::cmp_date` const +* Make `Utc2k::cmp_time` const + +### Replaced + +* `FmtUtc2k::min` (use `FmtUtc2k::MIN` instead) +* `FmtUtc2k::max` (use `FmtUtc2k::MAX` instead) +* `Utc2k::min` (use `Utc2k::MIN` instead) +* `Utc2k::max` (use `Utc2k::MAX` instead) + + + ## [0.10.0](https://github.com/Blobfolio/utc2k/releases/tag/v0.10.0) - 2024-09-14 ### Changed diff --git a/CREDITS.md b/CREDITS.md index 08d9eac..873c88e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,6 @@ # Project Dependencies Package: utc2k - Version: 0.10.0 - Generated: 2024-09-15 04:41:51 UTC + Version: 0.11.0 + Generated: 2024-10-25 18:12:03 UTC This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index 80e91c7..ca5f85b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "utc2k" -version = "0.10.0" +version = "0.11.0" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.81" diff --git a/benches/d_utc2k.rs b/benches/d_utc2k.rs index 2714445..67a75e7 100644 --- a/benches/d_utc2k.rs +++ b/benches/d_utc2k.rs @@ -30,6 +30,9 @@ benches!( Bench::spacer(), + Bench::new("utc2k::Utc2k::to_string()") + .run_seeded(Utc2k::from(Utc2k::MAX_UNIXTIME), |u| u.to_string()), + Bench::new("utc2k::Utc2k::to_rfc2822()") .run_seeded(Utc2k::from(Utc2k::MAX_UNIXTIME), |u| u.to_rfc2822()), diff --git a/src/date/mod.rs b/src/date/mod.rs index 8710825..7c61cff 100644 --- a/src/date/mod.rs +++ b/src/date/mod.rs @@ -118,7 +118,7 @@ macros::as_ref_borrow_cast!(FmtUtc2k: as_str str); impl Default for FmtUtc2k { #[inline] - fn default() -> Self { Self::min() } + fn default() -> Self { Self::MIN } } impl Deref for FmtUtc2k { @@ -213,42 +213,24 @@ impl TryFrom<&str> for FmtUtc2k { /// ## Min/Max. impl FmtUtc2k { /// # Minimum Date/Time. - pub(crate) const MIN: [u8; 19] = *b"2000-01-01 00:00:00"; - - /// # Maximum Date/Time. - pub(crate) const MAX: [u8; 19] = *b"2099-12-31 23:59:59"; - - #[inline] - #[must_use] - /// # Minimum Value. - /// - /// This is equivalent to `2000-01-01 00:00:00`. - /// - /// ## Examples /// /// ``` - /// use utc2k::FmtUtc2k; - /// - /// let date = FmtUtc2k::min(); - /// assert_eq!(date.as_str(), "2000-01-01 00:00:00"); + /// assert_eq!( + /// utc2k::FmtUtc2k::MIN.as_str(), + /// "2000-01-01 00:00:00", + /// ); /// ``` - pub const fn min() -> Self { Self(Self::MIN) } + pub const MIN: Self = Self(*b"2000-01-01 00:00:00"); - #[inline] - #[must_use] - /// # Maximum Value. - /// - /// This is equivalent to `2099-12-31 23:59:59`. - /// - /// ## Examples + /// # Maximum Date/Time. /// /// ``` - /// use utc2k::FmtUtc2k; - /// - /// let date = FmtUtc2k::max(); - /// assert_eq!(date.as_str(), "2099-12-31 23:59:59"); + /// assert_eq!( + /// utc2k::FmtUtc2k::MAX.as_str(), + /// "2099-12-31 23:59:59", + /// ); /// ``` - pub const fn max() -> Self { Self(Self::MAX) } + pub const MAX: Self = Self(*b"2099-12-31 23:59:59"); } /// ## Instantiation/Reuse. @@ -281,7 +263,7 @@ impl FmtUtc2k { /// /// As with all other part-based operations, overflows and underflows will /// be adjusted automatically, with minimum and maximum dates capped to - /// [`FmtUtc2k::min`] and [`FmtUtc2k::max`] respectively. + /// [`FmtUtc2k::MIN`] and [`FmtUtc2k::MAX`] respectively. /// /// ## Examples /// @@ -305,7 +287,7 @@ impl FmtUtc2k { /// /// As with all other part-based operations, overflows and underflows will /// be adjusted automatically, with minimum and maximum dates capped to - /// [`FmtUtc2k::min`] and [`FmtUtc2k::max`] respectively. + /// [`FmtUtc2k::MIN`] and [`FmtUtc2k::MAX`] respectively. /// /// ## Examples /// @@ -380,7 +362,7 @@ impl FmtUtc2k { /// ``` /// use utc2k::FmtUtc2k; /// - /// let fmt = FmtUtc2k::max(); + /// let fmt = FmtUtc2k::MAX; /// assert_eq!(fmt.as_bytes(), b"2099-12-31 23:59:59"); /// ``` pub const fn as_bytes(&self) -> &[u8] { &self.0 } @@ -400,7 +382,7 @@ impl FmtUtc2k { /// ``` /// use utc2k::FmtUtc2k; /// - /// let fmt = FmtUtc2k::max(); + /// let fmt = FmtUtc2k::MAX; /// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59"); /// ``` pub const fn as_str(&self) -> &str { @@ -676,7 +658,7 @@ impl AddAssign for Utc2k { impl Default for Utc2k { #[inline] - fn default() -> Self { Self::min() } + fn default() -> Self { Self::MIN } } impl fmt::Display for Utc2k { @@ -701,8 +683,8 @@ impl From for Utc2k { /// assert_eq!(Utc2k::from(u32::MAX).to_string(), "2099-12-31 23:59:59"); /// ``` fn from(src: u32) -> Self { - if src <= Self::MIN_UNIXTIME { Self::min() } - else if src >= Self::MAX_UNIXTIME { Self::max() } + if src <= Self::MIN_UNIXTIME { Self::MIN } + else if src >= Self::MAX_UNIXTIME { Self::MAX } else { // Tease out the date parts with a lot of terrible math. let (y, m, d) = parse::date_seconds(src.wrapping_div(DAY_IN_SECONDS)); @@ -957,43 +939,51 @@ impl TryFrom<&str> for Utc2k { /// ## Min/Max. impl Utc2k { - /// # Minimum Timestamp. - pub const MIN_UNIXTIME: u32 = 946_684_800; - - /// # Maximum Timestamp. - pub const MAX_UNIXTIME: u32 = 4_102_444_799; - - #[inline] - #[must_use] - /// # Minimum Value. + /// # Minimum Date/Time. /// - /// This is equivalent to `2000-01-01 00:00:00`. + /// ``` + /// assert_eq!( + /// utc2k::Utc2k::MIN.to_string(), + /// "2000-01-01 00:00:00", + /// ); + /// ``` + pub const MIN: Self = Self { y: 0, m: 1, d: 1, hh: 0, mm: 0, ss: 0 }; + + /// # Maximum Date/Time. /// - /// ## Examples + /// ``` + /// assert_eq!( + /// utc2k::Utc2k::MAX.to_string(), + /// "2099-12-31 23:59:59", + /// ); + /// ``` + pub const MAX: Self = Self { y: 99, m: 12, d: 31, hh: 23, mm: 59, ss: 59 }; + + /// # Minimum Unix Timestamp. /// /// ``` /// use utc2k::Utc2k; /// - /// let date = Utc2k::min(); - /// assert_eq!(date.to_string(), "2000-01-01 00:00:00"); + /// assert_eq!(Utc2k::MIN_UNIXTIME, 946_684_800); + /// assert_eq!( + /// Utc2k::MIN.unixtime(), + /// Utc2k::MIN_UNIXTIME, + /// ); /// ``` - pub const fn min() -> Self { Self { y: 0, m: 1, d: 1, hh: 0, mm: 0, ss: 0 } } + pub const MIN_UNIXTIME: u32 = 946_684_800; - #[inline] - #[must_use] - /// # Maximum Value. - /// - /// This is equivalent to `2099-12-31 23:59:59`. - /// - /// ## Examples + /// # Maximum Unix Timestamp. /// /// ``` /// use utc2k::Utc2k; /// - /// let date = Utc2k::max(); - /// assert_eq!(date.to_string(), "2099-12-31 23:59:59"); + /// assert_eq!(Utc2k::MAX_UNIXTIME, 4_102_444_799); + /// assert_eq!( + /// Utc2k::MAX.unixtime(), + /// Utc2k::MAX_UNIXTIME, + /// ); /// ``` - pub const fn max() -> Self { Self { y: 99, m: 12, d: 31, hh: 23, mm: 59, ss: 59 } } + pub const MAX_UNIXTIME: u32 = 4_102_444_799; } /// ## Instantiation. @@ -1857,7 +1847,7 @@ impl Utc2k { /// ``` /// use utc2k::Utc2k; /// - /// let date = Utc2k::max(); + /// let date = Utc2k::MAX; /// assert!(date.checked_add(1).is_none()); /// /// let date = Utc2k::new(2010, 1, 1, 0, 0, 0); @@ -1913,7 +1903,7 @@ impl Utc2k { /// ``` /// use utc2k::Utc2k; /// - /// let date = Utc2k::min(); + /// let date = Utc2k::MIN; /// assert!(date.checked_sub(1).is_none()); /// /// let date = Utc2k::new(2010, 1, 1, 0, 0, 0); @@ -1951,7 +1941,7 @@ impl Utc2k { /// /// // Because we're only dealing with a single century, there is an /// // upper limit to the possible return values… - /// assert_eq!(Utc2k::min().abs_diff(Utc2k::max()), 3_155_759_999); + /// assert_eq!(Utc2k::MIN.abs_diff(Utc2k::MAX), 3_155_759_999); /// ``` pub const fn abs_diff(self, other: Self) -> u32 { self.unixtime().abs_diff(other.unixtime()) @@ -1978,14 +1968,18 @@ impl Utc2k { /// let date3 = Utc2k::new(2022, 10, 31, 0, 0, 0); /// assert_eq!(date1.cmp_date(date3), Ordering::Less); /// ``` - pub fn cmp_date(self, other: Self) -> Ordering { - match self.y.cmp(&other.y) { - Ordering::Equal => match self.m.cmp(&other.m) { - Ordering::Equal => self.d.cmp(&other.d), - cmp => cmp, - }, - cmp => cmp, + pub const fn cmp_date(self, other: Self) -> Ordering { + if self.y == other.y { + if self.m == other.m { + if self.d == other.d { Ordering::Equal } + else if self.d < other.d { Ordering::Less } + else { Ordering::Greater } + } + else if self.m < other.m { Ordering::Less } + else { Ordering::Greater } } + else if self.y < other.y { Ordering::Less } + else { Ordering::Greater } } #[must_use] @@ -2009,14 +2003,18 @@ impl Utc2k { /// let date3 = Utc2k::new(2022, 10, 31, 0, 0, 0); /// assert_eq!(date1.cmp_time(date3), Ordering::Equal); /// ``` - pub fn cmp_time(self, other: Self) -> Ordering { - match self.hh.cmp(&other.hh) { - Ordering::Equal => match self.mm.cmp(&other.mm) { - Ordering::Equal => self.ss.cmp(&other.ss), - cmp => cmp, - }, - cmp => cmp, + pub const fn cmp_time(self, other: Self) -> Ordering { + if self.hh == other.hh { + if self.mm == other.mm { + if self.ss == other.ss { Ordering::Equal } + else if self.ss < other.ss { Ordering::Less } + else { Ordering::Greater } + } + else if self.mm < other.mm { Ordering::Less } + else { Ordering::Greater } } + else if self.hh < other.hh { Ordering::Less } + else { Ordering::Greater } } } @@ -2152,7 +2150,7 @@ mod tests { #[test] /// # Leap Years. - fn leap_years() { + fn t_leap_years() { for y in 2000..2100 { let date = Utc2k::new(y, 1, 1, 0, 0, 0); assert_eq!(date.year(), y); @@ -2163,9 +2161,25 @@ mod tests { } } + #[test] + /// # Test Min/Max Explicitly. + fn t_min_max() { + // Self and Timestamps. + assert_eq!(Utc2k::MIN, Utc2k::from(Utc2k::MIN_UNIXTIME)); + assert_eq!(Utc2k::MAX, Utc2k::from(Utc2k::MAX_UNIXTIME)); + assert_eq!(Utc2k::MIN.unixtime(), Utc2k::MIN_UNIXTIME); + assert_eq!(Utc2k::MAX.unixtime(), Utc2k::MAX_UNIXTIME); + + // Utc2k and FmtUtc2k. + assert_eq!(Utc2k::MIN, Utc2k::from(FmtUtc2k::MIN)); + assert_eq!(Utc2k::MAX, Utc2k::from(FmtUtc2k::MAX)); + assert_eq!(FmtUtc2k::MIN, FmtUtc2k::from(Utc2k::MIN)); + assert_eq!(FmtUtc2k::MAX, FmtUtc2k::from(Utc2k::MAX)); + } + #[test] /// # Test Ordering. - fn ordering() { + fn t_ordering() { let expected = vec![ Utc2k::try_from("2000-01-01 00:00:00").unwrap(), Utc2k::try_from("2010-05-31 01:02:03").unwrap(), @@ -2199,4 +2213,94 @@ mod tests { assert_eq!(expected, shuffled); assert_eq!(f_expected, f_shuffled); } + + #[test] + /// # Test Manual cmp_date. + fn t_cmp_date() { + let set = vec![ + Utc2k::new(2024, 1, 1, 0, 0, 0), + Utc2k::new(2024, 1, 2, 0, 0, 0), + Utc2k::new(2024, 2, 1, 0, 0, 0), + Utc2k::new(2024, 2, 2, 0, 0, 0), + Utc2k::new(2025, 1, 1, 0, 0, 0), + Utc2k::new(2025, 1, 2, 0, 0, 0), + Utc2k::new(2025, 2, 1, 0, 0, 0), + Utc2k::new(2025, 2, 2, 0, 0, 0), + ]; + + let mut sorted = set.clone(); + sorted.sort(); + sorted.dedup(); + assert_eq!(set, sorted); // Double-check our manual sorting. + + for pair in set.windows(2) { + let &[a, b] = pair else { panic!("Windows is broken?!"); }; + + // Each should be equal to itself. + assert!(a.cmp_date(a).is_eq()); + assert!(b.cmp_date(b).is_eq()); + + // And times shouldn't matter. + assert!(a.cmp_date(a.with_time(1, 2, 3)).is_eq()); + assert!(b.cmp_date(b.with_time(3, 2, 1)).is_eq()); + assert!(a.with_time(1, 2, 3).cmp_date(a).is_eq()); + assert!(b.with_time(3, 2, 1).cmp_date(b).is_eq()); + + // A < B, B > A. + assert!(a.cmp_date(b).is_lt()); + assert!(b.cmp_date(a).is_gt()); + + // Again, times shouldn't matter. + assert!(a.cmp_date(b.with_time(5, 6, 7)).is_lt()); + assert!(b.cmp_date(a.with_time(8, 9, 3)).is_gt()); + assert!(a.with_time(5, 6, 7).cmp_date(b).is_lt()); + assert!(b.with_time(8, 9, 3).cmp_date(a).is_gt()); + } + } + + #[test] + /// # Test Manual cmp_time. + fn t_cmp_time() { + let set = vec![ + Utc2k::new(2027, 6, 5, 0, 0, 0), + Utc2k::new(2027, 6, 5, 0, 0, 1), + Utc2k::new(2027, 6, 5, 0, 1, 0), + Utc2k::new(2027, 6, 5, 0, 1, 1), + Utc2k::new(2027, 6, 5, 1, 0, 0), + Utc2k::new(2027, 6, 5, 1, 0, 1), + Utc2k::new(2027, 6, 5, 1, 1, 0), + Utc2k::new(2027, 6, 5, 1, 1, 1), + ]; + + let mut sorted = set.clone(); + sorted.sort(); + sorted.dedup(); + assert_eq!(set, sorted); // Double-check our manual sorting. + + for pair in set.windows(2) { + let &[a, b] = pair else { panic!("Windows is broken?!"); }; + + // Each should be equal to itself. + assert!(a.cmp_time(a).is_eq()); + assert!(b.cmp_time(b).is_eq()); + + // The date shouldn't matter. + let c = a + crate::YEAR_IN_SECONDS; + assert!(a.cmp_time(c).is_eq()); + assert!(c.cmp_time(a).is_eq()); + let d = b + crate::YEAR_IN_SECONDS; + assert!(b.cmp_time(d).is_eq()); + assert!(d.cmp_time(b).is_eq()); + + // A < B, B > A. + assert!(a.cmp_time(b).is_lt()); + assert!(b.cmp_time(a).is_gt()); + + // Again, the date shouldn't matter. + assert!(a.cmp_time(d).is_lt()); + assert!(b.cmp_time(c).is_gt()); + assert!(c.cmp_time(b).is_lt()); + assert!(d.cmp_time(a).is_gt()); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 9fcf02f..557702b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,7 +213,7 @@ pub fn unixtime() -> u32 { SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map_or( Utc2k::MIN_UNIXTIME, - |n| n.as_secs().min(Utc2k::MAX_UNIXTIME as u64) as u32 + |n| n.as_secs().clamp(Utc2k::MIN_UNIXTIME as u64, Utc2k::MAX_UNIXTIME as u64) as u32 ) } @@ -233,3 +233,38 @@ pub fn year() -> u16 { let (y, _, _) = date::parse::date_seconds(unixtime().wrapping_div(DAY_IN_SECONDS)); u16::from(y) + 2000 } + + + +#[cfg(test)] +mod test { + use super::*; + use std::time::SystemTime; + + #[test] + fn t_unixtime() { + // Our method. + let our_secs = unixtime(); + + // Manual construction via SystemTime. + let secs: u32 = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("The system time is set to the deep past!") + .as_secs() + .try_into() + .expect("The system clock is set to the distant future!"); + + // The SystemTime version should fall within our range. + assert!( + (Utc2k::MIN_UNIXTIME..=Utc2k::MAX_UNIXTIME).contains(&secs), + "Bug: the system clock is completely wrong!", + ); + + // It should also match the `unixtime` output, but let's allow a tiny + // ten-second cushion in case the runner is _really_ slow. + assert!( + our_secs.abs_diff(secs) <= 10, + "SystemTime and unixtime are more different than expected!", + ) + } +} diff --git a/src/serde.rs b/src/serde.rs index 88e612e..591d276 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -101,7 +101,7 @@ impl<'de> Deserialize<'de> for Utc2k { where S: de::Error { // Return the max value on failure because it's too big, // otherwise parse as normal. - Ok(u32::try_from(src).map_or_else(|_| Utc2k::max(), Utc2k::from)) + Ok(u32::try_from(src).map_or_else(|_| Utc2k::MAX, Utc2k::from)) } // Too small to hold an in-range value.