automapper_validation/eval/
timezone.rs1use chrono::{Datelike, NaiveDate};
7
8use super::evaluator::ConditionResult;
9
10pub fn is_mesz_utc(dtm_value: &str) -> ConditionResult {
15 let s = dtm_value.trim();
16 if s.len() < 8 {
17 return ConditionResult::Unknown;
18 }
19
20 let year: i32 = match s[0..4].parse() {
21 Ok(v) => v,
22 Err(_) => return ConditionResult::Unknown,
23 };
24 let month: u32 = match s[4..6].parse() {
25 Ok(v) => v,
26 Err(_) => return ConditionResult::Unknown,
27 };
28 let day: u32 = match s[6..8].parse() {
29 Ok(v) => v,
30 Err(_) => return ConditionResult::Unknown,
31 };
32 let hour: u32 = if s.len() >= 10 {
33 match s[8..10].parse() {
34 Ok(v) => v,
35 Err(_) => return ConditionResult::Unknown,
36 }
37 } else {
38 0
39 };
40 let minute: u32 = if s.len() >= 12 {
41 match s[10..12].parse() {
42 Ok(v) => v,
43 Err(_) => return ConditionResult::Unknown,
44 }
45 } else {
46 0
47 };
48
49 let Some(dt) = NaiveDate::from_ymd_opt(year, month, day)
50 .and_then(|d| d.and_hms_opt(hour, minute, 0))
51 else {
52 return ConditionResult::Unknown;
53 };
54
55 let mesz_start = last_sunday_of_month(year, 3).and_hms_opt(1, 0, 0).unwrap();
56 let mesz_end = last_sunday_of_month(year, 10).and_hms_opt(1, 0, 0).unwrap();
57
58 ConditionResult::from(dt >= mesz_start && dt < mesz_end)
59}
60
61pub fn is_mez_utc(dtm_value: &str) -> ConditionResult {
65 match is_mesz_utc(dtm_value) {
66 ConditionResult::True => ConditionResult::False,
67 ConditionResult::False => ConditionResult::True,
68 ConditionResult::Unknown => ConditionResult::Unknown,
69 }
70}
71
72fn last_sunday_of_month(year: i32, month: u32) -> NaiveDate {
74 let last_day = NaiveDate::from_ymd_opt(year, month + 1, 1)
76 .unwrap_or_else(|| NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap())
77 .pred_opt()
78 .unwrap();
79 let days_since_sunday = last_day.weekday().num_days_from_sunday();
80 last_day - chrono::Duration::days(days_since_sunday as i64)
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_known_mesz_date() {
89 assert_eq!(is_mesz_utc("202607151200"), ConditionResult::True);
90 }
91
92 #[test]
93 fn test_known_mez_date() {
94 assert_eq!(is_mesz_utc("202601151200"), ConditionResult::False);
95 }
96
97 #[test]
98 fn test_march_transition_2026() {
99 assert_eq!(is_mesz_utc("202603290059"), ConditionResult::False);
101 assert_eq!(is_mesz_utc("202603290100"), ConditionResult::True);
102 }
103
104 #[test]
105 fn test_october_transition_2026() {
106 assert_eq!(is_mesz_utc("202610250059"), ConditionResult::True);
108 assert_eq!(is_mesz_utc("202610250100"), ConditionResult::False);
109 }
110
111 #[test]
112 fn test_short_input() {
113 assert_eq!(is_mesz_utc("2026"), ConditionResult::Unknown);
114 assert_eq!(is_mesz_utc(""), ConditionResult::Unknown);
115 assert_eq!(is_mesz_utc("20260715"), ConditionResult::True);
116 assert_eq!(is_mesz_utc("20260115"), ConditionResult::False);
117 }
118
119 #[test]
120 fn test_invalid_input_returns_unknown() {
121 assert_eq!(is_mesz_utc("abcdefghijkl"), ConditionResult::Unknown);
122 assert_eq!(is_mesz_utc("202613151200"), ConditionResult::Unknown);
123 assert_eq!(is_mesz_utc("202601321200"), ConditionResult::Unknown);
124 }
125
126 #[test]
127 fn test_is_mez_complements_is_mesz() {
128 assert_eq!(is_mez_utc("202607151200"), ConditionResult::False);
129 assert_eq!(is_mez_utc("202601151200"), ConditionResult::True);
130 assert_eq!(is_mez_utc("short"), ConditionResult::Unknown);
131 }
132
133 #[test]
134 fn test_value_with_timezone_suffix() {
135 assert_eq!(is_mesz_utc("202607151200UTC"), ConditionResult::True);
136 assert_eq!(is_mesz_utc("202601151200303"), ConditionResult::False);
137 }
138
139 #[test]
140 fn test_last_sunday_of_march_2026() {
141 assert_eq!(last_sunday_of_month(2026, 3), NaiveDate::from_ymd_opt(2026, 3, 29).unwrap());
142 }
143
144 #[test]
145 fn test_last_sunday_of_october_2026() {
146 assert_eq!(last_sunday_of_month(2026, 10), NaiveDate::from_ymd_opt(2026, 10, 25).unwrap());
147 }
148
149 #[test]
150 fn test_different_years() {
151 assert_eq!(last_sunday_of_month(2025, 3), NaiveDate::from_ymd_opt(2025, 3, 30).unwrap());
152 assert_eq!(last_sunday_of_month(2025, 10), NaiveDate::from_ymd_opt(2025, 10, 26).unwrap());
153 assert_eq!(last_sunday_of_month(2024, 3), NaiveDate::from_ymd_opt(2024, 3, 31).unwrap());
154 assert_eq!(last_sunday_of_month(2024, 10), NaiveDate::from_ymd_opt(2024, 10, 27).unwrap());
155 }
156}