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 = String::from("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                if date_time.timezone() != FixedOffset::east(0) {
 60                    return Err(Error::ParseError("Non-UTC timezone for utc element."));
 61                }
 62                utc = Some(date_time);
 63            } else {
 64                return Err(Error::ParseError("Unknown child in time element."));
 65            }
 66        }
 67
 68        let tzo = tzo.ok_or(Error::ParseError("Missing tzo child in time element."))?;
 69        let utc = utc.ok_or(Error::ParseError("Missing utc child in time element."))?;
 70        let date = utc.with_timezone(tzo);
 71
 72        Ok(TimeResult(date))
 73    }
 74}
 75
 76impl From<TimeResult> for Element {
 77    fn from(time: TimeResult) -> Element {
 78        Element::builder("time", ns::TIME)
 79            .append(Element::builder("tzo", ns::TIME).append(format!("{}", time.0.timezone())))
 80            .append(
 81                Element::builder("utc", ns::TIME)
 82                    .append(time.0.with_timezone(FixedOffset::east(0)).format("%FT%TZ")),
 83            )
 84            .build()
 85    }
 86}
 87
 88#[cfg(test)]
 89mod tests {
 90    use super::*;
 91
 92    // DateTime’s size doesn’t depend on the architecture.
 93    #[test]
 94    fn test_size() {
 95        assert_size!(TimeQuery, 0);
 96        assert_size!(TimeResult, 16);
 97    }
 98
 99    #[test]
100    fn parse_response() {
101        let elem: Element =
102            "<time xmlns='urn:xmpp:time'><tzo>-06:00</tzo><utc>2006-12-19T17:58:35Z</utc></time>"
103                .parse()
104                .unwrap();
105        let elem1 = elem.clone();
106        let time = TimeResult::try_from(elem).unwrap();
107        assert_eq!(time.0.timezone(), FixedOffset::west(6 * 3600));
108        assert_eq!(
109            time.0,
110            DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap()
111        );
112        let elem2 = Element::from(time);
113        assert_eq!(elem1, elem2);
114    }
115}