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}