automapper_validation/eval/
timezone.rs1use super::evaluator::ConditionResult;
7
8pub fn is_mesz_utc(dtm_value: &str) -> ConditionResult {
13 let s = dtm_value.trim();
14 if s.len() < 8 {
15 return ConditionResult::Unknown;
16 }
17
18 let year: u32 = match s[0..4].parse() {
19 Ok(v) => v,
20 Err(_) => return ConditionResult::Unknown,
21 };
22 let month: u32 = match s[4..6].parse() {
23 Ok(v) => v,
24 Err(_) => return ConditionResult::Unknown,
25 };
26 let day: u32 = match s[6..8].parse() {
27 Ok(v) => v,
28 Err(_) => return ConditionResult::Unknown,
29 };
30 let hour: u32 = if s.len() >= 10 {
32 match s[8..10].parse() {
33 Ok(v) => v,
34 Err(_) => return ConditionResult::Unknown,
35 }
36 } else {
37 0
38 };
39 let minute: u32 = if s.len() >= 12 {
40 match s[10..12].parse() {
41 Ok(v) => v,
42 Err(_) => return ConditionResult::Unknown,
43 }
44 } else {
45 0
46 };
47
48 if !(1..=12).contains(&month) || !(1..=31).contains(&day) || hour > 23 || minute > 59 {
50 return ConditionResult::Unknown;
51 }
52
53 let march_last_sunday = last_sunday_of_month(year, 3);
55 let october_last_sunday = last_sunday_of_month(year, 10);
56
57 let dt = (month, day, hour, minute);
62 let mesz_start = (3u32, march_last_sunday, 1u32, 0u32);
63 let mesz_end = (10u32, october_last_sunday, 1u32, 0u32);
64
65 let in_mesz = dt >= mesz_start && dt < mesz_end;
66
67 ConditionResult::from(in_mesz)
68}
69
70pub fn is_mez_utc(dtm_value: &str) -> ConditionResult {
74 match is_mesz_utc(dtm_value) {
75 ConditionResult::True => ConditionResult::False,
76 ConditionResult::False => ConditionResult::True,
77 ConditionResult::Unknown => ConditionResult::Unknown,
78 }
79}
80
81fn last_sunday_of_month(year: u32, month: u32) -> u32 {
85 let days_in_month = match month {
86 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
87 4 | 6 | 9 | 11 => 30,
88 2 => {
89 if is_leap_year(year) {
90 29
91 } else {
92 28
93 }
94 }
95 _ => unreachable!("invalid month"),
96 };
97
98 let dow = day_of_week(year, month, days_in_month);
100
101 days_in_month - dow
103}
104
105fn day_of_week(mut year: u32, month: u32, day: u32) -> u32 {
109 const T: [u32; 12] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
110 if month < 3 {
111 year -= 1;
112 }
113 (year + year / 4 - year / 100 + year / 400 + T[(month - 1) as usize] + day) % 7
114}
115
116fn is_leap_year(year: u32) -> bool {
117 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_known_mesz_date() {
126 assert_eq!(is_mesz_utc("202607151200"), ConditionResult::True);
128 }
129
130 #[test]
131 fn test_known_mez_date() {
132 assert_eq!(is_mesz_utc("202601151200"), ConditionResult::False);
134 }
135
136 #[test]
137 fn test_march_transition_2026() {
138 assert_eq!(is_mesz_utc("202603290059"), ConditionResult::False);
141 assert_eq!(is_mesz_utc("202603290100"), ConditionResult::True);
143 }
144
145 #[test]
146 fn test_october_transition_2026() {
147 assert_eq!(is_mesz_utc("202610250059"), ConditionResult::True);
150 assert_eq!(is_mesz_utc("202610250100"), ConditionResult::False);
152 }
153
154 #[test]
155 fn test_short_input() {
156 assert_eq!(is_mesz_utc("2026"), ConditionResult::Unknown);
158 assert_eq!(is_mesz_utc(""), ConditionResult::Unknown);
159 assert_eq!(is_mesz_utc("20260715"), ConditionResult::True); assert_eq!(is_mesz_utc("20260115"), ConditionResult::False); }
163
164 #[test]
165 fn test_invalid_input_returns_unknown() {
166 assert_eq!(is_mesz_utc("abcdefghijkl"), ConditionResult::Unknown);
167 assert_eq!(is_mesz_utc("202613151200"), ConditionResult::Unknown);
169 assert_eq!(is_mesz_utc("202601321200"), ConditionResult::Unknown);
171 }
172
173 #[test]
174 fn test_is_mez_complements_is_mesz() {
175 assert_eq!(is_mez_utc("202607151200"), ConditionResult::False);
177 assert_eq!(is_mez_utc("202601151200"), ConditionResult::True);
179 assert_eq!(is_mez_utc("short"), ConditionResult::Unknown);
181 }
182
183 #[test]
184 fn test_value_with_timezone_suffix() {
185 assert_eq!(is_mesz_utc("202607151200UTC"), ConditionResult::True);
187 assert_eq!(is_mesz_utc("202601151200303"), ConditionResult::False);
188 }
189
190 #[test]
191 fn test_last_sunday_of_march_2026() {
192 assert_eq!(last_sunday_of_month(2026, 3), 29);
195 }
196
197 #[test]
198 fn test_last_sunday_of_october_2026() {
199 assert_eq!(last_sunday_of_month(2026, 10), 25);
201 }
202
203 #[test]
204 fn test_different_years() {
205 assert_eq!(last_sunday_of_month(2025, 3), 30);
207 assert_eq!(last_sunday_of_month(2025, 10), 26);
208
209 assert_eq!(last_sunday_of_month(2024, 3), 31);
211 assert_eq!(last_sunday_of_month(2024, 10), 27);
212 }
213}