#[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;
#[cfg_attr(
feature = "diesel",
derive(FromSqlRow, AsExpression),
sql_type = "diesel::sql_types::Timestamp"
)]
#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
pub struct DateTime {
pub year: i32,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
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;
#[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(),
)
})
}
}
#[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()?,
))
}
}
#[cfg(feature = "diesel")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "diesel")))]
pub mod diesel_mod {
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 {
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> {
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();
}
let mut capture = RE2.captures(
comp.values
.get(0)
.ok_or(FromComponentError::NotEnoughValues)?
.get(0)
.ok_or(FromComponentError::NotEnoughValues)?,
);
if capture.is_none() {
capture = RE1.captures(
comp.values
.get(0)
.ok_or(FromComponentError::NotEnoughValues)?
.get(0)
.ok_or(FromComponentError::NotEnoughValues)?,
);
}
if capture.is_none() {
capture = RE3.captures(
comp.values
.get(0)
.ok_or(FromComponentError::NotEnoughValues)?
.get(0)
.ok_or(FromComponentError::NotEnoughValues)?,
);
}
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(),
})
}
}