1use alloc::borrow::Cow;
8
9use xso::{AsXml, FromXml};
10
11use crate::date::Xep0082;
12use crate::iq::{IqGetPayload, IqResultPayload};
13use crate::ns;
14use chrono::{DateTime, FixedOffset, Utc};
15use core::str::FromStr;
16use xso::{error::Error, text::TextCodec};
17
18struct ColonSeparatedOffset;
19
20impl TextCodec<FixedOffset> for ColonSeparatedOffset {
21 fn decode(&self, s: String) -> Result<FixedOffset, Error> {
22 Ok(FixedOffset::from_str(&s).map_err(Error::text_parse_error)?)
23 }
24
25 fn encode<'x>(&self, value: &'x FixedOffset) -> Result<Option<Cow<'x, str>>, Error> {
26 let offset = value.local_minus_utc();
27 let nminutes = offset / 60;
28 let nseconds = offset % 60;
29 let nhours = nminutes / 60;
30 let nminutes = nminutes % 60;
31 if nseconds == 0 {
32 Ok(Some(Cow::Owned(format!("{:+03}:{:02}", nhours, nminutes))))
33 } else {
34 Ok(Some(Cow::Owned(format!(
35 "{:+03}:{:02}:{:02}",
36 nhours, nminutes, nseconds
37 ))))
38 }
39 }
40}
41
42#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
44#[xml(namespace = ns::TIME, name = "time")]
45pub struct TimeQuery;
46
47impl IqGetPayload for TimeQuery {}
48
49#[derive(Debug, Clone, Copy, FromXml, AsXml)]
51#[xml(namespace = ns::TIME, name = "time")]
52pub struct TimeResult {
53 #[xml(extract(name = "tzo", fields(text(codec = ColonSeparatedOffset))))]
55 pub tz_offset: FixedOffset,
56
57 #[xml(extract(name = "utc", fields(text(codec = Xep0082))))]
59 pub utc: DateTime<Utc>,
60}
61
62impl IqResultPayload for TimeResult {}
63
64impl From<TimeResult> for DateTime<FixedOffset> {
65 fn from(time: TimeResult) -> Self {
66 time.utc.with_timezone(&time.tz_offset)
67 }
68}
69
70impl From<TimeResult> for DateTime<Utc> {
71 fn from(time: TimeResult) -> Self {
72 time.utc.into()
73 }
74}
75
76impl From<DateTime<FixedOffset>> for TimeResult {
77 fn from(dt: DateTime<FixedOffset>) -> Self {
78 let tz_offset = *dt.offset();
79 let utc = dt.with_timezone(&Utc);
80 TimeResult {
81 tz_offset,
82 utc: utc.into(),
83 }
84 }
85}
86
87impl From<DateTime<Utc>> for TimeResult {
88 fn from(dt: DateTime<Utc>) -> Self {
89 TimeResult {
90 tz_offset: FixedOffset::east_opt(0).unwrap(),
91 utc: dt.into(),
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 use minidom::Element;
101
102 #[test]
104 fn test_size() {
105 assert_size!(TimeQuery, 0);
106 assert_size!(TimeResult, 16);
107 }
108
109 #[test]
110 fn parse_response() {
111 let elem: Element =
112 "<time xmlns='urn:xmpp:time'><tzo>-06:00</tzo><utc>2006-12-19T17:58:35Z</utc></time>"
113 .parse()
114 .unwrap();
115 let elem1 = elem.clone();
116 let time = TimeResult::try_from(elem).unwrap();
117 let dt = DateTime::<FixedOffset>::from(time);
118 assert_eq!(dt.timezone(), FixedOffset::west_opt(6 * 3600).unwrap());
119 assert_eq!(
120 dt,
121 DateTime::<FixedOffset>::from_str("2006-12-19T12:58:35-05:00").unwrap()
122 );
123 let elem2 = Element::from(time);
124 assert_eq!(elem1, elem2);
125 }
126}