From 2596477b9db3bde336b06e8fc2bf5a6a9fe40800 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Thu, 13 Jul 2023 20:34:31 -0700 Subject: [PATCH 1/5] cleanup: replace `! -z` with `-n` --- justfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index 84a08d3..360168b 100644 --- a/justfile +++ b/justfile @@ -114,7 +114,7 @@ bench BENCH="": --target x86_64-unknown-linux-gnu \ --target-dir "{{ cargo_dir }}" \ -- --include-ignored - [ ! -z "{{ IGNORED }}" ] || cargo test \ + [ -n "{{ IGNORED }}" ] || cargo test \ --all-features \ --target x86_64-unknown-linux-gnu \ --target-dir "{{ cargo_dir }}" @@ -126,7 +126,7 @@ bench BENCH="": --target x86_64-unknown-linux-gnu \ --target-dir "{{ cargo_dir }}" \ -- --include-ignored - [ ! -z "{{ IGNORED }}" ] || cargo test \ + [ -n "{{ IGNORED }}" ] || cargo test \ --release \ --all-features \ --target x86_64-unknown-linux-gnu \ From e5d06a319d781a798d9fe4cafff874c2038caffb Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Sat, 19 Aug 2023 22:25:51 -0700 Subject: [PATCH 2/5] cleanup: inline crate paths in tests to make it clearer where structs are coming from --- src/weekday.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/weekday.rs b/src/weekday.rs index 8487729..048b094 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -381,10 +381,6 @@ impl Weekday { #[cfg(test)] mod tests { use super::*; - use time::{ - Date, - Month, - }; const ALL_DAYS: &[Weekday] = &[ Weekday::Sunday, @@ -400,7 +396,7 @@ mod tests { /// # Test First of Year. fn t_year_start() { for y in 2000..=2099 { - let c = Date::from_calendar_date(y, Month::January, 1) + let c = time::Date::from_calendar_date(y, time::Month::January, 1) .expect("Unable to create time::Date."); assert_eq!( Weekday::year_begins_on((y - 2000) as u8).as_ref(), From dd7edfe6ffbbe5fc1aa4721d7b1c81c192b4db16 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Wed, 4 Oct 2023 22:17:32 -0700 Subject: [PATCH 3/5] lint --- src/abacus.rs | 8 +++----- src/date/mod.rs | 3 +-- src/date/parse.rs | 9 ++++----- src/lib.rs | 4 +--- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/abacus.rs b/src/abacus.rs index f7edb9e..17a6aab 100644 --- a/src/abacus.rs +++ b/src/abacus.rs @@ -2,8 +2,6 @@ # UTC2K - Abacus */ -#![allow(clippy::integer_division)] - use crate::{ DAY_IN_SECONDS, HOUR_IN_SECONDS, @@ -141,7 +139,7 @@ impl Abacus { /// The bitshift wizardry was inspired by [this post](https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/). fn rebalance_ss(&mut self) { if self.ss >= DAY_IN_SECONDS { - let div = self.ss / DAY_IN_SECONDS; + let div = self.ss.wrapping_div(DAY_IN_SECONDS); self.d += div; self.ss -= div * DAY_IN_SECONDS; } @@ -173,7 +171,7 @@ impl Abacus { /// This moves overflowing hours to days. fn rebalance_hh(&mut self) { if self.hh > 23 { - let div = self.hh / 24; + let div = self.hh.wrapping_div(24); self.d += div; self.hh -= div * 24; } @@ -206,7 +204,7 @@ impl Abacus { } // Carry excess months over to years. else if 12 < self.m { - let div = (self.m - 1) / 12; + let div = (self.m - 1).wrapping_div(12); self.y += div; self.m -= div * 12; } diff --git a/src/date/mod.rs b/src/date/mod.rs index bf4232d..425593d 100644 --- a/src/date/mod.rs +++ b/src/date/mod.rs @@ -673,7 +673,6 @@ impl fmt::Display for Utc2k { } impl From for Utc2k { - #[allow(clippy::integer_division)] /// # From Timestamp. /// /// Note, this will saturate to [`Utc2k::MIN_UNIXTIME`] and @@ -692,7 +691,7 @@ impl From for Utc2k { 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 / DAY_IN_SECONDS); + let (y, m, d) = parse::date_seconds(src.wrapping_div(DAY_IN_SECONDS)); let (hh, mm, ss) = parse::time_seconds(src % DAY_IN_SECONDS); Self { y, m, d, hh, mm, ss } diff --git a/src/date/parse.rs b/src/date/parse.rs index fe375c1..a2ff622 100644 --- a/src/date/parse.rs +++ b/src/date/parse.rs @@ -15,7 +15,6 @@ use crate::{ #[allow(clippy::cast_possible_truncation)] // It fits. -#[allow(clippy::integer_division)] /// # Parse Date From Seconds. /// /// This parses the date portion of a date/time timestamp using the same @@ -26,12 +25,12 @@ use crate::{ pub(crate) const fn date_seconds(mut z: u32) -> (u8, u8, u8) { z += JULIAN_EPOCH - 1_721_119; let h: u32 = 100 * z - 25; - let mut a: u32 = h / 3_652_425; + let mut a: u32 = h.wrapping_div(3_652_425); a -= a >> 2; - let year: u32 = (100 * a + h) / 36_525; + let year: u32 = (100 * a + h).wrapping_div(36_525); a = a + z - 365 * year - (year >> 2); - let month: u32 = (5 * a + 456) / 153; - let day: u8 = (a - (153 * month - 457) / 5) as u8; + let month: u32 = (5 * a + 456).wrapping_div(153); + let day: u8 = (a - (153 * month - 457).wrapping_div(5)) as u8; if month > 12 { ((year - 1999) as u8, month as u8 - 12, day) diff --git a/src/lib.rs b/src/lib.rs index 3f3da2f..8dff8a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,8 +189,6 @@ pub fn unixtime() -> u32 { ) } -#[allow(clippy::cast_possible_truncation)] // It fits. -#[allow(clippy::integer_division)] // We want it. #[must_use] /// # Now (Current Year). /// @@ -204,6 +202,6 @@ pub fn unixtime() -> u32 { /// assert_eq!(utc2k::Utc2k::now().year(), utc2k::year()); /// ``` pub fn year() -> u16 { - let (y, _, _) = date::parse::date_seconds(unixtime() / DAY_IN_SECONDS); + let (y, _, _) = date::parse::date_seconds(unixtime().wrapping_div(DAY_IN_SECONDS)); u16::from(y) + 2000 } From 04ed5fa8191083821c4ff15b299ff321778074b0 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Thu, 5 Oct 2023 11:55:27 -0700 Subject: [PATCH 4/5] new: Weekday::first_in_month new: Weekday::last_in_month new: Weekday::nth_in_month --- src/weekday.rs | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/weekday.rs b/src/weekday.rs index 048b094..5ab06f3 100644 --- a/src/weekday.rs +++ b/src/weekday.rs @@ -337,7 +337,125 @@ impl Weekday { /// assert_eq!(Weekday::yesterday(), Utc2k::yesterday().weekday()); /// ``` pub fn yesterday() -> Self { Utc2k::yesterday().weekday() } +} + +impl Weekday { + #[inline] + #[must_use] + /// # Date of First Weekday. + /// + /// Return the day corresponding to the first occurrence of this weekday in + /// a given year/month. + /// + /// This will only return `None` if you pass a bad year and/or month. + /// + /// ## Examples + /// + /// ``` + /// use utc2k::Weekday; + /// + /// // The first Friday in November 2023 was on the 3rd. + /// assert_eq!( + /// Weekday::Friday.first_in_month(2023, 11), + /// Some(3), + /// ); + /// ``` + pub fn first_in_month(self, y: u16, m: u8) -> Option { self.nth_in_month(y, m, 1) } + #[inline] + #[must_use] + /// # Date of Last Weekday. + /// + /// Return the day corresponding to the last occurrence of this weekday in + /// a given year/month. + /// + /// This will only return `None` if you pass a bad year and/or month. + /// + /// ## Examples + /// + /// ``` + /// use utc2k::Weekday; + /// + /// // The last Saturday in Februrary 2020 was the 29th. LEAP! + /// assert_eq!( + /// Weekday::Saturday.last_in_month(2020, 02), + /// Some(29), + /// ); + /// ``` + pub fn last_in_month(self, y: u16, m: u8) -> Option { + // Load the first date of the month, and make sure it is sane. + let first = Utc2k::new(y, m, 1, 0, 0, 0); + if (y, m, 1) != first.ymd() { return None; } + + // Pull that first day's weekday. + let weekday = first.weekday(); + + // Find the first day. + let d = match (weekday as u8).cmp(&(self as u8)) { + Ordering::Less => 1 + self as u8 - weekday as u8, + Ordering::Equal => 1, + Ordering::Greater => 8 - (weekday as u8 - self as u8), + }; + + // Now find out how many weeks we can add to that without going over. + let n = (first.month_size() - d).wrapping_div(7); + + // Add them and we have our answer! + Some(d + n * 7) + } + + #[must_use] + /// # Date of Nth Weekday. + /// + /// Return the day corresponding to the nth occurrence of this weekday in a + /// given year/month, if any. (`None` is returned if it rolls over.) + /// + /// ## Examples + /// + /// ``` + /// use utc2k::Weekday; + /// + /// let day = Weekday::Monday; + /// + /// // There are five Mondays in October 2023: + /// assert_eq!(day.nth_in_month(2023, 10, 1), Some(2)); + /// assert_eq!(day.nth_in_month(2023, 10, 2), Some(9)); + /// assert_eq!(day.nth_in_month(2023, 10, 3), Some(16)); + /// assert_eq!(day.nth_in_month(2023, 10, 4), Some(23)); + /// assert_eq!(day.nth_in_month(2023, 10, 5), Some(30)); + /// + /// // But no more! + /// assert_eq!(day.nth_in_month(2023, 10, 6), None); + /// ``` + pub fn nth_in_month(self, y: u16, m: u8, n: u8) -> Option { + // Zero is meaningless, and there will never be more than five. + if ! (1..6).contains(&n) { return None; } + + // Load the first date of the month, and make sure it is sane. + let first = Utc2k::new(y, m, 1, 0, 0, 0); + if (y, m, 1) != first.ymd() { return None; } + + // Pull that first day's weekday. + let weekday = first.weekday(); + + // Calculate the day! + let d = + // Find the first. + match (weekday as u8).cmp(&(self as u8)) { + Ordering::Less => 1 + self as u8 - weekday as u8, + Ordering::Equal => 1, + Ordering::Greater => 8 - (weekday as u8 - self as u8), + } + // Scale to the nth. + + (n - 1) * 7; + + // Return it, unless we've passed into a different month. + if d <= first.month_size() { Some(d) } + else { None } + } +} + +impl Weekday { /// # From Abbreviation Bytes. /// /// This matches the first three bytes, case-insensitively, against the @@ -467,6 +585,46 @@ mod tests { } } + #[test] + /// # Nth Day. + fn t_nth_in_month() { + // One full month should cover our bases. + for (weekday, dates) in [ + (Weekday::Sunday, vec![1, 8, 15, 22, 29]), + (Weekday::Monday, vec![2, 9, 16, 23, 30]), + (Weekday::Tuesday, vec![3, 10, 17, 24, 31]), + (Weekday::Wednesday, vec![4, 11, 18, 25]), + (Weekday::Thursday, vec![5, 12, 19, 26]), + (Weekday::Friday, vec![6, 13, 20, 27]), + (Weekday::Saturday, vec![7, 14, 21, 28]), + ] { + for (k, v) in dates.iter().copied().enumerate() { + let tmp = weekday.nth_in_month(2023, 10, k as u8 + 1); + assert_eq!( + tmp, + Some(v), + "Expected {} {weekday} to be {v}, not {tmp:?}.", + k + 1, + ); + + // Test first for the first. This is an alias so shouldn't + // ever fail, but just in caseā€¦ + if k == 0 { assert_eq!(weekday.first_in_month(2023, 10), tmp); } + // And last for the last. + else if k + 1 == dates.len() { + assert_eq!( + weekday.last_in_month(2023, 10), + Some(v), + "Expected {weekday} to end on {v}, not {tmp:?}.", + ); + } + } + + // And make sure one more is too many. + assert_eq!(weekday.nth_in_month(2023, 10, dates.len() as u8 + 1), None); + } + } + #[test] /// # String Tests. fn t_str() { From 8ad8b443744ab83545439ee1a07432e813c86389 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Thu, 5 Oct 2023 11:56:41 -0700 Subject: [PATCH 5/5] bump: 0.7.0 --- CHANGELOG.md | 10 ++++++++++ CREDITS.md | 4 ++-- Cargo.toml | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8407b22..3efb1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ +## [0.7.0](https://github.com/Blobfolio/utc2k/releases/tag/v0.7.0) - 2023-10-05 + +### New + +* `Weekday::first_in_month` +* `Weekday::last_in_month` +* `Weekday::nth_in_month` + + + ## [0.6.1](https://github.com/Blobfolio/utc2k/releases/tag/v0.6.1) - 2023-07-13 ### Changed diff --git a/CREDITS.md b/CREDITS.md index 38b0b0c..05664b6 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,6 @@ # Project Dependencies Package: utc2k - Version: 0.6.1 - Generated: 2023-07-13 19:17:40 UTC + Version: 0.7.0 + Generated: 2023-10-05 18:56:07 UTC This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index 5771843..ec051ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "utc2k" -version = "0.6.1" +version = "0.7.0" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.70"