use std::cmp::Ordering;
use chrono::Datelike;
use crate::error::{FraiseQLError, Result};
pub(crate) fn parse_date(date_str: &str) -> Result<(u32, u32, u32)> {
let parts: Vec<&str> = date_str.split('-').collect();
if parts.len() != 3 {
return Err(FraiseQLError::Validation {
message: format!("Invalid date format: '{}'. Expected YYYY-MM-DD", date_str),
path: None,
});
}
let year = parts[0].parse::<u32>().map_err(|_| FraiseQLError::Validation {
message: format!("Invalid year: '{}'", parts[0]),
path: None,
})?;
let month = parts[1].parse::<u32>().map_err(|_| FraiseQLError::Validation {
message: format!("Invalid month: '{}'", parts[1]),
path: None,
})?;
let day = parts[2].parse::<u32>().map_err(|_| FraiseQLError::Validation {
message: format!("Invalid day: '{}'", parts[2]),
path: None,
})?;
if !(1..=12).contains(&month) {
return Err(FraiseQLError::Validation {
message: format!("Month must be between 1 and 12, got {}", month),
path: None,
});
}
let days_in_month = get_days_in_month(month, year);
if !(1..=days_in_month).contains(&day) {
return Err(FraiseQLError::Validation {
message: format!("Day must be between 1 and {}, got {}", days_in_month, day),
path: None,
});
}
Ok((year, month, day))
}
pub(crate) const fn is_leap_year(year: u32) -> bool {
(year.is_multiple_of(4) && !year.is_multiple_of(100)) || year.is_multiple_of(400)
}
pub(crate) const fn get_days_in_month(month: u32, year: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if is_leap_year(year) {
29
} else {
28
}
},
_ => 0,
}
}
fn get_today() -> (u32, u32, u32) {
let today = chrono::Utc::now().date_naive();
(today.year_ce().1, today.month(), today.day())
}
pub(crate) fn compare_dates(left: (u32, u32, u32), right: (u32, u32, u32)) -> i32 {
match left.0.cmp(&right.0) {
Ordering::Less => -1,
Ordering::Greater => 1,
Ordering::Equal => match left.1.cmp(&right.1) {
Ordering::Less => -1,
Ordering::Greater => 1,
Ordering::Equal => match left.2.cmp(&right.2) {
Ordering::Less => -1,
Ordering::Greater => 1,
Ordering::Equal => 0,
},
},
}
}
pub(crate) fn days_between(left: (u32, u32, u32), right: (u32, u32, u32)) -> i64 {
let days_left = i64::from(left.0) * 365 + i64::from(left.1) * 31 + i64::from(left.2);
let days_right = i64::from(right.0) * 365 + i64::from(right.1) * 31 + i64::from(right.2);
days_left - days_right
}
pub fn validate_min_date(date_str: &str, min_date_str: &str) -> Result<()> {
let date = parse_date(date_str)?;
let min_date = parse_date(min_date_str)?;
if compare_dates(date, min_date) < 0 {
return Err(FraiseQLError::Validation {
message: format!("Date '{}' must be >= '{}'", date_str, min_date_str),
path: None,
});
}
Ok(())
}
pub fn validate_max_date(date_str: &str, max_date_str: &str) -> Result<()> {
let date = parse_date(date_str)?;
let max_date = parse_date(max_date_str)?;
if compare_dates(date, max_date) > 0 {
return Err(FraiseQLError::Validation {
message: format!("Date '{}' must be <= '{}'", date_str, max_date_str),
path: None,
});
}
Ok(())
}
pub fn validate_date_range(date_str: &str, min_date_str: &str, max_date_str: &str) -> Result<()> {
validate_min_date(date_str, min_date_str)?;
validate_max_date(date_str, max_date_str)?;
Ok(())
}
pub fn validate_min_age(date_str: &str, min_age: u32) -> Result<()> {
let birth_date = parse_date(date_str)?;
let today = get_today();
let mut age = today.0 - birth_date.0;
if (today.1, today.2) < (birth_date.1, birth_date.2) {
age -= 1;
}
if age < min_age {
return Err(FraiseQLError::Validation {
message: format!("Age must be at least {} years old, got {}", min_age, age),
path: None,
});
}
Ok(())
}
pub fn validate_max_age(date_str: &str, max_age: u32) -> Result<()> {
let birth_date = parse_date(date_str)?;
let today = get_today();
let mut age = today.0 - birth_date.0;
if (today.1, today.2) < (birth_date.1, birth_date.2) {
age -= 1;
}
if age > max_age {
return Err(FraiseQLError::Validation {
message: format!("Age must be at most {} years old, got {}", max_age, age),
path: None,
});
}
Ok(())
}
pub fn validate_max_days_in_future(date_str: &str, max_days: i64) -> Result<()> {
let date = parse_date(date_str)?;
let today = get_today();
let days_diff = days_between(date, today);
if days_diff > max_days {
return Err(FraiseQLError::Validation {
message: format!(
"Date '{}' cannot be more than {} days in the future",
date_str, max_days
),
path: None,
});
}
Ok(())
}
pub fn validate_max_days_in_past(date_str: &str, max_days: i64) -> Result<()> {
let date = parse_date(date_str)?;
let today = get_today();
let days_diff = days_between(today, date);
if days_diff > max_days {
return Err(FraiseQLError::Validation {
message: format!(
"Date '{}' cannot be more than {} days in the past",
date_str, max_days
),
path: None,
});
}
Ok(())
}