2019-04-22 09:58:13 +00:00
|
|
|
|
// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
|
|
|
|
//
|
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
2024-07-09 15:01:42 +00:00
|
|
|
|
use xso::{AsXml, FromXml};
|
2024-06-21 15:54:12 +00:00
|
|
|
|
|
2019-04-22 09:58:13 +00:00
|
|
|
|
use crate::date::DateTime;
|
|
|
|
|
use crate::iq::{IqGetPayload, IqResultPayload};
|
|
|
|
|
use crate::ns;
|
2019-10-22 23:32:41 +00:00
|
|
|
|
use chrono::FixedOffset;
|
2024-07-24 18:28:22 +00:00
|
|
|
|
use minidom::Element;
|
2019-04-22 09:58:13 +00:00
|
|
|
|
use std::str::FromStr;
|
2024-06-21 14:27:43 +00:00
|
|
|
|
use xso::error::{Error, FromElementError};
|
2019-04-22 09:58:13 +00:00
|
|
|
|
|
2024-06-21 15:54:12 +00:00
|
|
|
|
/// An entity time query.
|
2024-07-09 15:01:42 +00:00
|
|
|
|
#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
|
2024-06-21 15:54:12 +00:00
|
|
|
|
#[xml(namespace = ns::TIME, name = "time")]
|
|
|
|
|
pub struct TimeQuery;
|
2019-04-22 09:58:13 +00:00
|
|
|
|
|
|
|
|
|
impl IqGetPayload for TimeQuery {}
|
|
|
|
|
|
2019-04-22 11:33:29 +00:00
|
|
|
|
/// An entity time result, containing an unique DateTime.
|
2019-04-22 09:58:13 +00:00
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct TimeResult(pub DateTime);
|
|
|
|
|
|
|
|
|
|
impl IqResultPayload for TimeResult {}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<Element> for TimeResult {
|
2024-06-21 14:27:43 +00:00
|
|
|
|
type Error = FromElementError;
|
2019-04-22 09:58:13 +00:00
|
|
|
|
|
2024-06-21 14:27:43 +00:00
|
|
|
|
fn try_from(elem: Element) -> Result<TimeResult, FromElementError> {
|
2019-04-22 09:58:13 +00:00
|
|
|
|
check_self!(elem, "time", TIME);
|
|
|
|
|
check_no_attributes!(elem, "time");
|
|
|
|
|
|
|
|
|
|
let mut tzo = None;
|
|
|
|
|
let mut utc = None;
|
|
|
|
|
|
|
|
|
|
for child in elem.children() {
|
|
|
|
|
if child.is("tzo", ns::TIME) {
|
|
|
|
|
if tzo.is_some() {
|
2024-06-21 14:27:43 +00:00
|
|
|
|
return Err(Error::Other("More than one tzo element in time.").into());
|
2019-04-22 09:58:13 +00:00
|
|
|
|
}
|
|
|
|
|
check_no_children!(child, "tzo");
|
|
|
|
|
check_no_attributes!(child, "tzo");
|
|
|
|
|
// TODO: Add a FromStr implementation to FixedOffset to avoid this hack.
|
2022-03-22 22:29:25 +00:00
|
|
|
|
let fake_date = format!("{}{}", "2019-04-22T11:38:00", child.text());
|
2024-06-21 14:27:43 +00:00
|
|
|
|
let date_time = DateTime::from_str(&fake_date).map_err(Error::text_parse_error)?;
|
2019-04-22 09:58:13 +00:00
|
|
|
|
tzo = Some(date_time.timezone());
|
|
|
|
|
} else if child.is("utc", ns::TIME) {
|
|
|
|
|
if utc.is_some() {
|
2024-06-21 14:27:43 +00:00
|
|
|
|
return Err(Error::Other("More than one utc element in time.").into());
|
2019-04-22 09:58:13 +00:00
|
|
|
|
}
|
|
|
|
|
check_no_children!(child, "utc");
|
|
|
|
|
check_no_attributes!(child, "utc");
|
2024-06-21 14:27:43 +00:00
|
|
|
|
let date_time =
|
|
|
|
|
DateTime::from_str(&child.text()).map_err(Error::text_parse_error)?;
|
2022-11-22 17:59:09 +00:00
|
|
|
|
match FixedOffset::east_opt(0) {
|
|
|
|
|
Some(tz) if date_time.timezone() == tz => (),
|
2024-06-21 14:27:43 +00:00
|
|
|
|
_ => return Err(Error::Other("Non-UTC timezone for utc element.").into()),
|
2019-04-22 09:58:13 +00:00
|
|
|
|
}
|
|
|
|
|
utc = Some(date_time);
|
|
|
|
|
} else {
|
2024-06-21 14:27:43 +00:00
|
|
|
|
return Err(Error::Other("Unknown child in time element.").into());
|
2019-04-22 09:58:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-21 14:27:43 +00:00
|
|
|
|
let tzo = tzo.ok_or(Error::Other("Missing tzo child in time element."))?;
|
|
|
|
|
let utc = utc.ok_or(Error::Other("Missing utc child in time element."))?;
|
2019-07-25 23:54:26 +00:00
|
|
|
|
let date = utc.with_timezone(tzo);
|
2019-04-22 09:58:13 +00:00
|
|
|
|
|
|
|
|
|
Ok(TimeResult(date))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<TimeResult> for Element {
|
|
|
|
|
fn from(time: TimeResult) -> Element {
|
2020-03-28 12:07:26 +00:00
|
|
|
|
Element::builder("time", ns::TIME)
|
|
|
|
|
.append(Element::builder("tzo", ns::TIME).append(format!("{}", time.0.timezone())))
|
2019-10-22 23:32:41 +00:00
|
|
|
|
.append(
|
2022-11-22 17:59:09 +00:00
|
|
|
|
Element::builder("utc", ns::TIME).append(
|
|
|
|
|
time.0
|
|
|
|
|
.with_timezone(FixedOffset::east_opt(0).unwrap())
|
|
|
|
|
.format("%FT%TZ"),
|
|
|
|
|
),
|
2019-10-22 23:32:41 +00:00
|
|
|
|
)
|
2019-04-22 09:58:13 +00:00
|
|
|
|
.build()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
// DateTime’s size doesn’t depend on the architecture.
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_size() {
|
|
|
|
|
assert_size!(TimeQuery, 0);
|
|
|
|
|
assert_size!(TimeResult, 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_response() {
|
|
|
|
|
let elem: Element =
|
|
|
|
|
"<time xmlns='urn:xmpp:time'><tzo>-06:00</tzo><utc>2006-12-19T17:58:35Z</utc></time>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let elem1 = elem.clone();
|
|
|
|
|
let time = TimeResult::try_from(elem).unwrap();
|
2022-11-22 17:59:09 +00:00
|
|
|
|
assert_eq!(time.0.timezone(), FixedOffset::west_opt(6 * 3600).unwrap());
|
2019-10-22 23:32:41 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
time.0,
|
|
|
|
|
DateTime::from_str("2006-12-19T12:58:35-05:00").unwrap()
|
|
|
|
|
);
|
2019-04-22 09:58:13 +00:00
|
|
|
|
let elem2 = Element::from(time);
|
|
|
|
|
assert_eq!(elem1, elem2);
|
|
|
|
|
}
|
|
|
|
|
}
|