1use std::ops::Div;
4
5use chrono::Datelike;
6
7pub trait HalfMonthBucket {
9 fn half_month_bucket(&self) -> i32;
13}
14
15impl<T> HalfMonthBucket for T
16where
17 T: Datelike,
18{
19 #[allow(
21 clippy::cast_possible_wrap,
22 clippy::expect_used,
23 clippy::cast_sign_loss
24 )]
25 fn half_month_bucket(&self) -> i32 {
26 const DAYS_PER_MONTH: [i32; 12] = [
28 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ];
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}