contack 0.9.2

A simple and easy contact library.
Documentation
//! Time properties in VCARD

#[cfg(feature = "chrono")]
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};

#[cfg(feature = "rolodex_support")]
use ::rolodex::value::TypeOrRaw;

#[cfg(feature = "read_write")]
use crate::read_write::component::Component;
#[cfg(feature = "read_write")]
use crate::read_write::error::FromComponentError;
#[cfg(feature = "read_write")]
use regex::Regex;

/// Represents some time. Can be converted from chrono.
#[cfg_attr(
    feature = "diesel",
    derive(FromSqlRow, AsExpression),
    sql_type = "diesel::sql_types::Timestamp"
)]
#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
pub struct DateTime {
    /// The years since 0 CE
    pub year: i32,
    /// The month of the year 1-12
    pub month: u8,
    /// The day of the month 1-(28|30|31)
    pub day: u8,
    /// The hour in the day 0-23
    pub hour: u8,
    /// The minute in the hour
    pub minute: u8,
    /// The second in the minute
    pub second: u8,
}

impl DateTime {
    #[must_use]
    pub const fn new(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
    ) -> Self {
        Self {
            year,
            month,
            day,
            hour,
            minute,
            second,
        }
    }
}

#[derive(Clone, Copy)]
pub struct DateTimeError;

// To Convert to Chrono NaiveDateTime
#[cfg_attr(feature = "dox", doc(cfg(feature = "chrono")))]
#[cfg(feature = "chrono")]
impl From<DateTime> for Option<chrono::NaiveDateTime> {
    fn from(them: DateTime) -> Self {
        NaiveDate::from_ymd_opt(them.year, them.month.into(), them.day.into())
            .map(|x| {
                x.and_hms(
                    them.hour.into(),
                    them.minute.into(),
                    them.second.into(),
                )
            })
    }
}

// To Convert from Chrono NaiveDateTime
#[cfg(feature = "chrono")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "chrono")))]
impl TryFrom<chrono::NaiveDateTime> for DateTime {
    type Error = std::num::TryFromIntError;

    fn try_from(dt: chrono::NaiveDateTime) -> Result<Self, Self::Error> {
        Ok(Self::new(
            dt.year(),
            dt.month().try_into()?,
            dt.day().try_into()?,
            dt.hour().try_into()?,
            dt.minute().try_into()?,
            dt.second().try_into()?,
        ))
    }
}

// For Diesel Support
#[cfg(feature = "diesel")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "diesel")))]
pub mod diesel_mod {
    // Imports
    use super::*;
    use diesel::{
        backend::Backend,
        deserialize,
        deserialize::FromSql,
        pg::Pg,
        serialize,
        serialize::{Output, ToSql},
        sql_types::Timestamp,
    };

    use std::io::Write;

    impl FromSql<Timestamp, Pg> for DateTime {
        fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
            let datetime: chrono::NaiveDateTime =
                FromSql::<Timestamp, Pg>::from_sql(bytes)?;

            Ok(datetime.try_into()?)
        }
    }

    impl<DB> ToSql<Timestamp, DB> for DateTime
    where
        DB: Backend,
        NaiveDateTime: ToSql<Timestamp, DB>,
    {
        fn to_sql<W: Write>(
            &self,
            out: &mut Output<W, DB>,
        ) -> serialize::Result {
            let dt: Option<NaiveDateTime> = self.clone().into();
            let dt: NaiveDateTime = dt.unwrap();
            dt.to_sql(out)
        }
    }
}

#[cfg(feature = "read_write")]
impl From<DateTime> for Component {
    fn from(dt: DateTime) -> Self {
        Self {
            // Note: Datetime can be used for more than one property so we
            // are just giving it a default name.
            name: "DATETIME".to_string(),
            values: vec![vec![format!(
                "{:04}{:02}{:02}T{:02}{:02}{:02}Z",
                dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
            )]],
            ..Self::default()
        }
    }
}

#[cfg(feature = "read_write")]
impl TryFrom<Component> for DateTime {
    type Error = FromComponentError;

    fn try_from(comp: Component) -> Result<Self, Self::Error> {
        // Define some regexes which could parse a date.
        lazy_static! {
            static ref RE1: Regex = Regex::new(
                r#"^(?P<year>\d{4})(?P<month>\d{2})(?P<day>\d{2})$"#
            ).unwrap();
            static ref RE2: Regex = Regex::new(
                r#"^(?P<year>\d{4})(?P<month>\d{2})(?P<day>\d{2})T(?P<hour>\d{2})(?P<minute>\d{2})(?P<second>\d{2})Z?$"#
            ).unwrap();
            static ref RE3: Regex = Regex::new(
                r#"^T(?P<hour>\d{2})(?P<minute>\d{2})(?P<second>\d{2})Z?$"#
            ).unwrap();
        }
        // Try to parse, first with the most detailed (RE2)
        let mut capture = RE2.captures(
            comp.values
                .get(0)
                .ok_or(FromComponentError::NotEnoughValues)?
                .get(0)
                .ok_or(FromComponentError::NotEnoughValues)?,
        );

        // Failing that we will assume its just a date
        if capture.is_none() {
            capture = RE1.captures(
                comp.values
                    .get(0)
                    .ok_or(FromComponentError::NotEnoughValues)?
                    .get(0)
                    .ok_or(FromComponentError::NotEnoughValues)?,
            );
        }

        // Failing still we will hope it is just a time
        if capture.is_none() {
            capture = RE3.captures(
                comp.values
                    .get(0)
                    .ok_or(FromComponentError::NotEnoughValues)?
                    .get(0)
                    .ok_or(FromComponentError::NotEnoughValues)?,
            );
        }

        // Converts the option into a result.
        let capture = capture.ok_or(FromComponentError::InvalidRegex)?;

        Ok(Self {
            year: capture
                .name("year")
                .map(|x| {
                    x.as_str()
                        .parse()
                        .map_err(FromComponentError::ParseIntError)
                })
                .transpose()?
                .unwrap_or_default(),
            month: capture
                .name("month")
                .map(|x| {
                    x.as_str()
                        .parse()
                        .map_err(FromComponentError::ParseIntError)
                })
                .transpose()?
                .unwrap_or_default(),
            day: capture
                .name("day")
                .map(|x| {
                    x.as_str()
                        .parse()
                        .map_err(FromComponentError::ParseIntError)
                })
                .transpose()?
                .unwrap_or_default(),
            hour: capture
                .name("hour")
                .map(|x| {
                    x.as_str()
                        .parse()
                        .map_err(FromComponentError::ParseIntError)
                })
                .transpose()?
                .unwrap_or_default(),
            minute: capture
                .name("minute")
                .map(|x| {
                    x.as_str()
                        .parse()
                        .map_err(FromComponentError::ParseIntError)
                })
                .transpose()?
                .unwrap_or_default(),
            second: capture
                .name("second")
                .map(|x| {
                    x.as_str()
                        .parse()
                        .map_err(FromComponentError::ParseIntError)
                })
                .transpose()?
                .unwrap_or_default(),
        })
    }
}