Skip to content

Commit

Permalink
release: 0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
joshstoik1 committed Oct 5, 2023
2 parents 1ff176f + 8ad8b44 commit 7493b1d
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 25 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions CREDITS.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "utc2k"
version = "0.6.1"
version = "0.7.0"
authors = ["Blobfolio, LLC. <[email protected]>"]
edition = "2021"
rust-version = "1.70"
Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
Expand All @@ -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 \
Expand Down
8 changes: 3 additions & 5 deletions src/abacus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
# UTC2K - Abacus
*/

#![allow(clippy::integer_division)]

use crate::{
DAY_IN_SECONDS,
HOUR_IN_SECONDS,
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 1 addition & 2 deletions src/date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,6 @@ impl fmt::Display for Utc2k {
}

impl From<u32> for Utc2k {
#[allow(clippy::integer_division)]
/// # From Timestamp.
///
/// Note, this will saturate to [`Utc2k::MIN_UNIXTIME`] and
Expand All @@ -692,7 +691,7 @@ impl From<u32> 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 }
Expand Down
9 changes: 4 additions & 5 deletions src/date/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
///
Expand All @@ -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
}
164 changes: 159 additions & 5 deletions src/weekday.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> { 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<u8> {
// 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<u8> {
// 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
Expand Down Expand Up @@ -381,10 +499,6 @@ impl Weekday {
#[cfg(test)]
mod tests {
use super::*;
use time::{
Date,
Month,
};

const ALL_DAYS: &[Weekday] = &[
Weekday::Sunday,
Expand All @@ -400,7 +514,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(),
Expand Down Expand Up @@ -471,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() {
Expand Down

0 comments on commit 7493b1d

Please sign in to comment.