gpx/parser/
time.rs

1//! time handles parsing of xsd:dateTime.
2
3use std::io::Read;
4
5/// format: [-]CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
6#[cfg(feature = "use-serde")]
7use serde::{Deserialize, Serialize};
8use time::{format_description::well_known::Iso8601, OffsetDateTime, PrimitiveDateTime, UtcOffset};
9
10use crate::errors::GpxResult;
11use crate::parser::{string, Context};
12
13#[derive(Debug, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Hash)]
14#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
15pub struct Time(OffsetDateTime);
16
17impl Time {
18    /// Render time in ISO 8601 format
19    pub fn format(&self) -> GpxResult<String> {
20        self.0.format(&Iso8601::DEFAULT).map_err(From::from)
21    }
22}
23
24impl From<OffsetDateTime> for Time {
25    fn from(t: OffsetDateTime) -> Self {
26        Time(t)
27    }
28}
29
30impl From<Time> for OffsetDateTime {
31    fn from(t: Time) -> Self {
32        t.0
33    }
34}
35
36/// consume consumes an element as a time.
37pub fn consume<R: Read>(context: &mut Context<R>) -> GpxResult<Time> {
38    let time_str = string::consume(context, "time", false)?;
39
40    // Try parsing as ISO 8601 with offset
41    let time = OffsetDateTime::parse(&time_str, &Iso8601::PARSING).or_else(|_| {
42        // Try parsing as ISO 8601 without offset, assuming UTC
43        PrimitiveDateTime::parse(&time_str, &Iso8601::PARSING).map(PrimitiveDateTime::assume_utc)
44    })?;
45
46    Ok(time.to_offset(UtcOffset::UTC).into())
47}
48
49#[cfg(test)]
50mod tests {
51    use crate::GpxVersion;
52
53    use super::consume;
54
55    #[test]
56    fn consume_time() {
57        let result = consume!("<time>1996-12-19T16:39:57-08:00</time>", GpxVersion::Gpx11);
58        assert!(result.is_ok());
59
60        // The following examples are taken from the xsd:dateTime examples.
61        let result = consume!("<time>2001-10-26T21:32:52</time>", GpxVersion::Gpx11);
62        assert!(result.is_ok());
63
64        let result = consume!("<time>2001-10-26T21:32:52+02:00</time>", GpxVersion::Gpx11);
65        assert!(result.is_ok());
66
67        let result = consume!("<time>2001-10-26T19:32:52Z</time>", GpxVersion::Gpx11);
68        assert!(result.is_ok());
69
70        let result = consume!("<time>2001-10-26T19:32:52+00:00</time>", GpxVersion::Gpx11);
71        assert!(result.is_ok());
72
73        let result = consume!("<time>2001-10-26T21:32:52.12679</time>", GpxVersion::Gpx11);
74        assert!(result.is_ok());
75
76        let result = consume!("<time>2001-10-26T21:32</time>", GpxVersion::Gpx11);
77        assert!(result.is_ok());
78
79        // These are invalid, again, from xsd:dateTime examples.
80        let result = consume!("<time>2001-10-26</time>", GpxVersion::Gpx11);
81        assert!(result.is_err());
82
83        let result = consume!("<time>2001-10-26T25:32:52+02:00</time>", GpxVersion::Gpx11);
84        assert!(result.is_err());
85
86        let result = consume!("<time>01-10-26T21:32</time>", GpxVersion::Gpx11);
87        assert!(result.is_err());
88
89        // TODO we currently don't allow for negative years although the standard demands it
90        //  see https://www.w3.org/TR/xmlschema-2/#dateTime
91        let result = consume!("<time>-2001-10-26T21:32:52</time>", GpxVersion::Gpx11);
92        assert!(result.is_err());
93
94        // https://github.com/georust/gpx/issues/77
95        let result = consume!("<time>2021-10-10T09:55:20.952</time>", GpxVersion::Gpx11);
96        assert!(result.is_ok());
97    }
98}