1use core::fmt::Display;
2
3#[repr(u8)]
11#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
12#[allow(missing_docs)]
13pub enum Month {
14 March = 0,
15 April = 1,
16 May = 2,
17 June = 3,
18 July = 4,
19 August = 5,
20 September = 6,
21 October = 7,
22 November = 8,
23 December = 9,
24 January = 10,
25 February = 11,
26 Addenduary = 12,
27}
28
29impl Month {
30 #[must_use]
34 pub const fn new(m: u8) -> Option<Self> {
35 if m >= 1 && m <= 13 {
36 Some(Self::idx0_to_month(m - 1))
37 } else {
38 None
39 }
40 }
41
42 #[must_use]
47 pub const fn ord(self) -> u8 {
48 self.idx0() + 1
49 }
50
51 #[must_use]
52 pub const fn add(self, rhs: u8) -> Self {
57 let rhs = rhs.rem_euclid(13);
62 let sum = self.idx0() + rhs;
63 Self::idx0_to_month(sum.rem_euclid(13))
64 }
65
66 #[must_use]
67 pub const fn sub(self, rhs: u8) -> Self {
72 self.add(13 - rhs.rem_euclid(13))
74 }
75
76 #[must_use]
77 pub const fn next(self) -> Self {
79 self.add(1)
80 }
81
82 #[must_use]
83 pub const fn previous(self) -> Self {
85 self.add(12)
87 }
88
89 #[must_use]
93 pub const fn name(self) -> &'static str {
94 use Month::*;
95
96 match self {
97 March => "March",
98 April => "April",
99 May => "May",
100 June => "June",
101 July => "July",
102 August => "August",
103 September => "September",
104 October => "October",
105 November => "November",
106 December => "December",
107 January => "January",
108 February => "February",
109 Addenduary => "Addenduary",
110 }
111 }
112
113 const fn idx0(self) -> u8 {
114 self as u8
115 }
116
117 const fn idx0_to_month(m: u8) -> Month {
118 use Month::*;
119
120 match m {
121 0 => March,
122 1 => April,
123 2 => May,
124 3 => June,
125 4 => July,
126 5 => August,
127 6 => September,
128 7 => October,
129 8 => November,
130 9 => December,
131 10 => January,
132 11 => February,
133 12 => Addenduary,
134 _ => panic!(),
135 }
136 }
137}
138
139impl Display for Month {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 write!(f, "{}", self.name())
142 }
143}
144
145macro_rules! num_trait_impl {
147 ($type:ident) => {
148 impl From<Month> for $type {
149 fn from(value: Month) -> Self {
150 value.ord() as $type
151 }
152 }
153
154 impl TryFrom<$type> for Month {
155 type Error = ();
156
157 fn try_from(value: $type) -> Result<Self, Self::Error> {
158 if (1..=13).contains(&value) {
159 return Ok(Month::new(value as u8).unwrap());
160 } else {
161 Err(())
162 }
163 }
164 }
165
166 impl core::ops::Add<$type> for Month {
167 type Output = Month;
168
169 fn add(self, rhs: $type) -> Month {
170 let reduced = rhs.rem_euclid(13) as u8;
171 Self::add(self, reduced)
172 }
173 }
174
175 impl core::ops::Sub<$type> for Month {
176 type Output = Month;
177
178 fn sub(self, rhs: $type) -> Month {
179 let reduced = rhs.rem_euclid(13) as u8;
180 Self::sub(self, reduced)
181 }
182 }
183 };
184}
185
186num_trait_impl!(u8);
187num_trait_impl!(u16);
188num_trait_impl!(u32);
189num_trait_impl!(u64);
190num_trait_impl!(u128);
191
192num_trait_impl!(i8);
193num_trait_impl!(i16);
194num_trait_impl!(i32);
195num_trait_impl!(i64);
196num_trait_impl!(i128);
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn addition_tests() {
204 assert_eq!(Month::September + 4i8, Month::January);
205 assert_eq!(Month::September + 4u8, Month::January);
206 assert_eq!(Month::September + 4i16, Month::January);
207 assert_eq!(Month::September + 4u16, Month::January);
208 assert_eq!(Month::September + 4i32, Month::January);
209 assert_eq!(Month::September + 4u32, Month::January);
210 assert_eq!(Month::September + 4i64, Month::January);
211 assert_eq!(Month::September + 4u64, Month::January);
212 assert_eq!(Month::September + 4i128, Month::January);
213 assert_eq!(Month::September + 4u128, Month::January);
214 }
215
216 #[test]
217 fn subtraction_tests() {
218 assert_eq!(Month::September - 4i8, Month::May);
219 assert_eq!(Month::September - 4u8, Month::May);
220 assert_eq!(Month::September - 4i16, Month::May);
221 assert_eq!(Month::September - 4u16, Month::May);
222 assert_eq!(Month::September - 4i32, Month::May);
223 assert_eq!(Month::September - 4u32, Month::May);
224 assert_eq!(Month::September - 4i64, Month::May);
225 assert_eq!(Month::September - 4u64, Month::May);
226 assert_eq!(Month::September - 4i128, Month::May);
227 assert_eq!(Month::September - 4u128, Month::May);
228 }
229
230 #[test]
231 fn into_implementation_works() {
232 let m: u8 = Month::March.into();
233 assert_eq!(m, 1);
234
235 let m: i32 = Month::September.into();
236 assert_eq!(m, 7);
237 }
238
239 #[test]
240 fn from_into_round_trip_works() {
241 for m in 1..=13 {
242 let typed: Month = m.try_into().unwrap();
243 let num: i32 = typed.into();
244
245 assert_eq!(m, num);
246 }
247 }
248}