use chrono::Local; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Row}; use std::fmt::Display; use std::str::FromStr; use crate::models::YearOptionalDate; #[derive(Debug, Clone)] pub struct Text { // language: Option, pub value: String, } #[derive(Debug, Clone)] pub enum Birthday { Date(YearOptionalDate), Text(Text), } impl Display for Birthday { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let str = match self { Birthday::Date(date) => date.to_string(), Birthday::Text(t) => t.value.clone(), }; write!(f, "{}", str) } } impl Birthday { pub fn next_occurrence(&self) -> Option { match &self { Birthday::Text(_) => None, Birthday::Date(date) => Some(date.next_month_day_occurrence()?), } } pub fn until_next(&self) -> Option { self.next_occurrence() .map(|when| when.signed_duration_since(Local::now().date_naive())) } /// None if this is a text birthday or doesn't have a year pub fn age(&self) -> Option { match &self { Birthday::Text(_) => None, Birthday::Date(date) => date .to_date_naive() .map(|birthdate| Local::now().date_naive().years_since(birthdate))?, } } pub fn serialize(&self) -> String { match &self { Birthday::Text(text) => text.value.clone(), Birthday::Date(date) => date.serialize(), } } } impl FromStr for Birthday { type Err = (); fn from_str(str: &str) -> Result { if let Some(date) = YearOptionalDate::from_str(str).ok() { Ok(Birthday::Date(date)) } else { Ok(Birthday::Text(super::birthday::Text { value: str.to_string(), })) } } } impl FromRow<'_, SqliteRow> for Birthday { fn from_row(row: &SqliteRow) -> sqlx::Result { let birthday_str = row.try_get("birthday")?; Ok(Birthday::from_str(birthday_str).unwrap()) } } use sqlx::{Database, Decode, Sqlite}; impl<'r> Decode<'r, Sqlite> for Birthday where &'r str: Decode<'r, Sqlite>, { fn decode( value: ::ValueRef<'r>, ) -> Result> { let value = <&str as Decode>::decode(value)?; Ok(Birthday::from_str(value).unwrap()) } }