use std::{
ops::{Deref, DerefMut},
sync::{LazyLock, RwLock},
};
use chrono::{Datelike, Days, Duration, Local, Months, NaiveDate, NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{
BaseFormat, GetInner,
error::{DateError, ErrorContext, SpanError},
span::Span,
};
pub(crate) static BASE_DATE_FORMAT: BaseFormat<&'static str> =
LazyLock::new(|| RwLock::new("%Y-%m-%d"));
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Serialize, Deserialize)]
pub enum DateUnit {
Year,
Month,
Day,
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Serialize, Deserialize)]
pub struct Date {
pub(crate) date: NaiveDate,
#[serde(skip)]
#[serde(default = "base_date_format")]
pub(crate) format: String,
}
fn base_date_format() -> String {
BASE_DATE_FORMAT.get().to_string()
}
impl Default for Date {
fn default() -> Self {
Self {
date: NaiveDate::default(),
format: BASE_DATE_FORMAT.get().to_string(),
}
}
}
impl std::fmt::Display for Date {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.date.format(&self.format))
}
}
impl Deref for Date {
type Target = NaiveDate;
fn deref(&self) -> &Self::Target {
&self.date
}
}
impl DerefMut for Date {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.date
}
}
impl Date {
pub fn date(&self) -> NaiveDate {
self.date
}
}
impl Span<DateUnit, i32> for Date {
fn new(year: i32, month: u32, day: u32) -> Result<Self, SpanError> {
let Some(date) = NaiveDate::from_ymd_opt(year, month, day) else {
return Err(SpanError::InvalidDate(year, month, day)).err_ctx(DateError);
};
Ok(Self {
date,
format: BASE_DATE_FORMAT.get().to_string(),
})
}
fn format(mut self, format: impl ToString) -> Self {
self.format = format.to_string();
self
}
fn default_format(mut self) -> Self {
self.format = BASE_DATE_FORMAT.get().to_string();
self
}
fn update(&self, unit: DateUnit, value: i32) -> Result<Self, SpanError> {
let date = match unit {
DateUnit::Year if value > 0 => {
self.date.checked_add_months(Months::new(value as u32 * 12))
}
DateUnit::Year => self
.date
.checked_sub_months(Months::new(value.unsigned_abs() * 12)),
DateUnit::Month if value > 0 => self.date.checked_add_months(Months::new(value as u32)),
DateUnit::Month => self
.date
.checked_sub_months(Months::new(value.unsigned_abs())),
DateUnit::Day if value > 0 => self.date.checked_add_days(Days::new(value as u64)),
DateUnit::Day => self
.date
.checked_sub_days(Days::new(value.unsigned_abs() as u64)),
};
match date {
Some(date) => Ok(Self {
date,
format: self.format.clone(),
}),
None => Err(SpanError::InvalidUpdate(format!(
"Cannot Add/Remove {value} {unit:?} to/from {self}"
)))
.err_ctx(DateError),
}
}
fn next(&self, unit: DateUnit) -> Result<Self, SpanError> {
self.update(unit, 1)
}
fn matches(&self, unit: DateUnit, value: u32) -> bool {
match unit {
DateUnit::Year => self.date.year() == value as i32,
DateUnit::Month => self.date.month() == value,
DateUnit::Day => self.date.day() == value,
}
}
fn now() -> Result<Self, SpanError> {
let now = Local::now();
Self::new(now.year(), now.month(), now.day())
}
fn is_in_future(&self) -> Result<bool, SpanError> {
Ok(self.date > Self::now()?.date)
}
fn elapsed(&self, lhs: &Self) -> Duration {
self.date.signed_duration_since(lhs.date)
}
fn unit_elapsed(&self, rhs: &Self, unit: DateUnit) -> Result<i64, SpanError> {
Ok(match unit {
DateUnit::Year => self.date.year() as i64 - rhs.date.year() as i64,
DateUnit::Month => {
self.date.year() as i64 * 12 + self.date.month() as i64
- (rhs.date.year() as i64 * 12 + rhs.date.month() as i64)
}
DateUnit::Day => {
let self_utc: chrono::DateTime<Utc> = self.try_into()?;
let lhs_utc: chrono::DateTime<Utc> = rhs.try_into()?;
self_utc.signed_duration_since(lhs_utc).num_days()
}
})
}
fn clear_unit(&self, unit: DateUnit) -> Result<Self, SpanError> {
let date = match unit {
DateUnit::Year => self.date.with_year(1970).ok_or(SpanError::ClearUnit(
"Error while setting year to 1970".to_string(),
)),
DateUnit::Month => self.date.with_month(1).ok_or(SpanError::ClearUnit(
"Error while setting month to 1".to_string(),
)),
DateUnit::Day => self.date.with_day(1).ok_or(SpanError::ClearUnit(
"Error while setting day to 1".to_string(),
)),
}
.err_ctx(DateError)?;
Ok(Self {
date,
format: self.format.clone(),
})
}
fn get_format(&self) -> String {
self.format.clone()
}
fn deserialize_with_format<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
Self: TryFrom<(String, String)>,
{
#[derive(Deserialize)]
struct Visitor {
date: String,
format: String,
}
let visitor: Visitor = Deserialize::deserialize(deserializer)?;
Self::try_from((visitor.date, visitor.format))
.map_err(|_| serde::de::Error::custom("Invalid date"))
}
}
impl From<NaiveDateTime> for Date {
fn from(value: NaiveDateTime) -> Self {
Self {
date: value.date(),
format: BASE_DATE_FORMAT.get().to_string(),
}
}
}
impl From<NaiveDate> for Date {
fn from(value: NaiveDate) -> Self {
Self {
date: value,
format: BASE_DATE_FORMAT.get().to_string(),
}
}
}
impl TryFrom<(String, String)> for Date {
type Error = SpanError;
fn try_from((date, format): (String, String)) -> Result<Self, Self::Error> {
let date = NaiveDate::parse_from_str(&date, &format)
.map_err(SpanError::ParseFromStr)
.err_ctx(DateError)?;
Ok(Self { date, format })
}
}
impl TryFrom<(&str, &str)> for Date {
type Error = SpanError;
fn try_from((date, format): (&str, &str)) -> Result<Self, Self::Error> {
let date = NaiveDate::parse_from_str(date, format)
.map_err(SpanError::ParseFromStr)
.err_ctx(DateError)?;
Ok(Self {
date,
format: format.to_string(),
})
}
}
impl TryFrom<String> for Date {
type Error = SpanError;
fn try_from(date: String) -> Result<Self, Self::Error> {
let date = NaiveDate::parse_from_str(&date, BASE_DATE_FORMAT.get())?;
Ok(Self {
date,
format: BASE_DATE_FORMAT.get().to_string(),
})
}
}
impl TryFrom<&str> for Date {
type Error = SpanError;
fn try_from(date: &str) -> Result<Self, Self::Error> {
let date = NaiveDate::parse_from_str(date, BASE_DATE_FORMAT.get())?;
Ok(Self {
date,
format: BASE_DATE_FORMAT.get().to_string(),
})
}
}
impl TryFrom<chrono::DateTime<Utc>> for Date {
type Error = SpanError;
fn try_from(value: chrono::DateTime<Utc>) -> Result<Self, Self::Error> {
Ok(value.naive_utc().into())
}
}
impl TryFrom<&Date> for chrono::DateTime<Utc> {
type Error = SpanError;
fn try_from(value: &Date) -> Result<Self, Self::Error> {
let date = value.date;
match Utc::now()
.with_year(date.year())
.and_then(|utc| utc.with_day(date.day()))
.and_then(|utc| utc.with_month(date.month()))
{
Some(utc) => Ok(utc),
None => Err(SpanError::InvalidUtc).err_ctx(DateError),
}
}
}
#[cfg(test)]
pub mod test {
use chrono::TimeDelta;
use super::*;
#[test]
fn date_add_overflow() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Day, i32::MAX);
assert_eq!(
new_date,
Err(SpanError::InvalidUpdate(
"Cannot Add/Remove 2147483647 Day to/from 2023-10-09".to_string()
))
.err_ctx(DateError)
);
Ok(())
}
#[test]
fn date_add_one_year() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Year, 1)?;
assert_eq!(new_date.to_string(), "2024-10-09".to_string());
Ok(())
}
#[test]
fn date_remove_one_year() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Year, -1)?;
assert_eq!(new_date.to_string(), "2022-10-09".to_string());
Ok(())
}
#[test]
fn date_add_one_month() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Month, 1)?;
assert_eq!(new_date.to_string(), "2023-11-09".to_string());
Ok(())
}
#[test]
fn date_remove_one_month() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Month, -1)?;
assert_eq!(new_date.to_string(), "2023-09-09".to_string());
Ok(())
}
#[test]
fn date_add_one_day() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Day, 1)?;
assert_eq!(new_date.to_string(), "2023-10-10".to_string());
Ok(())
}
#[test]
fn date_remove_one_day() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let new_date = date.update(DateUnit::Day, -1)?;
assert_eq!(new_date.to_string(), "2023-10-08".to_string());
Ok(())
}
#[test]
fn date_serialize() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let Ok(serialized) = serde_json::to_string(&date) else {
panic!("Error while serializing date");
};
assert_eq!(serialized, "{\"date\":\"2023-10-09\"}".to_string());
Ok(())
}
#[test]
fn date_deserialize() -> Result<(), SpanError> {
let serialized = "{\"date\":\"2023-10-09\"}".to_string();
let Ok(date) = serde_json::from_str::<Date>(&serialized) else {
panic!("Error while deserializing date");
};
assert_eq!(date.to_string(), "2023-10-09".to_string());
assert_eq!(date.format, BASE_DATE_FORMAT.get().to_string());
Ok(())
}
#[test]
fn date_serialize_format() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?.format("%d/%m/%Y");
let Ok(serialized) = serde_json::to_string(&date) else {
panic!("Error while serializing date");
};
assert_eq!(serialized, "{\"date\":\"2023-10-09\"}".to_string());
Ok(())
}
#[test]
fn date_deserialize_format() -> Result<(), SpanError> {
#[derive(Deserialize)]
struct CustomFmt(#[serde(deserialize_with = "Date::deserialize_with_format")] Date);
let serialized = "{\"date\":\"09/10/2023\",\"format\":\"%d/%m/%Y\"}".to_string();
let Ok(CustomFmt(date)) = serde_json::from_str::<CustomFmt>(&serialized) else {
panic!("Error while deserializing date");
};
assert_eq!(date.to_string(), "09/10/2023".to_string());
assert_eq!(date.format, "%d/%m/%Y".to_string());
Ok(())
}
#[test]
fn date_serialize_in_struct() -> Result<(), SpanError> {
#[derive(Serialize)]
struct Test {
begin_at: Date,
}
let test = Test {
begin_at: Date::new(2023, 10, 9)?,
};
let Ok(serialized) = serde_json::to_string(&test) else {
panic!("Error while serializing date");
};
assert_eq!(
serialized,
"{\"begin_at\":{\"date\":\"2023-10-09\"}}".to_string()
);
Ok(())
}
#[test]
fn date_deserialize_in_struct() -> Result<(), SpanError> {
#[derive(Deserialize)]
struct Test {
begin_at: Date,
}
let serialized = "{\"begin_at\":{\"date\":\"2023-10-09\"}}".to_string();
let Ok(test) = serde_json::from_str::<Test>(&serialized) else {
panic!("Error while deserializing date");
};
assert_eq!(test.begin_at.to_string(), "2023-10-09".to_string());
assert_eq!(test.begin_at.format, BASE_DATE_FORMAT.get().to_string());
Ok(())
}
#[test]
fn next_month_january_to_february() -> Result<(), SpanError> {
let mut date = Date::new(2023, 1, 31)?;
date = date.next(DateUnit::Month)?;
assert_eq!(date.to_string(), "2023-02-28".to_string());
Ok(())
}
#[test]
fn next_month_february_to_march() -> Result<(), SpanError> {
let mut date = Date::new(2023, 2, 2)?;
date = date.next(DateUnit::Month)?;
assert_eq!(date.to_string(), "2023-03-02".to_string());
Ok(())
}
#[test]
fn next_month() -> Result<(), SpanError> {
let mut date = Date::new(2023, 10, 9)?;
date = date.next(DateUnit::Month)?;
assert_eq!(date.to_string(), "2023-11-09".to_string());
Ok(())
}
#[test]
fn next_year() -> Result<(), SpanError> {
let mut date = Date::new(2023, 10, 9)?;
date = date.next(DateUnit::Year)?;
assert_eq!(date.to_string(), "2024-10-09".to_string());
Ok(())
}
#[test]
fn next_month_on_december() -> Result<(), SpanError> {
let mut date = Date::new(2023, 12, 9)?;
date = date.next(DateUnit::Month)?;
assert_eq!(date.to_string(), "2024-01-09".to_string());
Ok(())
}
#[test]
fn next_day_28_february_leap_year() -> Result<(), SpanError> {
let mut date = Date::new(2024, 2, 28)?;
date = date.next(DateUnit::Day)?;
assert_eq!(date.to_string(), "2024-02-29".to_string());
Ok(())
}
#[test]
fn next_day_28_february_non_leap_year() -> Result<(), SpanError> {
let mut date = Date::new(2023, 2, 28)?;
date = date.next(DateUnit::Day)?;
assert_eq!(date.to_string(), "2023-03-01".to_string());
Ok(())
}
#[test]
fn matches_every_unit_in_date() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
assert!(date.matches(DateUnit::Year, 2023));
assert!(date.matches(DateUnit::Month, 10));
assert!(date.matches(DateUnit::Day, 9));
Ok(())
}
#[test]
fn is_in_future_yesterday() -> Result<(), SpanError> {
let mut date = Date::now()?;
date = date.update(DateUnit::Day, -1)?;
assert!(!date.is_in_future()?);
Ok(())
}
#[test]
fn is_in_future_tomorrow() -> Result<(), SpanError> {
let mut date = Date::now()?;
date = date.update(DateUnit::Day, 1)?;
assert!(date.is_in_future()?);
Ok(())
}
#[test]
fn is_in_future_now() -> Result<(), SpanError> {
let date = Date::now()?;
assert!(!date.is_in_future()?);
Ok(())
}
#[test]
fn elapsed_one_year() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let lhs = Date::new(2022, 10, 9)?;
assert_eq!(date.elapsed(&lhs), TimeDelta::try_days(365).unwrap());
Ok(())
}
#[test]
fn elapsed_one_second() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let lhs = Date::new(2023, 10, 2)?;
assert_eq!(date.elapsed(&lhs), TimeDelta::try_weeks(1).unwrap());
Ok(())
}
#[test]
fn elapsed_multiple_units() -> Result<(), SpanError> {
let date = Date::new(2024, 2, 12)?;
let lhs = Date::new(2023, 2, 8)?;
assert_eq!(
date.elapsed(&lhs),
TimeDelta::try_weeks(52)
.unwrap()
.checked_add(&TimeDelta::try_days(5).unwrap())
.unwrap()
);
Ok(())
}
#[test]
fn unit_elapsed() -> Result<(), SpanError> {
let date = Date::new(2023, 10, 9)?;
let rhs = Date::new(2020, 2, 8)?;
let years_in_between = date.unit_elapsed(&rhs, DateUnit::Year)?;
let months_in_between = date.unit_elapsed(&rhs, DateUnit::Month)?;
let days_in_between = date.unit_elapsed(&rhs, DateUnit::Day)?;
assert_eq!(years_in_between, 3);
assert_eq!(months_in_between, years_in_between * 12 + 8);
assert_eq!(days_in_between, 1338);
Ok(())
}
#[test]
fn unit_elapsed_leap_year_days() -> Result<(), SpanError> {
let date = Date::new(2024, 3, 12)?;
let rhs = Date::new(2024, 1, 12)?;
let years_in_between = date.unit_elapsed(&rhs, DateUnit::Year)?;
let months_in_between = date.unit_elapsed(&rhs, DateUnit::Month)?;
let days_in_between = date.unit_elapsed(&rhs, DateUnit::Day)?;
assert_eq!(years_in_between, 0);
assert_eq!(months_in_between, years_in_between * 12 + 2);
assert_eq!(days_in_between, 59);
Ok(())
}
#[test]
fn unit_elapsed_non_leap_year_days() -> Result<(), SpanError> {
let date = Date::new(2023, 3, 12)?;
let rhs = Date::new(2023, 1, 12)?;
let years_in_between = date.unit_elapsed(&rhs, DateUnit::Year)?;
let months_in_between = date.unit_elapsed(&rhs, DateUnit::Month)?;
let days_in_between = date.unit_elapsed(&rhs, DateUnit::Day)?;
assert_eq!(years_in_between, 0);
assert_eq!(months_in_between, years_in_between * 12 + 2);
assert_eq!(days_in_between, 58);
Ok(())
}
}
#[cfg(all(feature = "date", feature = "datetime"))]
mod datetime_into_date {
use crate::GetInner;
impl From<crate::datetime::DateTime> for crate::date::Date {
fn from(value: crate::datetime::DateTime) -> Self {
Self {
date: value.date(),
format: crate::date::BASE_DATE_FORMAT.get().to_string(),
}
}
}
#[cfg(test)]
mod test {
use crate::span::Span;
#[test]
fn datetime_into_date() -> Result<(), crate::error::SpanError> {
let datetime = crate::datetime::DateTime::new(2023, 10, 9)?.with_time(12, 0, 0)?;
let date = crate::date::Date::from(datetime);
assert_eq!(date.to_string(), "2023-10-09".to_string());
Ok(())
}
#[test]
#[ignore]
fn datetime_into_date_wrong_format() -> Result<(), crate::error::SpanError> {
crate::builder::SpanBuilder::builder()
.datetime_format("%Y-%m-%d %H:%M:%S")
.date_format("%d/%m/%Y")
.build();
let datetime = crate::datetime::DateTime::new(2023, 10, 9)?.with_time(12, 0, 0)?;
let date = crate::date::Date::from(datetime);
assert_eq!(date.to_string(), "09/10/2023".to_string());
let next = date.next(crate::date::DateUnit::Day)?;
assert_eq!(next.to_string(), "10/10/2023".to_string());
Ok(())
}
}
}