use std::{fmt, str::FromStr};
use jiff::{Span, Zoned, civil::Date as Date_, tz::TimeZone};
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
use vecdb::{Formattable, Pco};
use crate::ONE_DAY_IN_SEC_F64;
use super::{Day1, Month1, Month3, Month6, Timestamp, Week1, Year1, Year10};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Pco, JsonSchema)]
pub struct Date(u32);
impl Date {
pub const INDEX_ZERO: Self = Self(20090101);
pub const INDEX_ZERO_: Date_ = Date_::constant(2009, 1, 1);
pub fn new(year: u16, month: u8, day: u8) -> Self {
Self(year as u32 * 1_00_00 + month as u32 * 1_00 + day as u32)
}
pub fn year(&self) -> u16 {
(self.0 / 1_00_00) as u16
}
pub fn month(&self) -> u8 {
((self.0 % 1_00_00) / 1_00) as u8
}
pub fn day(&self) -> u8 {
(self.0 % 1_00) as u8
}
pub fn into_jiff(self) -> Date_ {
self.into()
}
pub fn today() -> Self {
Self::from(Timestamp::now())
}
pub fn completion(&self) -> f64 {
let date = Date_::from(*self);
let now = Zoned::now().with_time_zone(TimeZone::UTC);
let today = now.date();
if date < today {
1.0
} else if date == today {
let rounded = jiff::Timestamp::from(*self);
now.timestamp().duration_since(rounded).as_secs_f64() / ONE_DAY_IN_SEC_F64
} else {
0.0
}
}
}
impl Default for Date {
fn default() -> Self {
Self::INDEX_ZERO
}
}
impl From<Date_> for Date {
#[inline]
fn from(value: Date_) -> Self {
Self::new(value.year() as u16, value.month() as u8, value.day() as u8)
}
}
impl From<Date> for Date_ {
#[inline]
fn from(value: Date) -> Self {
Self::new(value.year() as i16, value.month() as i8, value.day() as i8).unwrap()
}
}
impl From<Date> for jiff::Timestamp {
#[inline]
fn from(value: Date) -> Self {
Self::from(Timestamp::from(value))
}
}
impl From<Timestamp> for Date {
#[inline]
fn from(value: Timestamp) -> Self {
Self::from(Date_::from(
jiff::Timestamp::from(value).to_zoned(TimeZone::UTC),
))
}
}
impl From<Day1> for Date {
#[inline]
fn from(value: Day1) -> Self {
Self::from(
Self::INDEX_ZERO_
.checked_add(Span::new().days(i64::from(value)))
.unwrap(),
)
}
}
impl From<Week1> for Date {
#[inline]
fn from(value: Week1) -> Self {
Self::from(
Self::INDEX_ZERO_
.checked_add(Span::new().weeks(i64::from(u16::from(value))))
.unwrap(),
)
}
}
impl From<Month1> for Date {
#[inline]
fn from(value: Month1) -> Self {
Self::from(
Date_::constant(2009, 1, 1)
.checked_add(Span::new().months(i64::from(u16::from(value))))
.unwrap(),
)
}
}
impl From<Year1> for Date {
#[inline]
fn from(value: Year1) -> Self {
let year = 2009i16 + usize::from(value) as i16;
Self::from(Date_::constant(year, 1, 1))
}
}
impl From<Month3> for Date {
#[inline]
fn from(value: Month3) -> Self {
Self::from(
Date_::constant(2009, 1, 1)
.checked_add(Span::new().months(usize::from(value) as i64 * 3))
.unwrap(),
)
}
}
impl From<Month6> for Date {
#[inline]
fn from(value: Month6) -> Self {
Self::from(
Date_::constant(2009, 1, 1)
.checked_add(Span::new().months(usize::from(value) as i64 * 6))
.unwrap(),
)
}
}
impl From<Year10> for Date {
#[inline]
fn from(value: Year10) -> Self {
let year = 2009i16 + usize::from(value) as i16 * 10;
Self::from(Date_::constant(year, 1, 1))
}
}
impl Serialize for Date {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Date {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct DateVisitor;
impl Visitor<'_> for DateVisitor {
type Value = Date;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a date string in YYYY-MM-DD format")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.len() != 10 {
return Err(E::invalid_length(v.len(), &self));
}
let year: u16 = v[0..4]
.parse()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))?;
let month: u8 = v[5..7]
.parse()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))?;
let day: u8 = v[8..10]
.parse()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))?;
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return Err(E::invalid_value(serde::de::Unexpected::Str(v), &self));
}
Ok(Date::new(year, month, day))
}
}
deserializer.deserialize_str(DateVisitor)
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = itoa::Buffer::new();
f.write_str(buf.format(self.year()))?;
f.write_str("-")?;
let month = self.month();
if month < 10 {
f.write_str("0")?;
}
f.write_str(buf.format(month))?;
f.write_str("-")?;
let day = self.day();
if day < 10 {
f.write_str("0")?;
}
f.write_str(buf.format(day))
}
}
impl FromStr for Date {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 10 || s.as_bytes()[4] != b'-' || s.as_bytes()[7] != b'-' {
return Err("expected YYYY-MM-DD format");
}
let year: u16 = s[0..4].parse().map_err(|_| "invalid year")?;
let month: u8 = s[5..7].parse().map_err(|_| "invalid month")?;
let day: u8 = s[8..10].parse().map_err(|_| "invalid day")?;
Ok(Self::new(year, month, day))
}
}
impl Formattable for Date {
fn write_to(&self, buf: &mut Vec<u8>) {
use std::fmt::Write;
let mut s = String::new();
write!(s, "{}", self).unwrap();
buf.extend_from_slice(s.as_bytes());
}
fn fmt_json(&self, buf: &mut Vec<u8>) {
buf.push(b'"');
self.write_to(buf);
buf.push(b'"');
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_date_from_day1_zero() {
let date = Date::from(Day1::from(0_usize));
assert_eq!(date, Date::INDEX_ZERO);
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_day1_two() {
let date = Date::from(Day1::from(2_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 3);
}
#[test]
fn test_date_from_day1_eight() {
let date = Date::from(Day1::from(8_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 9);
}
#[test]
fn test_date_from_week1_zero() {
let date = Date::from(Week1::from(0_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_week1_one() {
let date = Date::from(Week1::from(1_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 8);
}
#[test]
fn test_date_from_month1_zero() {
let date = Date::from(Month1::from(0_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month1_one() {
let date = Date::from(Month1::from(1_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 2);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month1_twelve() {
let date = Date::from(Month1::from(12_usize));
assert_eq!(date.year(), 2010);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_year1_zero() {
let date = Date::from(Year1::from(0_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_year1_one() {
let date = Date::from(Year1::from(1_usize));
assert_eq!(date.year(), 2010);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month3_zero() {
let date = Date::from(Month3::from(0_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month3_one() {
let date = Date::from(Month3::from(1_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 4);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month3_four() {
let date = Date::from(Month3::from(4_usize));
assert_eq!(date.year(), 2010);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month6_zero() {
let date = Date::from(Month6::from(0_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month6_one() {
let date = Date::from(Month6::from(1_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 7);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_month6_two() {
let date = Date::from(Month6::from(2_usize));
assert_eq!(date.year(), 2010);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_year10_zero() {
let date = Date::from(Year10::from(0_usize));
assert_eq!(date.year(), 2009);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
#[test]
fn test_date_from_year10_one() {
let date = Date::from(Year10::from(1_usize));
assert_eq!(date.year(), 2019);
assert_eq!(date.month(), 1);
assert_eq!(date.day(), 1);
}
}