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() < 12 {
15 return ConditionResult::Unknown;
16 }
17 let s = &s[..12];
18
19 let year: u32 = match s[0..4].parse() {
20 Ok(v) => v,
21 Err(_) => return ConditionResult::Unknown,
22 };
23 let month: u32 = match s[4..6].parse() {
24 Ok(v) => v,
25 Err(_) => return ConditionResult::Unknown,
26 };
27 let day: u32 = match s[6..8].parse() {
28 Ok(v) => v,
29 Err(_) => return ConditionResult::Unknown,
30 };
31 let hour: u32 = match s[8..10].parse() {
32 Ok(v) => v,
33 Err(_) => return ConditionResult::Unknown,
34 };
35 let minute: u32 = match s[10..12].parse() {
36 Ok(v) => v,
37 Err(_) => return ConditionResult::Unknown,
38 };
39
40 if !(1..=12).contains(&month) || !(1..=31).contains(&day) || hour > 23 || minute > 59 {
42 return ConditionResult::Unknown;
43 }
44
45 let march_last_sunday = last_sunday_of_month(year, 3);
47 let october_last_sunday = last_sunday_of_month(year, 10);
48
49 let dt = (month, day, hour, minute);
54 let mesz_start = (3u32, march_last_sunday, 1u32, 0u32);
55 let mesz_end = (10u32, october_last_sunday, 1u32, 0u32);
56
57 let in_mesz = dt >= mesz_start && dt < mesz_end;
58
59 ConditionResult::from(in_mesz)
60}
61
62pub fn is_mez_utc(dtm_value: &str) -> ConditionResult {
66 match is_mesz_utc(dtm_value) {
67 ConditionResult::True => ConditionResult::False,
68 ConditionResult::False => ConditionResult::True,
69 ConditionResult::Unknown => ConditionResult::Unknown,
70 }
71}
72
73fn last_sunday_of_month(year: u32, month: u32) -> u32 {
77 let days_in_month = match month {
78 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
79 4 | 6 | 9 | 11 => 30,
80 2 => {
81 if is_leap_year(year) {
82 29
83 } else {
84 28
85 }
86 }
87 _ => unreachable!("invalid month"),
88 };
89
90 let dow = day_of_week(year, month, days_in_month);
92
93 days_in_month - dow
95}
96
97fn day_of_week(mut year: u32, month: u32, day: u32) -> u32 {
101 const T: [u32; 12] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
102 if month < 3 {
103 year -= 1;
104 }
105 (year + year / 4 - year / 100 + year / 400 + T[(month - 1) as usize] + day) % 7
106}
107
108fn is_leap_year(year: u32) -> bool {
109 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_known_mesz_date() {
118 assert_eq!(is_mesz_utc("202607151200"), ConditionResult::True);
120 }
121
122 #[test]
123 fn test_known_mez_date() {
124 assert_eq!(is_mesz_utc("202601151200"), ConditionResult::False);
126 }
127
128 #[test]
129 fn test_march_transition_2026() {
130 assert_eq!(is_mesz_utc("202603290059"), ConditionResult::False);
133 assert_eq!(is_mesz_utc("202603290100"), ConditionResult::True);
135 }
136
137 #[test]
138 fn test_october_transition_2026() {
139 assert_eq!(is_mesz_utc("202610250059"), ConditionResult::True);
142 assert_eq!(is_mesz_utc("202610250100"), ConditionResult::False);
144 }
145
146 #[test]
147 fn test_short_input_returns_unknown() {
148 assert_eq!(is_mesz_utc("2026"), ConditionResult::Unknown);
149 assert_eq!(is_mesz_utc(""), ConditionResult::Unknown);
150 assert_eq!(is_mesz_utc("20260715"), ConditionResult::Unknown);
151 }
152
153 #[test]
154 fn test_invalid_input_returns_unknown() {
155 assert_eq!(is_mesz_utc("abcdefghijkl"), ConditionResult::Unknown);
156 assert_eq!(is_mesz_utc("202613151200"), ConditionResult::Unknown);
158 assert_eq!(is_mesz_utc("202601321200"), ConditionResult::Unknown);
160 }
161
162 #[test]
163 fn test_is_mez_complements_is_mesz() {
164 assert_eq!(is_mez_utc("202607151200"), ConditionResult::False);
166 assert_eq!(is_mez_utc("202601151200"), ConditionResult::True);
168 assert_eq!(is_mez_utc("short"), ConditionResult::Unknown);
170 }
171
172 #[test]
173 fn test_value_with_timezone_suffix() {
174 assert_eq!(is_mesz_utc("202607151200UTC"), ConditionResult::True);
176 assert_eq!(is_mesz_utc("202601151200303"), ConditionResult::False);
177 }
178
179 #[test]
180 fn test_last_sunday_of_march_2026() {
181 assert_eq!(last_sunday_of_month(2026, 3), 29);
184 }
185
186 #[test]
187 fn test_last_sunday_of_october_2026() {
188 assert_eq!(last_sunday_of_month(2026, 10), 25);
190 }
191
192 #[test]
193 fn test_different_years() {
194 assert_eq!(last_sunday_of_month(2025, 3), 30);
196 assert_eq!(last_sunday_of_month(2025, 10), 26);
197
198 assert_eq!(last_sunday_of_month(2024, 3), 31);
200 assert_eq!(last_sunday_of_month(2024, 10), 27);
201 }
202}