use anyhow::{Result, bail};
use chrono::{Datelike, Duration, Local, NaiveDateTime, NaiveTime, Weekday};
use sha2::{Digest, Sha256};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub enum ParsedDate {
Today,
Yesterday,
LastWeek,
LastWeekday(Weekday),
Concrete(NaiveDateTime),
}
impl ParsedDate {
pub fn to_naive_datetime(&self) -> NaiveDateTime {
let today = to_midnight(&now());
match self {
ParsedDate::Today => today,
ParsedDate::Yesterday => today - Duration::days(1),
ParsedDate::LastWeek => today - Duration::days(7),
ParsedDate::LastWeekday(target_weekday) => {
let mut day = today - Duration::days(1);
while day.weekday() != *target_weekday {
day -= Duration::days(1);
}
to_midnight(&day)
}
ParsedDate::Concrete(date) => *date,
}
}
}
pub fn now() -> NaiveDateTime {
Local::now().naive_local()
}
fn to_midnight(datetime: &NaiveDateTime) -> NaiveDateTime {
let midnight = NaiveTime::from_hms_opt(0, 0, 0).unwrap();
return NaiveDateTime::new(datetime.date(), midnight);
}
fn weekday_from_str(day: &str) -> Option<Weekday> {
match day {
"monday" => Some(Weekday::Mon),
"tuesday" => Some(Weekday::Tue),
"wednesday" => Some(Weekday::Wed),
"thursday" => Some(Weekday::Thu),
"friday" => Some(Weekday::Fri),
"saturday" => Some(Weekday::Sat),
"sunday" => Some(Weekday::Sun),
_ => None,
}
}
pub fn parse_date_arg(input: &str) -> Result<NaiveDateTime> {
let input = input.trim().to_lowercase();
let parsed_date = match input.as_str() {
"today" => ParsedDate::Today,
"yesterday" => ParsedDate::Yesterday,
"last-week" => ParsedDate::LastWeek,
_ if input.starts_with("last-") => {
let day_str = &input[5..]; if let Some(weekday) = weekday_from_str(day_str) {
ParsedDate::LastWeekday(weekday)
} else {
bail!("Invalid last- weekday: {}", day_str);
}
}
_ => {
if let Ok(date) = NaiveDateTime::parse_from_str(&input, "%d/%m/%y") {
ParsedDate::Concrete(date)
} else {
bail!("Invalid date format: expected dd/mm/yy, got '{}'", input);
}
}
};
Ok(parsed_date.to_naive_datetime())
}
pub fn generate_reproducible_id_from_datetime(date_time: NaiveDateTime) -> Uuid {
let date_str = format!(
"{:04}-{:02}-{:02}",
date_time.year(),
date_time.month(),
date_time.day()
);
let hash = Sha256::digest(date_str.as_bytes());
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&hash[..16]);
bytes[6] = (bytes[6] & 0x0F) | 0x40;
bytes[8] = (bytes[8] & 0x3F) | 0x80;
Uuid::from_bytes(bytes)
}