Skip to main content

use_month/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive month helpers.
3//!
4//! These helpers expose explicit month names and Gregorian days-in-month logic.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use use_month::{Month, days_in_month, month_from_number};
10//!
11//! let month = month_from_number(2).unwrap();
12//!
13//! assert_eq!(month, Month::February);
14//! assert_eq!(month.short_name(), "Feb");
15//! assert_eq!(days_in_month(2024, 2).unwrap(), 29);
16//! ```
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Month {
20    January,
21    February,
22    March,
23    April,
24    May,
25    June,
26    July,
27    August,
28    September,
29    October,
30    November,
31    December,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum MonthError {
36    InvalidMonth,
37}
38
39fn is_leap_year(year: i32) -> bool {
40    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
41}
42
43impl Month {
44    #[must_use]
45    pub fn number(&self) -> u8 {
46        match self {
47            Self::January => 1,
48            Self::February => 2,
49            Self::March => 3,
50            Self::April => 4,
51            Self::May => 5,
52            Self::June => 6,
53            Self::July => 7,
54            Self::August => 8,
55            Self::September => 9,
56            Self::October => 10,
57            Self::November => 11,
58            Self::December => 12,
59        }
60    }
61
62    #[must_use]
63    pub fn name(&self) -> &'static str {
64        match self {
65            Self::January => "January",
66            Self::February => "February",
67            Self::March => "March",
68            Self::April => "April",
69            Self::May => "May",
70            Self::June => "June",
71            Self::July => "July",
72            Self::August => "August",
73            Self::September => "September",
74            Self::October => "October",
75            Self::November => "November",
76            Self::December => "December",
77        }
78    }
79
80    #[must_use]
81    pub fn short_name(&self) -> &'static str {
82        match self {
83            Self::January => "Jan",
84            Self::February => "Feb",
85            Self::March => "Mar",
86            Self::April => "Apr",
87            Self::May => "May",
88            Self::June => "Jun",
89            Self::July => "Jul",
90            Self::August => "Aug",
91            Self::September => "Sep",
92            Self::October => "Oct",
93            Self::November => "Nov",
94            Self::December => "Dec",
95        }
96    }
97}
98
99#[must_use]
100pub fn month_from_number(month: u8) -> Option<Month> {
101    Some(match month {
102        1 => Month::January,
103        2 => Month::February,
104        3 => Month::March,
105        4 => Month::April,
106        5 => Month::May,
107        6 => Month::June,
108        7 => Month::July,
109        8 => Month::August,
110        9 => Month::September,
111        10 => Month::October,
112        11 => Month::November,
113        12 => Month::December,
114        _ => return None,
115    })
116}
117
118pub fn days_in_month(year: i32, month: u8) -> Result<u8, MonthError> {
119    Ok(match month {
120        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
121        4 | 6 | 9 | 11 => 30,
122        2 => {
123            if is_leap_year(year) {
124                29
125            } else {
126                28
127            }
128        }
129        _ => return Err(MonthError::InvalidMonth),
130    })
131}
132
133#[must_use]
134pub fn is_valid_month(month: u8) -> bool {
135    month_from_number(month).is_some()
136}
137
138#[cfg(test)]
139mod tests {
140    use super::{days_in_month, is_valid_month, month_from_number, Month, MonthError};
141
142    #[test]
143    fn maps_month_numbers_and_names() {
144        let march = month_from_number(3).unwrap();
145
146        assert_eq!(march, Month::March);
147        assert_eq!(march.number(), 3);
148        assert_eq!(march.name(), "March");
149        assert_eq!(march.short_name(), "Mar");
150        assert!(is_valid_month(12));
151        assert!(!is_valid_month(13));
152    }
153
154    #[test]
155    fn counts_days_in_month() {
156        assert_eq!(days_in_month(2024, 2).unwrap(), 29);
157        assert_eq!(days_in_month(2023, 2).unwrap(), 28);
158        assert_eq!(days_in_month(2024, 4).unwrap(), 30);
159        assert_eq!(days_in_month(2024, 12).unwrap(), 31);
160    }
161
162    #[test]
163    fn rejects_invalid_month_values() {
164        assert_eq!(days_in_month(2024, 0), Err(MonthError::InvalidMonth));
165        assert_eq!(days_in_month(2024, 13), Err(MonthError::InvalidMonth));
166    }
167}