date.rs

  1// Copyright (c) 2017 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 std::str::FromStr;
  8
  9use minidom::{IntoAttributeValue, IntoElements, ElementEmitter};
 10use chrono::{DateTime as ChronoDateTime, FixedOffset};
 11
 12use error::Error;
 13
 14/// Implements the DateTime profile of XEP-0082, which represents a
 15/// non-recurring moment in time, with an accuracy of seconds or fraction of
 16/// seconds, and includes a timezone.
 17#[derive(Debug, Clone, PartialEq)]
 18pub struct DateTime(ChronoDateTime<FixedOffset>);
 19
 20impl FromStr for DateTime {
 21    type Err = Error;
 22
 23    fn from_str(s: &str) -> Result<DateTime, Error> {
 24        Ok(DateTime(ChronoDateTime::parse_from_rfc3339(s)?))
 25    }
 26}
 27
 28impl IntoAttributeValue for DateTime {
 29    fn into_attribute_value(self) -> Option<String> {
 30        Some(self.0.to_rfc3339())
 31    }
 32}
 33
 34impl IntoElements for DateTime {
 35    fn into_elements(self, emitter: &mut ElementEmitter) {
 36        emitter.append_text_node(self.0.to_rfc3339())
 37    }
 38}
 39
 40#[cfg(test)]
 41mod tests {
 42    use super::*;
 43    use chrono::{Datelike, Timelike};
 44    use std::error::Error as StdError;
 45
 46    #[test]
 47    fn test_simple() {
 48        let date: DateTime = "2002-09-10T23:08:25Z".parse().unwrap();
 49        assert_eq!(date.0.year(), 2002);
 50        assert_eq!(date.0.month(), 9);
 51        assert_eq!(date.0.day(), 10);
 52        assert_eq!(date.0.hour(), 23);
 53        assert_eq!(date.0.minute(), 08);
 54        assert_eq!(date.0.second(), 25);
 55        assert_eq!(date.0.nanosecond(), 0);
 56        assert_eq!(date.0.timezone(), FixedOffset::east(0));
 57    }
 58
 59    #[test]
 60    fn test_invalid_date() {
 61        // There is no thirteenth month.
 62        let error = DateTime::from_str("2017-13-01T12:23:34Z").unwrap_err();
 63        let message = match error {
 64            Error::ChronoParseError(string) => string,
 65            _ => panic!(),
 66        };
 67        assert_eq!(message.description(), "input is out of range");
 68
 69        // Timezone ≥24:00 aren’t allowed.
 70        let error = DateTime::from_str("2017-05-27T12:11:02+25:00").unwrap_err();
 71        let message = match error {
 72            Error::ChronoParseError(string) => string,
 73            _ => panic!(),
 74        };
 75        assert_eq!(message.description(), "input is out of range");
 76
 77        // Timezone without the : separator aren’t allowed.
 78        let error = DateTime::from_str("2017-05-27T12:11:02+0100").unwrap_err();
 79        let message = match error {
 80            Error::ChronoParseError(string) => string,
 81            _ => panic!(),
 82        };
 83        assert_eq!(message.description(), "input contains invalid characters");
 84
 85        // No seconds, error message could be improved.
 86        let error = DateTime::from_str("2017-05-27T12:11+01:00").unwrap_err();
 87        let message = match error {
 88            Error::ChronoParseError(string) => string,
 89            _ => panic!(),
 90        };
 91        assert_eq!(message.description(), "input contains invalid characters");
 92
 93        // TODO: maybe we’ll want to support this one, as per XEP-0082 §4.
 94        let error = DateTime::from_str("20170527T12:11:02+01:00").unwrap_err();
 95        let message = match error {
 96            Error::ChronoParseError(string) => string,
 97            _ => panic!(),
 98        };
 99        assert_eq!(message.description(), "input contains invalid characters");
100
101        // No timezone.
102        let error = DateTime::from_str("2017-05-27T12:11:02").unwrap_err();
103        let message = match error {
104            Error::ChronoParseError(string) => string,
105            _ => panic!(),
106        };
107        assert_eq!(message.description(), "premature end of input");
108    }
109
110    #[test]
111    fn test_serialise() {
112        let date = DateTime(ChronoDateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap());
113        let attr = date.into_attribute_value();
114        assert_eq!(attr, Some(String::from("2017-05-21T20:19:55+01:00")));
115    }
116}