Detailed changes
@@ -0,0 +1,116 @@
+// Copyright (c) 2017 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/.
+
+use std::str::FromStr;
+
+use minidom::{IntoAttributeValue, IntoElements, ElementEmitter};
+use chrono::{DateTime as ChronoDateTime, FixedOffset};
+
+use error::Error;
+
+/// Implements the DateTime profile of XEP-0082, which represents a
+/// non-recurring moment in time, with an accuracy of seconds or fraction of
+/// seconds, and includes a timezone.
+#[derive(Debug, Clone, PartialEq)]
+pub struct DateTime(ChronoDateTime<FixedOffset>);
+
+impl FromStr for DateTime {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<DateTime, Error> {
+ Ok(DateTime(ChronoDateTime::parse_from_rfc3339(s)?))
+ }
+}
+
+impl IntoAttributeValue for DateTime {
+ fn into_attribute_value(self) -> Option<String> {
+ Some(self.0.to_rfc3339())
+ }
+}
+
+impl IntoElements for DateTime {
+ fn into_elements(self, emitter: &mut ElementEmitter) {
+ emitter.append_text_node(self.0.to_rfc3339())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use chrono::{Datelike, Timelike};
+ use std::error::Error as StdError;
+
+ #[test]
+ fn test_simple() {
+ let date: DateTime = "2002-09-10T23:08:25Z".parse().unwrap();
+ assert_eq!(date.0.year(), 2002);
+ assert_eq!(date.0.month(), 9);
+ assert_eq!(date.0.day(), 10);
+ assert_eq!(date.0.hour(), 23);
+ assert_eq!(date.0.minute(), 08);
+ assert_eq!(date.0.second(), 25);
+ assert_eq!(date.0.nanosecond(), 0);
+ assert_eq!(date.0.timezone(), FixedOffset::east(0));
+ }
+
+ #[test]
+ fn test_invalid_date() {
+ // There is no thirteenth month.
+ let error = DateTime::from_str("2017-13-01T12:23:34Z").unwrap_err();
+ let message = match error {
+ Error::ChronoParseError(string) => string,
+ _ => panic!(),
+ };
+ assert_eq!(message.description(), "input is out of range");
+
+ // Timezone β₯24:00 arenβt allowed.
+ let error = DateTime::from_str("2017-05-27T12:11:02+25:00").unwrap_err();
+ let message = match error {
+ Error::ChronoParseError(string) => string,
+ _ => panic!(),
+ };
+ assert_eq!(message.description(), "input is out of range");
+
+ // Timezone without the : separator arenβt allowed.
+ let error = DateTime::from_str("2017-05-27T12:11:02+0100").unwrap_err();
+ let message = match error {
+ Error::ChronoParseError(string) => string,
+ _ => panic!(),
+ };
+ assert_eq!(message.description(), "input contains invalid characters");
+
+ // No seconds, error message could be improved.
+ let error = DateTime::from_str("2017-05-27T12:11+01:00").unwrap_err();
+ let message = match error {
+ Error::ChronoParseError(string) => string,
+ _ => panic!(),
+ };
+ assert_eq!(message.description(), "input contains invalid characters");
+
+ // TODO: maybe weβll want to support this one, as per XEP-0082 Β§4.
+ let error = DateTime::from_str("20170527T12:11:02+01:00").unwrap_err();
+ let message = match error {
+ Error::ChronoParseError(string) => string,
+ _ => panic!(),
+ };
+ assert_eq!(message.description(), "input contains invalid characters");
+
+ // No timezone.
+ let error = DateTime::from_str("2017-05-27T12:11:02").unwrap_err();
+ let message = match error {
+ Error::ChronoParseError(string) => string,
+ _ => panic!(),
+ };
+ assert_eq!(message.description(), "premature end of input");
+ }
+
+ #[test]
+ fn test_serialise() {
+ let date = DateTime(ChronoDateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap());
+ let attr = date.into_attribute_value();
+ assert_eq!(attr, Some(String::from("2017-05-21T20:19:55+01:00")));
+ }
+}
@@ -7,7 +7,7 @@
use try_from::TryFrom;
use minidom::Element;
-use chrono::{DateTime, FixedOffset};
+use date::DateTime;
use error::Error;
use jid::Jid;
@@ -17,7 +17,7 @@ use ns;
#[derive(Debug, Clone)]
pub struct Delay {
pub from: Option<Jid>,
- pub stamp: DateTime<FixedOffset>,
+ pub stamp: DateTime,
pub data: Option<String>,
}
@@ -32,7 +32,7 @@ impl TryFrom<Element> for Delay {
return Err(Error::ParseError("Unknown child in delay element."));
}
let from = get_attr!(elem, "from", optional);
- let stamp = get_attr!(elem, "stamp", required, stamp, DateTime::parse_from_rfc3339(stamp)?);
+ let stamp = get_attr!(elem, "stamp", required);
let data = match elem.text().as_ref() {
"" => None,
text => Some(text.to_owned()),
@@ -50,7 +50,7 @@ impl From<Delay> for Element {
Element::builder("delay")
.ns(ns::DELAY)
.attr("from", delay.from)
- .attr("stamp", delay.stamp.to_rfc3339())
+ .attr("stamp", delay.stamp)
.append(delay.data)
.build()
}
@@ -60,21 +60,13 @@ impl From<Delay> for Element {
mod tests {
use super::*;
use std::str::FromStr;
- use chrono::{Datelike, Timelike};
#[test]
fn test_simple() {
let elem: Element = "<delay xmlns='urn:xmpp:delay' from='capulet.com' stamp='2002-09-10T23:08:25Z'/>".parse().unwrap();
let delay = Delay::try_from(elem).unwrap();
assert_eq!(delay.from, Some(Jid::from_str("capulet.com").unwrap()));
- assert_eq!(delay.stamp.year(), 2002);
- assert_eq!(delay.stamp.month(), 9);
- assert_eq!(delay.stamp.day(), 10);
- assert_eq!(delay.stamp.hour(), 23);
- assert_eq!(delay.stamp.minute(), 08);
- assert_eq!(delay.stamp.second(), 25);
- assert_eq!(delay.stamp.nanosecond(), 0);
- assert_eq!(delay.stamp.timezone(), FixedOffset::east(0));
+ assert_eq!(delay.stamp, DateTime::from_str("2002-09-10T23:08:25Z").unwrap());
assert_eq!(delay.data, None);
}
@@ -105,7 +97,7 @@ mod tests {
let elem: Element = "<delay xmlns='urn:xmpp:delay' stamp='2002-09-10T23:08:25+00:00'/>".parse().unwrap();
let delay = Delay {
from: None,
- stamp: DateTime::parse_from_rfc3339("2002-09-10T23:08:25Z").unwrap(),
+ stamp: DateTime::from_str("2002-09-10T23:08:25Z").unwrap(),
data: None,
};
let elem2 = delay.into();
@@ -117,7 +109,7 @@ mod tests {
let elem: Element = "<delay xmlns='urn:xmpp:delay' from='juliet@example.org' stamp='2002-09-10T23:08:25+00:00'>Reason</delay>".parse().unwrap();
let delay = Delay {
from: Some(Jid::from_str("juliet@example.org").unwrap()),
- stamp: DateTime::parse_from_rfc3339("2002-09-10T23:08:25Z").unwrap(),
+ stamp: DateTime::from_str("2002-09-10T23:08:25Z").unwrap(),
data: Some(String::from("Reason")),
};
let elem2 = delay.into();
@@ -7,44 +7,20 @@
use try_from::TryFrom;
use minidom::Element;
-use chrono::{DateTime, FixedOffset};
+use date::DateTime;
use error::Error;
use ns;
-#[derive(Debug, Clone)]
-pub struct Idle {
- pub since: DateTime<FixedOffset>,
-}
-
-impl TryFrom<Element> for Idle {
- type Err = Error;
-
- fn try_from(elem: Element) -> Result<Idle, Error> {
- if !elem.is("idle", ns::IDLE) {
- return Err(Error::ParseError("This is not an idle element."));
- }
- for _ in elem.children() {
- return Err(Error::ParseError("Unknown child in idle element."));
- }
- let since = get_attr!(elem, "since", required, since, DateTime::parse_from_rfc3339(since)?);
- Ok(Idle { since: since })
- }
-}
-
-impl From<Idle> for Element {
- fn from(idle: Idle) -> Element {
- Element::builder("idle")
- .ns(ns::IDLE)
- .attr("since", idle.since.to_rfc3339())
- .build()
- }
-}
+generate_element_with_only_attributes!(Idle, "idle", ns::IDLE, [
+ since: DateTime = "since" => required,
+]);
#[cfg(test)]
mod tests {
use super::*;
+ use std::str::FromStr;
use std::error::Error as StdError;
#[test]
@@ -135,7 +111,7 @@ mod tests {
#[test]
fn test_serialise() {
let elem: Element = "<idle xmlns='urn:xmpp:idle:1' since='2017-05-21T20:19:55+01:00'/>".parse().unwrap();
- let idle = Idle { since: DateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap() };
+ let idle = Idle { since: DateTime::from_str("2017-05-21T20:19:55+01:00").unwrap() };
let elem2 = idle.into();
assert_eq!(elem, elem2);
}
@@ -11,9 +11,9 @@ use std::str::FromStr;
use hashes::Hash;
use jingle::{Creator, ContentId};
+use date::DateTime;
use minidom::{Element, IntoAttributeValue};
-use chrono::{DateTime, FixedOffset};
use error::Error;
use ns;
@@ -60,7 +60,7 @@ generate_id!(Desc);
#[derive(Debug, Clone)]
pub struct File {
- pub date: Option<DateTime<FixedOffset>>,
+ pub date: Option<DateTime>,
pub media_type: Option<String>,
pub name: Option<String>,
pub descs: BTreeMap<Lang, Desc>,
@@ -137,7 +137,7 @@ impl From<File> for Element {
if let Some(date) = file.date {
root.append_child(Element::builder("date")
.ns(ns::JINGLE_FT)
- .append(date.to_rfc3339())
+ .append(date)
.build());
}
if let Some(media_type) = file.media_type {
@@ -285,7 +285,7 @@ mod tests {
assert_eq!(desc.file.media_type, Some(String::from("text/plain")));
assert_eq!(desc.file.name, Some(String::from("test.txt")));
assert_eq!(desc.file.descs, BTreeMap::new());
- assert_eq!(desc.file.date, Some(DateTime::parse_from_rfc3339("2015-07-26T21:46:00+01:00").unwrap()));
+ assert_eq!(desc.file.date, DateTime::from_str("2015-07-26T21:46:00+01:00").ok());
assert_eq!(desc.file.size, Some(6144u64));
assert_eq!(desc.file.range, None);
assert_eq!(desc.file.hashes[0].algo, Algo::Sha_1);
@@ -325,6 +325,9 @@ pub mod pubsub;
/// XEP-0077: In-Band Registration
pub mod ibr;
+/// XEP-0082: XMPP Date and Time Profiles
+pub mod date;
+
/// XEP-0085: Chat State Notifications
pub mod chatstates;
@@ -9,7 +9,7 @@ use std::str::FromStr;
use minidom::{Element, IntoAttributeValue};
use jid::Jid;
-use chrono::{DateTime, FixedOffset};
+use date::DateTime;
use error::Error;
@@ -98,7 +98,7 @@ pub enum PubSubEvent {
},
Subscription {
node: NodeName,
- expiry: Option<DateTime<FixedOffset>>,
+ expiry: Option<DateTime>,
jid: Option<Jid>,
subid: Option<SubscriptionId>,
subscription: Option<Subscription>,
@@ -256,7 +256,7 @@ impl From<PubSubEvent> for Element {
Element::builder("subscription")
.ns(ns::PUBSUB_EVENT)
.attr("node", node)
- .attr("expiry", expiry.map(|expiry| expiry.to_rfc3339()))
+ .attr("expiry", expiry)
.attr("jid", jid)
.attr("subid", subid)
.attr("subscription", subscription)