use std::fmt::Display;
use std::fmt::Formatter;
use chrono::Local;
use chrono::NaiveDate;
use rusqlite::ToSql;
use rusqlite::types::FromSql;
use rusqlite::types::FromSqlError;
use rusqlite::types::FromSqlResult;
use rusqlite::types::ToSqlOutput;
use rusqlite::types::ValueRef;
use serde::Serialize;
use crate::error::ErrorReport;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Date(NaiveDate);
impl Date {
pub fn new(naive_date: NaiveDate) -> Self {
Self(naive_date)
}
pub fn today() -> Self {
Self(Local::now().naive_local().date())
}
pub fn into_inner(self) -> NaiveDate {
self.0
}
}
impl Display for Date {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.format("%Y-%m-%d"))
}
}
impl TryFrom<String> for Date {
type Error = ErrorReport;
fn try_from(value: String) -> Result<Self, Self::Error> {
let date = NaiveDate::parse_from_str(&value, "%Y-%m-%d")
.map_err(|_| ErrorReport::new(format!("invalid date: {}", value)))?;
Ok(Date(date))
}
}
impl ToSql for Date {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
let str = self.to_string();
Ok(ToSqlOutput::from(str))
}
}
impl FromSql for Date {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let string: String = FromSql::column_result(value)?;
Date::try_from(string).map_err(|e| FromSqlError::Other(Box::new(e)))
}
}
impl Serialize for Date {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = self.to_string();
serializer.serialize_str(&s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Fallible;
#[test]
fn test_serialize() -> Fallible<()> {
let date = Date::new(NaiveDate::from_ymd_opt(2024, 1, 2).unwrap());
let serialized = serde_json::to_string(&date)?;
assert_eq!(serialized, "\"2024-01-02\"");
Ok(())
}
}