use std::fmt::{Display, Formatter};
use std::str::FromStr;
use chrono::{Datelike, NaiveDate};
use serde::Serialize;
use crate::types::Interval;
pub const UNIX_EPOCH_DAYS: i32 = 719_163;
#[derive(PartialOrd, Ord, PartialEq, Eq, Debug, Copy, Clone, Default, Hash, Serialize)]
pub struct Date(i32);
impl Date {
pub const fn new(inner: i32) -> Self {
Date(inner)
}
pub fn get_inner(&self) -> i32 {
self.0
}
}
pub type ParseDateError = chrono::ParseError;
impl FromStr for Date {
type Err = chrono::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NaiveDate::parse_from_str(s, "%Y-%m-%d")
.map(|ret| Date(ret.num_days_from_ce() - UNIX_EPOCH_DAYS))
}
}
impl std::ops::Add<Interval> for Date {
type Output = Date;
fn add(self, rhs: Interval) -> Self::Output {
let days = self.0 + rhs.days();
let date = NaiveDate::from_num_days_from_ce_opt(days + UNIX_EPOCH_DAYS).unwrap();
let mut day = date.day();
let mut month = date.month() as i32 + rhs.months();
let mut year = date.year() + rhs.years();
if month > 12 {
month -= 12;
year += 1;
} else if month <= 0 {
month += 12;
year -= 1;
}
assert!((1..=12).contains(&month));
day = day.min(get_month_days(year, month as usize));
Date::new(
NaiveDate::from_ymd_opt(year, month as u32, day)
.unwrap()
.num_days_from_ce()
- UNIX_EPOCH_DAYS,
)
}
}
const fn get_month_days(year: i32, month: usize) -> u32 {
const fn is_leap_year(year: i32) -> bool {
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
const LEAP_DAYS: &[u32] = &[0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const NORMAL_DAYS: &[u32] = &[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if is_leap_year(year) {
LEAP_DAYS[month]
} else {
NORMAL_DAYS[month]
}
}
impl std::ops::Sub<Interval> for Date {
type Output = Date;
fn sub(self, rhs: Interval) -> Self::Output {
self + (-rhs)
}
}
impl std::ops::Mul<Interval> for Date {
type Output = Date;
fn mul(self, rhs: Interval) -> Self::Output {
panic!("invalid operation: {:?} * {:?}", self, rhs)
}
}
impl std::ops::Div<Interval> for Date {
type Output = Date;
fn div(self, rhs: Interval) -> Self::Output {
panic!("invalid operation: {:?} / {:?}", self, rhs)
}
}
impl std::ops::Rem<Interval> for Date {
type Output = Date;
fn rem(self, rhs: Interval) -> Self::Output {
panic!("invalid operation: {:?} % {:?}", self, rhs)
}
}
impl Display for Date {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
NaiveDate::from_num_days_from_ce_opt(self.0 + UNIX_EPOCH_DAYS)
.unwrap()
.format("%Y-%m-%d")
)
}
}