Skip to main content

klirr_core/models/
month.rs

1use crate::prelude::*;
2
3/// A month of the year, e.g. 1 for January, 2 for February, etc.
4#[derive(
5    Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display, Serialize, Deserialize, IsVariant,
6)]
7#[display("{}", self.month())]
8pub enum Month {
9    January = 1,
10    February,
11    March,
12    April,
13    May,
14    June,
15    July,
16    August,
17    September,
18    October,
19    November,
20    December,
21}
22
23impl std::fmt::Debug for Month {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.month())
26    }
27}
28
29impl Month {
30    /// Returns the month as a number, e.g. 1 for January, 2 for February, etc.
31    /// This is useful for serialization and comparisons.
32    /// # Examples
33    /// ```
34    /// extern crate klirr_core;
35    /// use klirr_core::prelude::*;
36    /// assert_eq!(Month::January.month(), &1);
37    /// assert_eq!(Month::December.month(), &12);
38    /// ```
39    pub fn month(&self) -> &u8 {
40        match self {
41            Month::January => &1,
42            Month::February => &2,
43            Month::March => &3,
44            Month::April => &4,
45            Month::May => &5,
46            Month::June => &6,
47            Month::July => &7,
48            Month::August => &8,
49            Month::September => &9,
50            Month::October => &10,
51            Month::November => &11,
52            Month::December => &12,
53        }
54    }
55}
56impl std::ops::Deref for Month {
57    type Target = u8;
58    fn deref(&self) -> &Self::Target {
59        self.month()
60    }
61}
62
63impl TryFrom<i32> for Month {
64    type Error = crate::prelude::Error;
65
66    /// Attempts to convert an integer to a `Month`.
67    /// The integer must be between 1 and 12, inclusive.
68    /// If the integer is outside this range, an `Error::InvalidMonth` is returned
69    ///
70    /// # Examples
71    /// ```
72    /// extern crate klirr_core;
73    /// use klirr_core::prelude::*;
74    /// let march = Month::try_from(3).unwrap();
75    /// assert_eq!(march.to_string(), "3".to_owned());
76    /// ```
77    fn try_from(month: i32) -> Result<Self> {
78        match month {
79            1 => Ok(Month::January),
80            2 => Ok(Month::February),
81            3 => Ok(Month::March),
82            4 => Ok(Month::April),
83            5 => Ok(Month::May),
84            6 => Ok(Month::June),
85            7 => Ok(Month::July),
86            8 => Ok(Month::August),
87            9 => Ok(Month::September),
88            10 => Ok(Month::October),
89            11 => Ok(Month::November),
90            12 => Ok(Month::December),
91            _ => Err(Error::InvalidMonth {
92                month,
93                reason: "Month must be between 1 and 12".to_string(),
94            }),
95        }
96    }
97}
98
99impl FromStr for Month {
100    type Err = crate::prelude::Error;
101
102    /// Parses a month from a string.
103    /// The string must be a valid month number (1-12).
104    /// If the string is not a valid month, an `Error::InvalidMonth` is returned.
105    ///
106    /// # Examples
107    /// ```
108    /// extern crate klirr_core;
109    /// use klirr_core::prelude::*;
110    /// let month: Month = "3".parse().unwrap();
111    /// assert_eq!(month, Month::March);
112    /// ```
113    fn from_str(s: &str) -> Result<Self> {
114        let month = s.parse::<i32>().map_err(|_| Error::FailedToParseMonth {
115            invalid_string: s.to_owned(),
116        })?;
117        Self::try_from(month)
118    }
119}
120
121impl TryFrom<u8> for Month {
122    type Error = crate::prelude::Error;
123    fn try_from(month: u8) -> Result<Self> {
124        Self::try_from(month as i32)
125    }
126}
127
128impl TryFrom<u32> for Month {
129    type Error = crate::prelude::Error;
130    fn try_from(month: u32) -> Result<Self> {
131        Self::try_from(month as i32)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use insta::assert_debug_snapshot;
139    use test_log::test;
140
141    #[test]
142    fn test_month_conversion() {
143        assert_eq!(Month::try_from(1).unwrap(), Month::January);
144        assert_eq!(Month::try_from(2).unwrap(), Month::February);
145        assert_eq!(Month::try_from(7).unwrap(), Month::July);
146        assert_eq!(Month::try_from(8).unwrap(), Month::August);
147        assert_eq!(Month::try_from(9).unwrap(), Month::September);
148        assert_eq!(Month::try_from(10).unwrap(), Month::October);
149        assert_eq!(Month::try_from(11).unwrap(), Month::November);
150        assert_eq!(Month::try_from(12).unwrap(), Month::December);
151        assert!(Month::try_from(0).is_err());
152        assert!(Month::try_from(13).is_err());
153    }
154
155    #[test]
156    fn test_month_display() {
157        assert_eq!(Month::January.to_string(), "1");
158        assert_eq!(Month::December.to_string(), "12");
159    }
160
161    #[test]
162    fn test_month_deref() {
163        let month: &u8 = &Month::March;
164        assert_eq!(*month, 3);
165    }
166
167    #[test]
168    fn test_month_debug() {
169        assert_debug_snapshot!(Month::April, @"4");
170    }
171
172    #[test]
173    fn test_from_str_invalid_all_reasons() {
174        let invalid_months = ["0", "13", "abc", "1.5"];
175        for &invalid in &invalid_months {
176            let result: Result<Month> = invalid.parse();
177            assert!(
178                result.is_err(),
179                "Expected error for invalid month '{}'",
180                invalid
181            );
182        }
183    }
184}