time.rs

  1// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use crate::date::DateTime;
  8use crate::iq::{IqGetPayload, IqResultPayload};
  9use crate::ns;
 10use crate::util::error::Error;
 11use crate::Element;
 12use chrono::FixedOffset;
 13use std::convert::TryFrom;
 14use std::str::FromStr;
 15
 16generate_empty_element!(
 17    /// An entity time query.
 18    TimeQuery,
 19    "time",
 20    TIME
 21);
 22
 23impl IqGetPayload for TimeQuery {}
 24
 25/// An entity time result, containing an unique DateTime.
 26#[derive(Debug, Clone)]
 27pub struct TimeResult(pub DateTime);
 28
 29impl IqResultPayload for TimeResult {}
 30
 31impl TryFrom<Element> for TimeResult {
 32    type Error = Error;
 33
 34    fn try_from(elem: Element) -> Result<TimeResult, Error> {
 35        check_self!(elem, "time", TIME);
 36        check_no_attributes!(elem, "time");
 37
 38        let mut tzo = None;
 39        let mut utc = None;
 40
 41        for child in elem.children() {
 42            if child.is("tzo", ns::TIME) {
 43                if tzo.is_some() {
 44                    return Err(Error::ParseError("More than one tzo element in time."));
 45                }
 46                check_no_children!(child, "tzo");
 47                check_no_attributes!(child, "tzo");
 48                // TODO: Add a FromStr implementation to FixedOffset to avoid this hack.
 49                let fake_date = format!("{}{}", "2019-04-22T11:38:00", child.text());
 50                let date_time = DateTime::from_str(&fake_date)?;
 51                tzo = Some(date_time.timezone());
 52            } else if child.is("utc", ns::TIME) {
 53                if utc.is_some() {
 54                    return Err(Error::ParseError("More than one utc element in time."));
 55                }
 56                check_no_children!(child, "utc");
 57                check_no_attributes!(child, "utc");
 58                let date_time = DateTime::from_str(&child.text())?;
 59                match FixedOffset::east_opt(0) {
 60                    Some(tz) if date_time.timezone() == tz => (),
 61                    _ => return Err(Error::ParseError("Non-UTC timezone for utc element.")),
 62                }
 63                utc = Some(date_time);
 64            } else {
 65                return Err(Error::ParseError("Unknown child in time element."));
 66            }
 67        }
 68
 69        let tzo = tzo.ok_or(Error::ParseError("Missing tzo child in time element."))?;
 70        let utc = utc.ok_or(Error::ParseError("Missing utc child in time element."))?;
 71        let date = utc.with_timezone(tzo);
 72
 73        Ok(TimeResult(date))
 74    }
 75}
 76
 77impl From<TimeResult> for Element {
 78    fn from(time: TimeResult) -> Element {
 79        Element::builder("time", ns::TIME)
 80            .append(Element::builder("tzo", ns::TIME).append(format!("{}", time.0.timezone())))
 81            .append(
 82                Element::builder("utc", ns::TIME).append(
 83                    time.0
 84                        .with_timezone(FixedOffset::east_opt(0).unwrap())
 85                        .format("%FT%TZ"),
 86                ),
 87            )
 88            .build()
 89    }
 90}
 91
 92#[cfg(test)]
 93mod tests {
 94    use super::*;
 95
 96    // DateTime’s size doesn’t depend on the architecture.
 97    #[test]
 98    fn test_size() {
 99        assert_size!(TimeQuery, 0);
100        assert_size!(TimeResult, 16);
101    }
102
103    #[test]
104    fn parse_response() {
105        let elem: Element =
106            "<time xmlns='urn:xmpp:time'><tzo>-06:00</tzo><utc>2006-12-19T17:58:35Z</utc></time>"
107                .parse()
108                .unwrap();
109        let elem1 = elem.clone();
110        let time = TimeResult::try_from(elem).unwrap();
111        assert_eq!(time.0.timezone(), FixedOffset::west_opt(6 * 3600).unwrap());
112        assert_eq!(
113            time.0,
114            DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap()
115        );
116        let elem2 = Element::from(time);
117        assert_eq!(elem1, elem2);
118    }
119}