use chrono::NaiveTime; use crate::models::Timecard; /// Check if two time ranges overlap. fn timecards_overlap(a: &Timecard, b: &Timecard) -> bool { a.from_time < b.to_time && b.from_time < a.to_time } /// Find all overlapping timecard pairs for a day. /// Returns a list of tuples containing the two overlapping timecards. pub fn find_overlapping_timecards( timecards: &[Timecard], ) -> Vec<((NaiveTime, NaiveTime), (NaiveTime, NaiveTime))> { let mut overlaps = Vec::new(); for i in 0..timecards.len() { for j in (i + 1)..timecards.len() { if timecards_overlap(&timecards[i], &timecards[j]) { overlaps.push(( (timecards[i].from_time, timecards[i].to_time), (timecards[j].from_time, timecards[j].to_time), )); } } } overlaps } #[cfg(test)] mod tests { use super::*; fn time(hour: u32, min: u32) -> NaiveTime { NaiveTime::from_hms_opt(hour, min, 0).unwrap() } fn card(from_h: u32, from_m: u32, to_h: u32, to_m: u32) -> Timecard { Timecard::new(time(from_h, from_m), time(to_h, to_m)) } #[test] fn test_no_overlap_adjacent_timecards() { let timecards = vec![card(9, 0, 12, 0), card(13, 0, 17, 0)]; let overlaps = find_overlapping_timecards(&timecards); assert!(overlaps.is_empty()); } #[test] fn test_no_overlap_exact_touch() { // Touching at 12:00 is NOT an overlap (end time = start time) let timecards = vec![card(9, 0, 12, 0), card(12, 0, 17, 0)]; let overlaps = find_overlapping_timecards(&timecards); assert!(overlaps.is_empty()); } #[test] fn test_partial_overlap() { let timecards = vec![card(9, 0, 12, 30), card(12, 0, 13, 0)]; let overlaps = find_overlapping_timecards(&timecards); assert_eq!(overlaps.len(), 1); assert_eq!(overlaps[0].0, (time(9, 0), time(12, 30))); assert_eq!(overlaps[0].1, (time(12, 0), time(13, 0))); } #[test] fn test_full_containment() { // One timecard fully contains another let timecards = vec![card(9, 0, 17, 0), card(10, 0, 11, 0)]; let overlaps = find_overlapping_timecards(&timecards); assert_eq!(overlaps.len(), 1); } #[test] fn test_exact_match_overlap() { let timecards = vec![card(9, 0, 12, 0), card(9, 0, 12, 0)]; let overlaps = find_overlapping_timecards(&timecards); assert_eq!(overlaps.len(), 1); } #[test] fn test_multiple_overlaps_same_day() { // First overlaps with second, and second overlaps with third let timecards = vec![card(9, 0, 11, 0), card(10, 0, 13, 0), card(12, 0, 15, 0)]; let overlaps = find_overlapping_timecards(&timecards); // 9-11 overlaps with 10-13, and 10-13 overlaps with 12-15 assert_eq!(overlaps.len(), 2); } #[test] fn test_single_timecard_no_overlap() { let timecards = vec![card(9, 0, 17, 0)]; let overlaps = find_overlapping_timecards(&timecards); assert!(overlaps.is_empty()); } #[test] fn test_empty_timecards_no_overlap() { let timecards: Vec = vec![]; let overlaps = find_overlapping_timecards(&timecards); assert!(overlaps.is_empty()); } #[test] fn test_three_timecards_all_overlap() { // All three overlap with each other let timecards = vec![card(9, 0, 15, 0), card(10, 0, 16, 0), card(11, 0, 17, 0)]; let overlaps = find_overlapping_timecards(&timecards); // 9-15 with 10-16, 9-15 with 11-17, and 10-16 with 11-17 assert_eq!(overlaps.len(), 3); } }