dw_models/
timeinterval.rs

1use std::cmp::{max, min};
2use std::fmt;
3
4use serde::de::{self, Deserialize, Deserializer, Visitor};
5
6use chrono::DateTime;
7use chrono::Duration;
8use chrono::Utc;
9
10// TODO: Implement serialize
11
12#[derive(Clone, Debug)]
13pub struct TimeInterval {
14    start: DateTime<Utc>,
15    end: DateTime<Utc>,
16}
17
18#[derive(Debug)]
19pub enum TimeIntervalError {
20    ParseError(),
21}
22
23/// Python versions of many of these functions can be found at https://github.com/ErikBjare/timeslot
24impl TimeInterval {
25    pub fn new(start: DateTime<Utc>, end: DateTime<Utc>) -> TimeInterval {
26        TimeInterval { start, end }
27    }
28
29    pub fn new_from_string(period: &str) -> Result<TimeInterval, TimeIntervalError> {
30        let splits = period.split('/').collect::<Vec<&str>>();
31        if splits.len() != 2 {
32            return Err(TimeIntervalError::ParseError());
33        }
34        let start = match DateTime::parse_from_rfc3339(splits[0]) {
35            Ok(dt) => dt.with_timezone(&Utc),
36            Err(_e) => return Err(TimeIntervalError::ParseError()),
37        };
38        let end = match DateTime::parse_from_rfc3339(splits[1]) {
39            Ok(dt) => dt.with_timezone(&Utc),
40            Err(_e) => return Err(TimeIntervalError::ParseError()),
41        };
42
43        Ok(TimeInterval::new(start, end))
44    }
45
46    pub fn start(&self) -> &DateTime<Utc> {
47        &self.start
48    }
49
50    pub fn end(&self) -> &DateTime<Utc> {
51        &self.end
52    }
53
54    pub fn duration(&self) -> Duration {
55        self.end - self.start
56    }
57
58    /// If intervals are separated by a non-zero gap, return the gap as a new TimeInterval, else None
59    pub fn gap(&self, other: &TimeInterval) -> Option<TimeInterval> {
60        if self.end < other.start {
61            Some(TimeInterval::new(self.end, other.start))
62        } else if other.end < self.start {
63            Some(TimeInterval::new(other.end, self.start))
64        } else {
65            None
66        }
67    }
68
69    /// Joins two intervals together if they don't have a gap, else None
70    pub fn union(&self, other: &TimeInterval) -> Option<TimeInterval> {
71        match self.gap(other) {
72            Some(_) => None,
73            None => Some(TimeInterval::new(
74                min(self.start, other.start),
75                max(self.end, other.end),
76            )),
77        }
78    }
79}
80
81impl fmt::Display for TimeInterval {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        write!(f, "{}/{}", self.start.to_rfc3339(), self.end.to_rfc3339())
84    }
85}
86
87struct TimeIntervalVisitor;
88use serde::de::Unexpected;
89
90impl<'de> Visitor<'de> for TimeIntervalVisitor {
91    type Value = TimeInterval;
92
93    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94        formatter.write_str("an string in ISO timeinterval format (such as 2000-01-01T00:00:00+01:00/2001-02-02T01:01:01+01:00)")
95    }
96
97    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
98    where
99        E: de::Error,
100    {
101        match TimeInterval::new_from_string(&value) {
102            Ok(ti) => Ok(ti),
103            Err(e) => {
104                warn!("{:?}", e);
105                Err(de::Error::invalid_value(Unexpected::Str(value), &self))
106            }
107        }
108    }
109}
110
111impl<'de> Deserialize<'de> for TimeInterval {
112    fn deserialize<D>(deserializer: D) -> Result<TimeInterval, D::Error>
113    where
114        D: Deserializer<'de>,
115    {
116        deserializer.deserialize_str(TimeIntervalVisitor)
117    }
118}
119
120#[test]
121fn test_timeinterval() {
122    use std::str::FromStr;
123
124    let start = DateTime::from_str("2000-01-01T00:00:00Z").unwrap();
125    let end = DateTime::from_str("2000-01-02T00:00:00Z").unwrap();
126    let period_str = "2000-01-01T00:00:00+00:00/2000-01-02T00:00:00+00:00";
127    let duration = end - start;
128    let tp = TimeInterval::new(start, end);
129    assert_eq!(tp.start(), &start);
130    assert_eq!(tp.end(), &end);
131    assert_eq!(tp.duration(), duration);
132    assert_eq!(tp.to_string(), period_str);
133
134    let tp = TimeInterval::new_from_string(period_str).unwrap();
135    assert_eq!(tp.start(), &start);
136    assert_eq!(tp.end(), &end);
137    assert_eq!(tp.duration(), duration);
138    assert_eq!(tp.to_string(), period_str);
139}