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}