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 chrono::FixedOffset;
  8use crate::date::DateTime;
  9use crate::iq::{IqGetPayload, IqResultPayload};
 10use crate::ns;
 11use crate::util::error::Error;
 12use minidom::Element;
 13use std::convert::TryFrom;
 14use std::str::FromStr;
 15
 16generate_empty_element!(
 17    /// An entity time query.
 18    TimeQuery, "time", TIME
 19);
 20
 21impl IqGetPayload for TimeQuery {}
 22
 23/// An entity time result, containing an unique DateTime.
 24#[derive(Debug, Clone)]
 25pub struct TimeResult(pub DateTime);
 26
 27impl IqResultPayload for TimeResult {}
 28
 29impl TryFrom<Element> for TimeResult {
 30    type Error = Error;
 31
 32    fn try_from(elem: Element) -> Result<TimeResult, Error> {
 33        check_self!(elem, "time", TIME);
 34        check_no_attributes!(elem, "time");
 35
 36        let mut tzo = None;
 37        let mut utc = None;
 38
 39        for child in elem.children() {
 40            if child.is("tzo", ns::TIME) {
 41                if tzo.is_some() {
 42                    return Err(Error::ParseError("More than one tzo element in time."));
 43                }
 44                check_no_children!(child, "tzo");
 45                check_no_attributes!(child, "tzo");
 46                // TODO: Add a FromStr implementation to FixedOffset to avoid this hack.
 47                let fake_date = String::from("2019-04-22T11:38:00") + &child.text();
 48                let date_time = DateTime::from_str(&fake_date)?;
 49                tzo = Some(date_time.timezone());
 50            } else if child.is("utc", ns::TIME) {
 51                if utc.is_some() {
 52                    return Err(Error::ParseError("More than one utc element in time."));
 53                }
 54                check_no_children!(child, "utc");
 55                check_no_attributes!(child, "utc");
 56                let date_time = DateTime::from_str(&child.text())?;
 57                if date_time.timezone() != FixedOffset::east(0) {
 58                    return Err(Error::ParseError("Non-UTC timezone for utc element."));
 59                }
 60                utc = Some(date_time);
 61            } else {
 62                return Err(Error::ParseError(
 63                    "Unknown child in time element.",
 64                ));
 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")
 79            .ns(ns::TIME)
 80            .append(Element::builder("tzo")
 81                    .append(format!("{}", time.0.timezone()))
 82                    .build())
 83            .append(Element::builder("utc")
 84                    .append(time.0.with_timezone(&FixedOffset::east(0)).format("%FT%TZ"))
 85                    .build())
 86            .build()
 87    }
 88}
 89
 90#[cfg(test)]
 91mod tests {
 92    use super::*;
 93
 94    // DateTime’s size doesn’t depend on the architecture.
 95    #[test]
 96    fn test_size() {
 97        assert_size!(TimeQuery, 0);
 98        assert_size!(TimeResult, 16);
 99    }
100
101    #[test]
102    fn parse_response() {
103        let elem: Element =
104            "<time xmlns='urn:xmpp:time'><tzo>-06:00</tzo><utc>2006-12-19T17:58:35Z</utc></time>"
105                .parse()
106                .unwrap();
107        let elem1 = elem.clone();
108        let time = TimeResult::try_from(elem).unwrap();
109        assert_eq!(time.0.timezone(), FixedOffset::west(6 * 3600));
110        assert_eq!(time.0, DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap());
111        let elem2 = Element::from(time);
112        assert_eq!(elem1, elem2);
113    }
114}