Skip to main content

use_year/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive calendar year helpers.
3//!
4//! These helpers keep leap-year and year-boundary calculations explicit.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use use_year::{CalendarYear, first_day_of_year, last_day_of_year};
10//!
11//! let year = CalendarYear::new(2024);
12//!
13//! assert!(year.is_leap_year());
14//! assert_eq!(year.days_in_year(), 366);
15//! assert_eq!(first_day_of_year(2024).unwrap().month(), 1);
16//! assert_eq!(last_day_of_year(2024).unwrap().day(), 31);
17//! ```
18
19use use_date::CalendarDate;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct CalendarYear {
23    year: i32,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum YearError {
28    InvalidDate,
29}
30
31#[must_use]
32pub fn is_leap_year(year: i32) -> bool {
33    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
34}
35
36#[must_use]
37pub fn days_in_year(year: i32) -> u16 {
38    if is_leap_year(year) { 366 } else { 365 }
39}
40
41impl CalendarYear {
42    #[must_use]
43    pub fn new(year: i32) -> Self {
44        Self { year }
45    }
46
47    #[must_use]
48    pub fn year(&self) -> i32 {
49        self.year
50    }
51
52    #[must_use]
53    pub fn is_leap_year(&self) -> bool {
54        is_leap_year(self.year)
55    }
56
57    #[must_use]
58    pub fn days_in_year(&self) -> u16 {
59        days_in_year(self.year)
60    }
61}
62
63pub fn first_day_of_year(year: i32) -> Result<CalendarDate, YearError> {
64    CalendarDate::new(year, 1, 1).map_err(|_| YearError::InvalidDate)
65}
66
67pub fn last_day_of_year(year: i32) -> Result<CalendarDate, YearError> {
68    CalendarDate::new(year, 12, 31).map_err(|_| YearError::InvalidDate)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::{CalendarYear, days_in_year, first_day_of_year, is_leap_year, last_day_of_year};
74    use use_date::CalendarDate;
75
76    #[test]
77    fn checks_leap_years_and_lengths() {
78        let year = CalendarYear::new(2024);
79
80        assert_eq!(year.year(), 2024);
81        assert!(year.is_leap_year());
82        assert_eq!(year.days_in_year(), 366);
83        assert!(!is_leap_year(1900));
84        assert!(is_leap_year(2000));
85        assert_eq!(days_in_year(2023), 365);
86    }
87
88    #[test]
89    fn builds_year_boundary_dates() {
90        assert_eq!(
91            first_day_of_year(2024).unwrap(),
92            CalendarDate::new(2024, 1, 1).unwrap()
93        );
94        assert_eq!(
95            last_day_of_year(2024).unwrap(),
96            CalendarDate::new(2024, 12, 31).unwrap()
97        );
98    }
99}