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 crate::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_size() {
 48        assert_size!(DateTime, 16);
 49    }
 50
 51    #[test]
 52    fn test_simple() {
 53        let date: DateTime = "2002-09-10T23:08:25Z".parse().unwrap();
 54        assert_eq!(date.0.year(), 2002);
 55        assert_eq!(date.0.month(), 9);
 56        assert_eq!(date.0.day(), 10);
 57        assert_eq!(date.0.hour(), 23);
 58        assert_eq!(date.0.minute(), 08);
 59        assert_eq!(date.0.second(), 25);
 60        assert_eq!(date.0.nanosecond(), 0);
 61        assert_eq!(date.0.timezone(), FixedOffset::east(0));
 62    }
 63
 64    #[test]
 65    fn test_invalid_date() {
 66        // There is no thirteenth month.
 67        let error = DateTime::from_str("2017-13-01T12:23:34Z").unwrap_err();
 68        let message = match error {
 69            Error::ChronoParseError(string) => string,
 70            _ => panic!(),
 71        };
 72        assert_eq!(message.description(), "input is out of range");
 73
 74        // Timezone ≥24:00 aren’t allowed.
 75        let error = DateTime::from_str("2017-05-27T12:11:02+25:00").unwrap_err();
 76        let message = match error {
 77            Error::ChronoParseError(string) => string,
 78            _ => panic!(),
 79        };
 80        assert_eq!(message.description(), "input is out of range");
 81
 82        // Timezone without the : separator aren’t allowed.
 83        let error = DateTime::from_str("2017-05-27T12:11:02+0100").unwrap_err();
 84        let message = match error {
 85            Error::ChronoParseError(string) => string,
 86            _ => panic!(),
 87        };
 88        assert_eq!(message.description(), "input contains invalid characters");
 89
 90        // No seconds, error message could be improved.
 91        let error = DateTime::from_str("2017-05-27T12:11+01:00").unwrap_err();
 92        let message = match error {
 93            Error::ChronoParseError(string) => string,
 94            _ => panic!(),
 95        };
 96        assert_eq!(message.description(), "input contains invalid characters");
 97
 98        // TODO: maybe we’ll want to support this one, as per XEP-0082 §4.
 99        let error = DateTime::from_str("20170527T12:11:02+01:00").unwrap_err();
100        let message = match error {
101            Error::ChronoParseError(string) => string,
102            _ => panic!(),
103        };
104        assert_eq!(message.description(), "input contains invalid characters");
105
106        // No timezone.
107        let error = DateTime::from_str("2017-05-27T12:11:02").unwrap_err();
108        let message = match error {
109            Error::ChronoParseError(string) => string,
110            _ => panic!(),
111        };
112        assert_eq!(message.description(), "premature end of input");
113    }
114
115    #[test]
116    fn test_serialise() {
117        let date = DateTime(ChronoDateTime::parse_from_rfc3339("2017-05-21T20:19:55+01:00").unwrap());
118        let attr = date.into_attribute_value();
119        assert_eq!(attr, Some(String::from("2017-05-21T20:19:55+01:00")));
120    }
121}