dw_transform/
period_union.rs

1use super::sort::sort_by_timestamp;
2use dw_models::Event;
3use std::collections::VecDeque;
4
5/// Takes a list of two events and returns a new list of events covering the union
6/// of the timeperiods contained in the eventlists with no overlapping events.
7///
8/// dw-core implementation: https://github.com/DeskWatch/dw-core/blob/b11fbe08a0405dec01380493f7b3261163cc6878/dw_transform/filter_period_intersect.py#L92
9///
10/// WARNING: This function strips all data from events as it cannot keep it consistent.
11///
12///
13/// # Example
14/// ```ignore
15///   events1   |   -------       --------- |
16///   events2   | ------  ---  --    ----   |
17///   result    | -----------  -- --------- |
18/// ```
19pub fn period_union(events1: &[Event], events2: &[Event]) -> Vec<Event> {
20    let mut sorted_events: VecDeque<Event> = VecDeque::new();
21    sorted_events.extend(sort_by_timestamp([events1, events2].concat()));
22
23    let mut events_union = Vec::new();
24
25    if !events1.is_empty() {
26        events_union.push(sorted_events.pop_front().unwrap())
27    }
28
29    for e in sorted_events {
30        let last_event = events_union.last().unwrap();
31
32        let e_p = e.interval();
33        let le_p = last_event.interval();
34
35        match e_p.union(&le_p) {
36            Some(new_period) => {
37                // If no gap and could be unioned, modify last event
38                let mut e_mod = events_union.pop().unwrap();
39                e_mod.duration = new_period.duration();
40                events_union.push(e_mod);
41            }
42            None => {
43                // If gap and could not be unioned, push event
44                events_union.push(e);
45            }
46        }
47    }
48
49    events_union
50        .drain(..)
51        .map(|mut e| {
52            e.data = json_map! {};
53            e
54        })
55        .collect()
56}
57
58#[cfg(test)]
59mod tests {
60    use std::str::FromStr;
61
62    use chrono::DateTime;
63    use chrono::Duration;
64    use chrono::Utc;
65    use serde_json::json;
66
67    use dw_models::Event;
68
69    use super::period_union;
70
71    #[test]
72    fn test_period_union_empty() {
73        let e_result = period_union(&[], &[]);
74        assert_eq!(e_result.len(), 0);
75    }
76
77    #[test]
78    fn test_period_union() {
79        let e1 = Event {
80            id: None,
81            timestamp: DateTime::from_str("2000-01-01T00:00:01Z").unwrap(),
82            duration: Duration::seconds(1),
83            data: json_map! {"test": json!(1)},
84        };
85
86        let mut e2 = e1.clone();
87        e2.timestamp = DateTime::from_str("2000-01-01T00:00:02Z").unwrap();
88
89        let e_result = period_union(&[e1], &[e2]);
90        assert_eq!(e_result.len(), 1);
91
92        let dt: DateTime<Utc> = DateTime::from_str("2000-01-01T00:00:01.000Z").unwrap();
93        assert_eq!(e_result[0].timestamp, dt);
94        assert_eq!(e_result[0].duration, Duration::milliseconds(2000));
95    }
96
97    /// Make sure nothing gets done when nothing to union (gaps present)
98    #[test]
99    fn test_period_union_nop() {
100        let e1 = Event {
101            id: None,
102            timestamp: DateTime::from_str("2000-01-01T00:00:01Z").unwrap(),
103            duration: Duration::seconds(1),
104            data: json_map! {"test": json!(1)},
105        };
106
107        let mut e2 = e1.clone();
108        e2.timestamp = DateTime::from_str("2000-01-01T00:00:03Z").unwrap();
109
110        let e_result = period_union(&[e1], &[e2]);
111        assert_eq!(e_result.len(), 2);
112    }
113}