1#[derive(Debug, Copy, Clone, Eq, PartialEq)]
5pub(crate) enum WeekStart {
6 Sunday = 0,
8 Monday = 1,
10}
11
12pub(crate) fn week_number(week_day: i64, year_day_1: i64, week_start: WeekStart) -> i64 {
21 let year_day = year_day_1 - 1;
22 let start_of_first_week = (year_day - week_day + week_start as i64).rem_euclid(7);
23 (year_day + 7 - start_of_first_week) / 7
24}
25
26pub(crate) fn iso_8601_year_and_week_number(
39 year: i64,
40 week_day: i64,
41 year_day_1: i64,
42) -> (i64, i64) {
43 let year_day = year_day_1 - 1;
44
45 let mut start_of_first_week = (year_day - week_day + 1).rem_euclid(7);
46
47 if start_of_first_week > 3 {
48 start_of_first_week -= 7;
49 }
50
51 if year_day < start_of_first_week {
52 let previous_year = year - 1;
54
55 let previous_year_day = if is_leap_year(previous_year) {
56 366 + year_day
57 } else {
58 365 + year_day
59 };
60
61 return iso_8601_year_and_week_number(previous_year, week_day, previous_year_day + 1);
62 }
63
64 let week_number = (year_day + 7 - start_of_first_week) / 7;
65
66 if week_number >= 52 {
67 let last_year_day = if is_leap_year(year) { 365 } else { 364 };
68
69 let week_day_of_last_year_day = (week_day + last_year_day - year_day) % 7;
70
71 if (1..=3).contains(&week_day_of_last_year_day) {
72 let last_monday = last_year_day - (week_day_of_last_year_day - 1);
73 if year_day >= last_monday {
74 return (year + 1, 1);
76 }
77 }
78 }
79
80 (year, week_number)
82}
83
84fn is_leap_year(year: i64) -> bool {
86 year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_week_number() {
95 assert_eq!(week_number(1, 0, WeekStart::Sunday), 0);
96 assert_eq!(week_number(2, 1, WeekStart::Sunday), 0);
97 assert_eq!(week_number(3, 2, WeekStart::Sunday), 0);
98 assert_eq!(week_number(4, 3, WeekStart::Sunday), 0);
99 assert_eq!(week_number(5, 4, WeekStart::Sunday), 0);
100 assert_eq!(week_number(6, 5, WeekStart::Sunday), 0);
101 assert_eq!(week_number(0, 6, WeekStart::Sunday), 1);
102 assert_eq!(week_number(1, 7, WeekStart::Sunday), 1);
103 assert_eq!(week_number(2, 8, WeekStart::Sunday), 1);
104
105 assert_eq!(week_number(0, 0, WeekStart::Monday), 0);
106 assert_eq!(week_number(1, 1, WeekStart::Monday), 1);
107 assert_eq!(week_number(2, 2, WeekStart::Monday), 1);
108 assert_eq!(week_number(3, 3, WeekStart::Monday), 1);
109 assert_eq!(week_number(4, 4, WeekStart::Monday), 1);
110 assert_eq!(week_number(5, 5, WeekStart::Monday), 1);
111 assert_eq!(week_number(6, 6, WeekStart::Monday), 1);
112 assert_eq!(week_number(7, 7, WeekStart::Monday), 1);
113 assert_eq!(week_number(8, 8, WeekStart::Monday), 2);
114
115 assert_eq!(week_number(0, 365, WeekStart::Sunday), 53);
116 }
117
118 #[test]
119 fn test_iso_8601_year_and_week() {
120 assert_eq!(iso_8601_year_and_week_number(2025, 0, 362), (2025, 52));
121 assert_eq!(iso_8601_year_and_week_number(2025, 1, 363), (2026, 1));
122 assert_eq!(iso_8601_year_and_week_number(2025, 2, 364), (2026, 1));
123 assert_eq!(iso_8601_year_and_week_number(2025, 3, 365), (2026, 1));
124 assert_eq!(iso_8601_year_and_week_number(2026, 4, 1), (2026, 1));
125 assert_eq!(iso_8601_year_and_week_number(2026, 5, 2), (2026, 1));
126 assert_eq!(iso_8601_year_and_week_number(2026, 6, 3), (2026, 1));
127 assert_eq!(iso_8601_year_and_week_number(2026, 0, 4), (2026, 1));
128 assert_eq!(iso_8601_year_and_week_number(2026, 1, 5), (2026, 2));
129
130 assert_eq!(iso_8601_year_and_week_number(2026, 0, 361), (2026, 52));
131 assert_eq!(iso_8601_year_and_week_number(2026, 1, 362), (2026, 53));
132 assert_eq!(iso_8601_year_and_week_number(2026, 2, 363), (2026, 53));
133 assert_eq!(iso_8601_year_and_week_number(2026, 3, 364), (2026, 53));
134 assert_eq!(iso_8601_year_and_week_number(2026, 4, 365), (2026, 53));
135 assert_eq!(iso_8601_year_and_week_number(2027, 5, 1), (2026, 53));
136 assert_eq!(iso_8601_year_and_week_number(2027, 6, 2), (2026, 53));
137 assert_eq!(iso_8601_year_and_week_number(2027, 0, 3), (2026, 53));
138 assert_eq!(iso_8601_year_and_week_number(2027, 1, 4), (2027, 1));
139
140 assert_eq!(iso_8601_year_and_week_number(2020, 0, 362), (2020, 52));
141 assert_eq!(iso_8601_year_and_week_number(2020, 1, 363), (2020, 53));
142 assert_eq!(iso_8601_year_and_week_number(2020, 2, 364), (2020, 53));
143 assert_eq!(iso_8601_year_and_week_number(2020, 3, 365), (2020, 53));
144 assert_eq!(iso_8601_year_and_week_number(2020, 4, 366), (2020, 53));
145 assert_eq!(iso_8601_year_and_week_number(2021, 5, 1), (2020, 53));
146 assert_eq!(iso_8601_year_and_week_number(2021, 6, 2), (2020, 53));
147 assert_eq!(iso_8601_year_and_week_number(2021, 0, 3), (2020, 53));
148 assert_eq!(iso_8601_year_and_week_number(2021, 1, 4), (2021, 1));
149 }
150
151 #[test]
152 fn test_is_leap_year() {
153 assert!(is_leap_year(2000));
154 assert!(!is_leap_year(2001));
155 assert!(is_leap_year(2004));
156 assert!(!is_leap_year(2100));
157 assert!(!is_leap_year(2200));
158 assert!(!is_leap_year(2300));
159 assert!(is_leap_year(2400));
160 }
161
162 #[cfg(feature = "alloc")]
163 #[test]
164 fn test_week_start_debug_is_non_empty() {
165 use alloc::format;
166
167 assert!(!format!("{:?}", WeekStart::Sunday).is_empty());
168 assert!(!format!("{:?}", WeekStart::Monday).is_empty());
169 }
170}