94 lines
2.5 KiB
Rust
94 lines
2.5 KiB
Rust
|
|
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<String>,
|
||
|
|
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<chrono::NaiveDate> {
|
||
|
|
match &self {
|
||
|
|
Birthday::Text(_) => None,
|
||
|
|
Birthday::Date(date) => Some(date.next_month_day_occurrence()?),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn until_next(&self) -> Option<chrono::TimeDelta> {
|
||
|
|
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<u32> {
|
||
|
|
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<Self, Self::Err> {
|
||
|
|
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<Self> {
|
||
|
|
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: <Sqlite as Database>::ValueRef<'r>,
|
||
|
|
) -> Result<Birthday, Box<dyn std::error::Error + 'static + Send + Sync>> {
|
||
|
|
let value = <&str as Decode<Sqlite>>::decode(value)?;
|
||
|
|
|
||
|
|
Ok(Birthday::from_str(value).unwrap())
|
||
|
|
}
|
||
|
|
}
|