1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Contains the [`TimeLike`] struct, used to indicate strings that contain timestamps which can
//! be parsed using [`TimeLike.parse`] (if the `chrono` feature is enabled, otherwise the method
//! is not implemented).
//!
//! [`TimeLike`]: time/struct.TimeLike.html
//! [`TimeLike.parse`]: time/struct.TimeLike.html#method.parse

use serde::{self, Serialize, Deserialize};
use std::fmt::Display;

/// Represents a timestamp provided by the Brawl API. If the `chrono` feature is enabled (it is
/// by default), then it is possible to use helper methods to convert it to `chrono` data
/// structures - see [`TimeLike.parse`] (this is recommended, as it is aware of the correct
/// format).
///
/// [`TimeLike.parse`]: #method.parse
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeLike(pub(crate) String);

impl Default for TimeLike {
    /// Returns an initial `TimeLike` instance containing an empty string.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use brawl_api::TimeLike;
    ///
    /// assert_eq!(
    ///     TimeLike::default().to_string(),
    ///     "",
    /// )
    /// ```
    fn default() -> TimeLike {
        TimeLike(String::from(""))
    }
}

impl Display for TimeLike {
    /// Displays the inner string of the `TimeLike` instance.
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl TimeLike {
    /// Returns an immutable reference to the inner string.
    pub fn inner(&self) -> &String {
        &self.0
    }

    /// Returns a mutable reference to the inner string.
    pub fn inner_mut(&mut self) -> &mut String {
        &mut self.0
    }
}

#[cfg(feature = "chrono")]
pub mod _impls {  // public so its implementations can be accessed
    use crate::time::TimeLike;
    use crate::error::{Result, Error};
    use chrono::prelude::*;
    use crate::constants::TIMELIKE_FORMAT;

    impl TimeLike {
        /// Parses this timestamp into a [`chrono::DateTime<chrono::Utc>`], using the API's
        /// format (see [`constants::TIMELIKE_FORMAT`]).
        ///
        /// # Errors
        ///
        /// If the string is invalid, an [`Error::ParseTimeLike`] is returned.
        /// Generally, when requesting from the API, this shouldn't ever happen.
        ///
        /// # Examples
        ///
        /// ```rust
        /// use brawl_api::prelude::*;
        /// use brawl_api::{TimeLike, Battle};
        /// use chrono::{DateTime, Utc};
        ///
        ///
        /// // after obtaining a fetched battlelog's battle
        /// let battle: Battle;
        /// # battle = Battle::default();
        ///
        /// let mut the_time: TimeLike = battle.battle_time;
        /// # let mut _the_str = the_time.inner_mut();
        /// # *_the_str = String::from("20200129T042143.000Z");
        /// let parsed_time: DateTime<Utc> = the_time.parse()?;
        /// // the parsed time of the battle is now available for use.
        ///
        /// # Ok::<(), Box<dyn ::std::error::Error>>(())
        /// ```
        ///
        /// [`chrono::DateTime<chrono::Utc>`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html
        /// [`chrono::ParseError`]: https://docs.rs/chrono/*/chrono/format/struct.ParseError.html
        /// [`Error::ParseTimeLike`]: ../error/enum.Error.html#variant.ParseTimeLike
        /// [`constants::TIMELIKE_FORMAT`]: ../constants/constant.TIMELIKE_FORMAT.html
        pub fn parse(&self) -> Result<DateTime<Utc>> {
            Utc.datetime_from_str(
                &self.0, TIMELIKE_FORMAT
            ).map_err(|e| {
                Error::ParseTimeLike {
                    reason: e.to_string(),
                    offender: Some(self.0.clone()),
                    original_err: Some(e)
                }
            })
        }
    }
}

#[cfg(feature = "datetime")]
pub use _impls::*;

///////////////////////////////////   tests   ///////////////////////////////////

#[cfg(test)]
mod tests {
    use super::TimeLike;

    /// Tests TimeLike to DateTime<Utc> conversion.
    #[test]
    #[cfg(feature = "chrono")]
    fn timelike_to_datetime_convert() -> Result<(), Box<dyn ::std::error::Error>> {
        use chrono::prelude::*;
        let time_str = "20200129T042143.000Z";
        let time = TimeLike(String::from(time_str));

        let dt: DateTime<Utc> = Utc
            .ymd(2020, 01, 29).and_hms(04, 21, 43);

        assert_eq!(time.parse()?, dt);

        Ok(())
    }
}