1#![forbid(unsafe_code)]
2use 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}