use diesel::{ backend::Backend, deserialize::{FromSql, FromSqlRow}, expression::AsExpression, sql_types::Integer, sqlite::Sqlite, }; #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, AsExpression, FromSqlRow)] #[diesel(sql_type=Integer)] pub struct ActualbudgetDate(i64); #[derive(Clone, PartialOrd, PartialEq, Eq)] enum DatePart { MonthOfYear, DayOfMonth, } // actualbudget represents dates as numbers which "look like" the corresponding // ISO string when written in decimal. E.g. 20240901 represents 1 Sep 2024. It // also uses month specifiers, e.g. 202409 to represent Sep 2024. In this case, // we interpret this as the first day of the month, e.g. 2024-09-01. impl ActualbudgetDate { pub fn to_iso(&self) -> String { format!( "{0}-{1}-{2}T00:00:00.000Z", self.year(), self.month(), self.day() ) } pub fn to_iso_date(&self) -> String { format!("{0}-{1}-{2}", self.year(), self.month(), self.day()) } pub fn year(&self) -> String { self.0.to_string()[0..4].to_string() } pub fn month(&self) -> String { self.0.to_string()[4..6].to_string() } pub fn day(&self) -> String { match self.date_part() { DatePart::DayOfMonth => self.0.to_string()[6..8].to_string(), DatePart::MonthOfYear => String::from("01"), } } pub fn from_i32(i: i32) -> Self { ActualbudgetDate(i.into()) } pub fn from_ymd(year: T, month: S, day: U) -> Self where T: Into, S: Into, U: Into, { Self(year.into() * 10_000 + month.into() * 100 + day.into()) } pub fn oldest_valid_date() -> Self { Self(10000101) } fn date_part(&self) -> DatePart { if self.0 < 10000000 { DatePart::MonthOfYear } else { DatePart::DayOfMonth } } fn dayified_value(&self) -> i64 { match self.date_part() { DatePart::DayOfMonth => self.0, DatePart::MonthOfYear => self.0 * 100 + 1, } } } impl Ord for ActualbudgetDate { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.dayified_value().cmp(&other.dayified_value()) } } impl PartialOrd for ActualbudgetDate { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl FromSql for ActualbudgetDate { fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { let int: i32 = FromSql::::from_sql(bytes)?; Ok(ActualbudgetDate(int.into())) } } #[cfg(test)] mod test { use crate::ActualbudgetDate; #[test] fn when_from_ymd_2026_01_16_then_constructs_2026_01_16() { let actualbudget_date = ActualbudgetDate::from_ymd(2026, 1, 16); assert_eq!(actualbudget_date.year(), "2026"); assert_eq!(actualbudget_date.month(), "01"); assert_eq!(actualbudget_date.day(), "16"); } }