backend/service/
util.rs

1//! Utility traits and implementations.
2
3use std::ops::Div;
4
5use chrono::Datelike;
6
7/// Trait for getting the half month bucket of a `NaiveDate`.
8pub trait HalfMonthBucket {
9    /// Returns the half month bucket of the date.
10    /// This is a number between 0 and 23.
11    /// 0 is the first half of January, 1 is the second half of January, 2 is the first half of February, etc.
12    fn half_month_bucket(&self) -> i32;
13}
14
15impl<T> HalfMonthBucket for T
16where
17    T: Datelike,
18{
19    // I know what I'm doing.
20    #[allow(
21        clippy::cast_possible_wrap,
22        clippy::expect_used,
23        clippy::cast_sign_loss
24    )]
25    fn half_month_bucket(&self) -> i32 {
26        /// The number of days in each month.
27        const DAYS_PER_MONTH: [i32; 12] = [
28            31, // January
29            28, // February
30            31, // March
31            30, // April
32            31, // May
33            30, // June
34            31, // July
35            31, // August
36            30, // September
37            31, // October
38            30, // November
39            31, // December
40        ];
41
42        let month = self.month0() as i32;
43        let day = self.day() as i32;
44
45        let days_in_month = DAYS_PER_MONTH
46            .get(month as usize)
47            .copied()
48            .expect("month is between 0 and 11, so this should never panic.");
49
50        let half_month = i32::from(day > days_in_month.div(2));
51
52        month * 2 + half_month
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use chrono::NaiveDate;
60
61    #[allow(clippy::shadow_unrelated, clippy::unwrap_used)]
62    #[test]
63    fn test_half_month_bucket() {
64        let date = NaiveDate::from_ymd_opt(2020, 1, 1).unwrap();
65        assert_eq!(date.half_month_bucket(), 0);
66
67        let date = NaiveDate::from_ymd_opt(2020, 1, 15).unwrap();
68        assert_eq!(date.half_month_bucket(), 0);
69
70        let date = NaiveDate::from_ymd_opt(2020, 1, 16).unwrap();
71        assert_eq!(date.half_month_bucket(), 1);
72
73        let date = NaiveDate::from_ymd_opt(2020, 2, 1).unwrap();
74        assert_eq!(date.half_month_bucket(), 2);
75
76        let date = NaiveDate::from_ymd_opt(2020, 2, 15).unwrap();
77        assert_eq!(date.half_month_bucket(), 3);
78
79        let date = NaiveDate::from_ymd_opt(2020, 12, 31).unwrap();
80        assert_eq!(date.half_month_bucket(), 23);
81    }
82}