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) {
39 366
40 } else {
41 365
42 }
43}
44
45impl CalendarYear {
46 #[must_use]
47 pub fn new(year: i32) -> Self {
48 Self { year }
49 }
50
51 #[must_use]
52 pub fn year(&self) -> i32 {
53 self.year
54 }
55
56 #[must_use]
57 pub fn is_leap_year(&self) -> bool {
58 is_leap_year(self.year)
59 }
60
61 #[must_use]
62 pub fn days_in_year(&self) -> u16 {
63 days_in_year(self.year)
64 }
65}
66
67pub fn first_day_of_year(year: i32) -> Result<CalendarDate, YearError> {
68 CalendarDate::new(year, 1, 1).map_err(|_| YearError::InvalidDate)
69}
70
71pub fn last_day_of_year(year: i32) -> Result<CalendarDate, YearError> {
72 CalendarDate::new(year, 12, 31).map_err(|_| YearError::InvalidDate)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::{days_in_year, first_day_of_year, is_leap_year, last_day_of_year, CalendarYear};
78 use use_date::CalendarDate;
79
80 #[test]
81 fn checks_leap_years_and_lengths() {
82 let year = CalendarYear::new(2024);
83
84 assert_eq!(year.year(), 2024);
85 assert!(year.is_leap_year());
86 assert_eq!(year.days_in_year(), 366);
87 assert!(!is_leap_year(1900));
88 assert!(is_leap_year(2000));
89 assert_eq!(days_in_year(2023), 365);
90 }
91
92 #[test]
93 fn builds_year_boundary_dates() {
94 assert_eq!(
95 first_day_of_year(2024).unwrap(),
96 CalendarDate::new(2024, 1, 1).unwrap()
97 );
98 assert_eq!(
99 last_day_of_year(2024).unwrap(),
100 CalendarDate::new(2024, 12, 31).unwrap()
101 );
102 }
103}