1#![forbid(unsafe_code)]
2use use_date::CalendarDate;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Quarter {
20 Q1,
21 Q2,
22 Q3,
23 Q4,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum QuarterError {
28 InvalidMonth,
29}
30
31impl Quarter {
32 #[must_use]
33 pub fn number(&self) -> u8 {
34 match self {
35 Self::Q1 => 1,
36 Self::Q2 => 2,
37 Self::Q3 => 3,
38 Self::Q4 => 4,
39 }
40 }
41
42 #[must_use]
43 pub fn start_month(&self) -> u8 {
44 match self {
45 Self::Q1 => 1,
46 Self::Q2 => 4,
47 Self::Q3 => 7,
48 Self::Q4 => 10,
49 }
50 }
51
52 #[must_use]
53 pub fn end_month(&self) -> u8 {
54 match self {
55 Self::Q1 => 3,
56 Self::Q2 => 6,
57 Self::Q3 => 9,
58 Self::Q4 => 12,
59 }
60 }
61}
62
63pub fn quarter_for_month(month: u8) -> Result<Quarter, QuarterError> {
64 Ok(match month {
65 1..=3 => Quarter::Q1,
66 4..=6 => Quarter::Q2,
67 7..=9 => Quarter::Q3,
68 10..=12 => Quarter::Q4,
69 _ => return Err(QuarterError::InvalidMonth),
70 })
71}
72
73#[must_use]
74pub fn quarter_start_date(year: i32, quarter: Quarter) -> CalendarDate {
75 CalendarDate::new(year, quarter.start_month(), 1).unwrap()
76}
77
78#[must_use]
79pub fn quarter_end_date(year: i32, quarter: Quarter) -> CalendarDate {
80 let (month, day) = match quarter {
81 Quarter::Q1 => (3, 31),
82 Quarter::Q2 => (6, 30),
83 Quarter::Q3 => (9, 30),
84 Quarter::Q4 => (12, 31),
85 };
86
87 CalendarDate::new(year, month, day).unwrap()
88}
89
90pub fn month_in_quarter(month: u8, quarter: Quarter) -> Result<bool, QuarterError> {
91 Ok(quarter_for_month(month)? == quarter)
92}
93
94#[cfg(test)]
95mod tests {
96 use super::{
97 month_in_quarter, quarter_end_date, quarter_for_month, quarter_start_date, Quarter,
98 QuarterError,
99 };
100 use use_date::CalendarDate;
101
102 #[test]
103 fn maps_months_to_quarters() {
104 assert_eq!(quarter_for_month(1).unwrap(), Quarter::Q1);
105 assert_eq!(quarter_for_month(5).unwrap(), Quarter::Q2);
106 assert_eq!(quarter_for_month(8).unwrap(), Quarter::Q3);
107 assert_eq!(quarter_for_month(12).unwrap(), Quarter::Q4);
108 assert_eq!(Quarter::Q3.start_month(), 7);
109 assert_eq!(Quarter::Q4.end_month(), 12);
110 }
111
112 #[test]
113 fn builds_quarter_boundaries() {
114 assert_eq!(
115 quarter_start_date(2024, Quarter::Q2),
116 CalendarDate::new(2024, 4, 1).unwrap()
117 );
118 assert_eq!(
119 quarter_end_date(2024, Quarter::Q2),
120 CalendarDate::new(2024, 6, 30).unwrap()
121 );
122 assert!(month_in_quarter(5, Quarter::Q2).unwrap());
123 assert!(!month_in_quarter(7, Quarter::Q2).unwrap());
124 }
125
126 #[test]
127 fn rejects_invalid_quarter_months() {
128 assert_eq!(quarter_for_month(0), Err(QuarterError::InvalidMonth));
129 assert_eq!(
130 month_in_quarter(13, Quarter::Q1),
131 Err(QuarterError::InvalidMonth)
132 );
133 }
134}