use diesel::{backend::Backend, deserialize::{FromSql, FromSqlRow}, expression::AsExpression, sql_types::Integer, sqlite::Sqlite}; #[derive(Clone, Hash, PartialEq, Eq, Debug, AsExpression, FromSqlRow)] #[diesel(sql_type=Integer)] pub struct ActualbudgetDate(i32); #[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) } fn date_part(&self) -> DatePart { if self.0 < 10000000 { DatePart::MonthOfYear } else { DatePart::DayOfMonth } } fn dayified_value(&self) -> i32 { 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)) } }