ogcapi_types/common/
datetime.rs

1use std::str::FromStr;
2use std::{cmp::Ordering, fmt};
3
4use chrono::{DateTime, SecondsFormat, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
8pub enum Datetime {
9    Datetime(DateTime<Utc>),
10    Interval {
11        from: IntervalDatetime,
12        to: IntervalDatetime,
13    },
14}
15
16#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
17pub enum IntervalDatetime {
18    Datetime(DateTime<Utc>),
19    Open,
20}
21
22impl FromStr for IntervalDatetime {
23    type Err = chrono::ParseError;
24
25    fn from_str(s: &str) -> Result<Self, Self::Err> {
26        Ok(match s.trim() {
27            ".." | "" => IntervalDatetime::Open,
28            d => IntervalDatetime::Datetime(DateTime::parse_from_rfc3339(d)?.into()),
29        })
30    }
31}
32
33impl fmt::Display for IntervalDatetime {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        match self {
36            IntervalDatetime::Datetime(d) => {
37                write!(f, "{}", d.to_rfc3339_opts(SecondsFormat::Secs, true))
38            }
39            IntervalDatetime::Open => write!(f, ".."),
40        }
41    }
42}
43
44impl fmt::Display for Datetime {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        match self {
47            Datetime::Datetime(datetime) => {
48                write!(f, "{}", datetime.to_rfc3339_opts(SecondsFormat::Secs, true))
49            }
50            Datetime::Interval { from, to } => write!(f, "{}/{}", from, to),
51        }
52    }
53}
54
55impl FromStr for Datetime {
56    type Err = chrono::ParseError;
57
58    fn from_str(s: &str) -> Result<Self, Self::Err> {
59        if s.contains('/') && !["../..", "../", "/..", "/"].contains(&s.trim()) {
60            let mut datetimes = s.trim().splitn(2, '/');
61
62            let from = datetimes.next().unwrap_or_default().parse()?;
63            let to = datetimes.next().unwrap_or_default().parse()?;
64
65            Ok(Datetime::Interval { from, to })
66        } else {
67            Ok(Datetime::Datetime(DateTime::parse_from_rfc3339(s)?.into()))
68        }
69    }
70}
71
72impl PartialOrd for IntervalDatetime {
73    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
74        match self {
75            IntervalDatetime::Datetime(d) => match other {
76                IntervalDatetime::Datetime(o) => d.partial_cmp(o),
77                IntervalDatetime::Open => Some(Ordering::Less),
78            },
79            IntervalDatetime::Open => Some(Ordering::Less),
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::Datetime;
87    use std::str::FromStr;
88
89    #[test]
90    fn parse_datetime() {
91        let datetime_str = "2018-02-12T23:20:52Z";
92        let datetime = Datetime::from_str(datetime_str).unwrap();
93        assert_eq!(format!("{:#}", datetime), datetime_str)
94    }
95
96    #[test]
97    fn parse_intervals() {
98        let interval_str = "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z";
99        let datetime = Datetime::from_str(interval_str).unwrap();
100        assert_eq!(format!("{:#}", datetime), interval_str);
101
102        let interval_str = "2018-02-12T00:00:00Z/..";
103        let datetime = Datetime::from_str(interval_str).unwrap();
104        assert_eq!(format!("{:#}", datetime), interval_str);
105
106        let interval_str = "../2018-03-18T12:31:12Z";
107        let datetime = Datetime::from_str(interval_str).unwrap();
108        assert_eq!(format!("{:#}", datetime), interval_str)
109    }
110}