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}