1use xso::{AsXml, FromXml};
8
9use crate::date::DateTime;
10use crate::iq::{IqGetPayload, IqResultPayload};
11use crate::ns;
12use chrono::FixedOffset;
13use core::str::FromStr;
14use minidom::Element;
15use xso::error::{Error, FromElementError};
16
17#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
19#[xml(namespace = ns::TIME, name = "time")]
20pub struct TimeQuery;
21
22impl IqGetPayload for TimeQuery {}
23
24#[derive(Debug, Clone)]
26pub struct TimeResult(pub DateTime);
27
28impl IqResultPayload for TimeResult {}
29
30impl TryFrom<Element> for TimeResult {
31 type Error = FromElementError;
32
33 fn try_from(elem: Element) -> Result<TimeResult, FromElementError> {
34 check_self!(elem, "time", TIME);
35 check_no_attributes!(elem, "time");
36
37 let mut tzo = None;
38 let mut utc = None;
39
40 for child in elem.children() {
41 if child.is("tzo", ns::TIME) {
42 if tzo.is_some() {
43 return Err(Error::Other("More than one tzo element in time.").into());
44 }
45 check_no_children!(child, "tzo");
46 check_no_attributes!(child, "tzo");
47 let fake_date = format!("{}{}", "2019-04-22T11:38:00", child.text());
49 let date_time = DateTime::from_str(&fake_date).map_err(Error::text_parse_error)?;
50 tzo = Some(date_time.timezone());
51 } else if child.is("utc", ns::TIME) {
52 if utc.is_some() {
53 return Err(Error::Other("More than one utc element in time.").into());
54 }
55 check_no_children!(child, "utc");
56 check_no_attributes!(child, "utc");
57 let date_time =
58 DateTime::from_str(&child.text()).map_err(Error::text_parse_error)?;
59 match FixedOffset::east_opt(0) {
60 Some(tz) if date_time.timezone() == tz => (),
61 _ => return Err(Error::Other("Non-UTC timezone for utc element.").into()),
62 }
63 utc = Some(date_time);
64 } else {
65 return Err(Error::Other("Unknown child in time element.").into());
66 }
67 }
68
69 let tzo = tzo.ok_or(Error::Other("Missing tzo child in time element."))?;
70 let utc = utc.ok_or(Error::Other("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 #[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}