1use regex::Regex;
2use std::collections::HashMap;
3
4pub fn check_above_12(numeric_dates: &[Vec<i32>]) -> Option<bool> {
11 if numeric_dates.iter().any(|d| index_above_value(0, 12)(d)) {
12 return Some(true);
13 }
14 if numeric_dates.iter().any(|d| index_above_value(1, 12)(d)) {
15 return Some(false);
16 }
17 None
18}
19
20pub fn check_decreasing(numeric_dates: &[Vec<i32>]) -> Option<bool> {
30 let dates_by_year = group_array_by_value_at_index(numeric_dates, 2);
31 let results: Vec<Option<bool>> = dates_by_year
32 .iter()
33 .map(|dates| {
34 let days_first = dates.windows(2).any(|w| is_negative(w[1][0] - w[0][0]));
35 if days_first {
36 return Some(true);
37 }
38
39 let days_second = dates.windows(2).any(|w| is_negative(w[1][1] - w[0][1]));
40 if days_second {
41 return Some(false);
42 }
43
44 None
45 })
46 .collect();
47
48 if results.iter().any(|&r| r == Some(true)) {
49 return Some(true);
50 }
51 if results.iter().any(|&r| r == Some(false)) {
52 return Some(false);
53 }
54
55 None
56}
57
58pub fn change_frequency_analysis(numeric_dates: &[Vec<i32>]) -> Option<bool> {
65 let diffs: Vec<Vec<i32>> = numeric_dates
66 .windows(2)
67 .map(|w| {
68 w[1].iter()
69 .zip(w[0].iter())
70 .map(|(a, b)| (a - b).abs())
71 .collect()
72 })
73 .collect();
74
75 let (first, second) = diffs.iter().fold((0, 0), |(mut acc_f, mut acc_s), diff| {
76 acc_f += diff[0];
77 acc_s += diff[1];
78 (acc_f, acc_s)
79 });
80
81 if first > second {
82 return Some(true);
83 }
84 if first < second {
85 return Some(false);
86 }
87
88 None
89}
90
91pub fn days_before_months(numeric_dates: &[Vec<i32>]) -> Option<bool> {
98 check_above_12(numeric_dates)
99 .or_else(|| check_decreasing(numeric_dates))
100 .or_else(|| change_frequency_analysis(numeric_dates))
101}
102
103pub fn normalize_date(year: &str, month: &str, day: &str) -> (String, String, String) {
106 let normalized_year = if year.len() <= 2 {
108 format!("20{:0>2}", year)
109 } else {
110 year.to_string()
111 };
112
113 (
114 normalized_year,
115 format!("{:0>2}", month),
116 format!("{:0>2}", day),
117 )
118}
119
120pub fn order_date_components(date: &str) -> (String, String, String) {
123 let parts: Vec<&str> = date
124 .split(|c| c == '-' || c == '/' || c == '.')
125 .map(|s| s.trim())
126 .collect();
127 let a = parts[0];
128 let b = parts[1];
129 let c = parts[2];
130
131 let max_len = a.len().max(b.len()).max(c.len());
132
133 if c.len() == max_len {
134 (a.to_string(), b.to_string(), c.to_string())
135 } else if b.len() == max_len {
136 (a.to_string(), c.to_string(), b.to_string())
137 } else {
138 (b.to_string(), c.to_string(), a.to_string())
139 }
140}
141
142pub fn convert_time_12_to_24(time: &str, ampm: &str) -> String {
144 let re = Regex::new(r"[:.]").unwrap();
145 let parts: Vec<&str> = re.split(time).collect();
146
147 let mut hours = parts[0].parse::<i32>().unwrap();
148 let minutes = parts[1];
149 let seconds = if parts.len() > 2 {
150 Some(parts[2])
151 } else {
152 None
153 };
154
155 if hours == 12 {
156 hours = 0;
157 }
158
159 if ampm == "PM" {
160 hours += 12;
161 }
162
163 if let Some(seconds) = seconds {
164 format!("{:02}:{}:{}", hours, minutes, seconds)
165 } else {
166 format!("{:02}:{}", hours, minutes)
167 }
168}
169
170pub fn normalize_time(time: &str) -> String {
172 let re = Regex::new(r"[:.]").unwrap();
173 let parts: Vec<&str> = re.split(time).collect();
174
175 let hours = parts[0];
176 let minutes = parts[1];
177 let seconds = if parts.len() > 2 { parts[2] } else { "00" };
178
179 format!("{:0>2}:{}:{}", hours, minutes, seconds)
180}
181
182pub fn normalize_ampm(ampm: &str) -> String {
184 ampm.replace(|c: char| !c.is_alphabetic(), "")
185 .to_uppercase()
186}
187
188pub fn index_above_value(index: usize, value: i32) -> impl Fn(&[i32]) -> bool {
191 move |array: &[i32]| array[index] > value
192}
193
194pub fn is_negative(number: i32) -> bool {
198 number < 0
199}
200
201pub fn group_array_by_value_at_index<T: Clone>(array: &[Vec<T>], index: usize) -> Vec<Vec<Vec<T>>>
204where
205 T: std::cmp::Eq + std::hash::Hash + ToString,
206{
207 let mut map: HashMap<String, Vec<Vec<T>>> = HashMap::new();
208
209 for item in array {
210 let key = format!("_{}", item[index].to_string());
211 map.entry(key).or_default().push(item.clone());
212 }
213
214 map.into_values().collect()
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_check_above_12() {
223 let days_first = vec![vec![3, 6, 2017], vec![13, 11, 2017], vec![26, 12, 2017]];
224 let months_first = vec![vec![4, 2, 2017], vec![6, 11, 2017], vec![12, 13, 2017]];
225 let undetectable = vec![vec![4, 6, 2017], vec![11, 10, 2017], vec![12, 12, 2017]];
226
227 assert_eq!(check_above_12(&days_first), Some(true));
228 assert_eq!(check_above_12(&months_first), Some(false));
229 assert_eq!(check_above_12(&undetectable), None);
230 }
231
232 #[test]
233 fn test_check_decreasing() {
234 let days_first = vec![vec![8, 3, 2017], vec![10, 5, 2017], vec![6, 9, 2017]];
235 let months_first = vec![vec![6, 3, 2017], vec![8, 5, 2017], vec![9, 4, 2017]];
236 let undetectable = vec![vec![1, 1, 2017], vec![3, 3, 2017], vec![6, 6, 2017]];
237 let different_years1 = vec![vec![8, 3, 2017], vec![7, 5, 2017], vec![6, 9, 2018]];
238 let different_years2 = vec![vec![8, 3, 2017], vec![10, 2, 2017], vec![6, 9, 2018]];
239 let different_years3 = vec![vec![8, 3, 2017], vec![10, 5, 2017], vec![6, 9, 2018]];
240
241 assert_eq!(check_decreasing(&days_first), Some(true));
242 assert_eq!(check_decreasing(&months_first), Some(false));
243 assert_eq!(check_decreasing(&undetectable), None);
244 assert_eq!(check_decreasing(&different_years1), Some(true));
245 assert_eq!(check_decreasing(&different_years2), Some(false));
246 assert_eq!(check_decreasing(&different_years3), None);
247 }
248
249 #[test]
250 fn test_change_frequency_analysis() {
251 let days_first = vec![vec![3, 4, 2017], vec![7, 5, 2017], vec![11, 6, 2017]];
252 let months_first = vec![vec![1, 1, 2017], vec![1, 3, 2017], vec![2, 7, 2017]];
253 let undetectable = vec![vec![6, 3, 2017], vec![8, 5, 2017], vec![9, 4, 2017]];
254
255 assert_eq!(change_frequency_analysis(&days_first), Some(true));
256 assert_eq!(change_frequency_analysis(&months_first), Some(false));
257 assert_eq!(change_frequency_analysis(&undetectable), None);
258 }
259
260 #[test]
261 fn test_normalize_date() {
262 let expected = ("2011".to_string(), "03".to_string(), "04".to_string());
263
264 assert_eq!(normalize_date("11", "3", "4"), expected);
265 assert_eq!(normalize_date("2011", "03", "04"), expected);
266 }
267
268 #[test]
269 fn test_convert_time_12_to_24() {
270 assert_eq!(convert_time_12_to_24("12:00", "PM"), "12:00");
271 assert_eq!(convert_time_12_to_24("12:00", "AM"), "00:00");
272 assert_eq!(convert_time_12_to_24("05:06", "PM"), "17:06");
273 assert_eq!(convert_time_12_to_24("07:19", "AM"), "07:19");
274 assert_eq!(convert_time_12_to_24("01:02:34", "PM"), "13:02:34");
275 assert_eq!(convert_time_12_to_24("02:04:54", "AM"), "02:04:54");
276 }
277
278 #[test]
279 fn test_normalize_ampm() {
280 assert_eq!(normalize_ampm("am"), "AM");
281 assert_eq!(normalize_ampm("pm"), "PM");
282 assert_eq!(normalize_ampm("AM"), "AM");
283 assert_eq!(normalize_ampm("PM"), "PM");
284 assert_eq!(normalize_ampm("a.m."), "AM");
285 assert_eq!(normalize_ampm("p.m."), "PM");
286 assert_eq!(normalize_ampm("A.M."), "AM");
287 assert_eq!(normalize_ampm("P.M."), "PM");
288 }
289
290 #[test]
291 fn test_normalize_time() {
292 assert_eq!(normalize_time("12:34"), "12:34:00");
293 assert_eq!(normalize_time("1:23:45"), "01:23:45");
294 assert_eq!(normalize_time("12:34:56"), "12:34:56");
295 }
296
297 #[test]
298 fn test_index_above_value() {
299 let array = vec![34, 16];
300
301 assert!(index_above_value(0, 33)(&array));
302 assert!(!index_above_value(0, 34)(&array));
303 assert!(index_above_value(1, 15)(&array));
304 assert!(!index_above_value(1, 16)(&array));
305 assert!(!index_above_value(1, 17)(&array));
306 }
307
308 #[test]
309 fn test_is_negative() {
310 assert!(is_negative(-1));
311 assert!(is_negative(-15));
312 assert!(is_negative(std::i32::MIN));
313
314 assert!(!is_negative(0));
315 assert!(!is_negative(1));
316 assert!(!is_negative(15));
317 assert!(!is_negative(std::i32::MAX));
318 }
319
320 #[test]
321 fn test_group_array_by_value_at_index() {
322 let array = vec![
323 vec!["8".to_string(), "30".to_string(), "sample".to_string()],
324 vec!["9".to_string(), "50".to_string(), "sample".to_string()],
325 vec!["6".to_string(), "30".to_string(), "sample".to_string()],
326 ];
327
328 let grouped_by_0 = group_array_by_value_at_index(&array, 0);
329 assert_eq!(grouped_by_0.len(), 3);
330
331 let grouped_by_1 = group_array_by_value_at_index(&array, 1);
332 assert_eq!(grouped_by_1.len(), 2);
333
334 let grouped_by_2 = group_array_by_value_at_index(&array, 2);
335 assert_eq!(grouped_by_2.len(), 1);
336 }
337}