use crate::types::Value;
use crate::error::Error;
use chrono::{DateTime, Local, NaiveDate, Utc, Datelike, Timelike};
pub fn is_datetime_function(name: &str) -> bool {
matches!(name, "NOW" | "DATE" | "TIME" | "YEAR" | "MONTH" | "DAY" | "DATEADD" | "DATEDIFF")
}
pub fn exec_datetime(name: &str, args: &[Value]) -> Result<Value, Error> {
match name {
"NOW" => {
let now = Utc::now();
Ok(Value::DateTime(now.timestamp()))
}
"DATE" => {
if args.is_empty() {
let today = Local::now().date_naive();
let timestamp = today.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp();
Ok(Value::DateTime(timestamp))
} else if args.len() == 3 {
let year = match args.get(0) {
Some(Value::Number(n)) => *n as i32,
_ => return Err(Error::new("DATE expects year as number", None)),
};
let month = match args.get(1) {
Some(Value::Number(n)) => *n as u32,
_ => return Err(Error::new("DATE expects month as number", None)),
};
let day = match args.get(2) {
Some(Value::Number(n)) => *n as u32,
_ => return Err(Error::new("DATE expects day as number", None)),
};
let date = NaiveDate::from_ymd_opt(year, month, day)
.ok_or_else(|| Error::new("Invalid date", None))?;
let timestamp = date.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp();
Ok(Value::DateTime(timestamp))
} else {
Err(Error::new("DATE expects either no arguments or three arguments (year, month, day)", None))
}
}
"TIME" => {
let now = Local::now().time();
let seconds_since_midnight = now.num_seconds_from_midnight() as f64;
Ok(Value::Number(seconds_since_midnight))
}
"YEAR" => {
if let Some(Value::DateTime(timestamp)) = args.get(0) {
let dt = DateTime::from_timestamp(*timestamp, 0)
.ok_or_else(|| Error::new("Invalid timestamp", None))?;
Ok(Value::Number(dt.year() as f64))
} else {
Err(Error::new("YEAR expects datetime", None))
}
}
"MONTH" => {
if let Some(Value::DateTime(timestamp)) = args.get(0) {
let dt = DateTime::from_timestamp(*timestamp, 0)
.ok_or_else(|| Error::new("Invalid timestamp", None))?;
Ok(Value::Number(dt.month() as f64))
} else {
Err(Error::new("MONTH expects datetime", None))
}
}
"DAY" => {
if let Some(Value::DateTime(timestamp)) = args.get(0) {
let dt = DateTime::from_timestamp(*timestamp, 0)
.ok_or_else(|| Error::new("Invalid timestamp", None))?;
Ok(Value::Number(dt.day() as f64))
} else {
Err(Error::new("DAY expects datetime", None))
}
}
"DATEADD" => {
if args.len() < 3 {
return Err(Error::new("DATEADD expects date, interval, unit", None));
}
let timestamp = match args.get(0) {
Some(Value::DateTime(ts)) => *ts,
_ => return Err(Error::new("DATEADD expects datetime as first argument", None)),
};
let interval = match args.get(1) {
Some(Value::Number(n)) => *n as i64,
_ => return Err(Error::new("DATEADD expects number as second argument", None)),
};
let unit = match args.get(2) {
Some(Value::String(s)) => s.to_lowercase(),
_ => return Err(Error::new("DATEADD expects string unit as third argument", None)),
};
let dt = DateTime::from_timestamp(timestamp, 0)
.ok_or_else(|| Error::new("Invalid timestamp", None))?;
let new_dt = match unit.as_str() {
"days" | "day" | "d" => dt + chrono::Duration::days(interval),
"hours" | "hour" | "h" => dt + chrono::Duration::hours(interval),
"minutes" | "minute" | "m" => dt + chrono::Duration::minutes(interval),
"seconds" | "second" | "s" => dt + chrono::Duration::seconds(interval),
"weeks" | "week" | "w" => dt + chrono::Duration::weeks(interval),
"months" | "month" => {
let mut year = dt.year();
let mut month = dt.month() as i32;
month += interval as i32;
while month > 12 {
year += 1;
month -= 12;
}
while month < 1 {
year -= 1;
month += 12;
}
let new_date = NaiveDate::from_ymd_opt(year, month as u32, dt.day())
.unwrap_or_else(|| NaiveDate::from_ymd_opt(year, month as u32, 28).unwrap());
new_date.and_time(dt.time()).and_utc()
}
"years" | "year" | "y" => {
let new_year = dt.year() + interval as i32;
let new_date = NaiveDate::from_ymd_opt(new_year, dt.month(), dt.day())
.unwrap_or_else(|| NaiveDate::from_ymd_opt(new_year, dt.month(), 28).unwrap());
new_date.and_time(dt.time()).and_utc()
}
_ => return Err(Error::new("DATEADD unit must be one of: days, hours, minutes, seconds, weeks, months, years", None)),
};
Ok(Value::DateTime(new_dt.timestamp()))
}
"DATEDIFF" => {
if args.len() < 3 {
return Err(Error::new("DATEDIFF expects date1, date2, unit", None));
}
let timestamp1 = match args.get(0) {
Some(Value::DateTime(ts)) => *ts,
_ => return Err(Error::new("DATEDIFF expects datetime as first argument", None)),
};
let timestamp2 = match args.get(1) {
Some(Value::DateTime(ts)) => *ts,
_ => return Err(Error::new("DATEDIFF expects datetime as second argument", None)),
};
let unit = match args.get(2) {
Some(Value::String(s)) => s.to_lowercase(),
_ => return Err(Error::new("DATEDIFF expects string unit as third argument", None)),
};
let dt1 = DateTime::from_timestamp(timestamp1, 0)
.ok_or_else(|| Error::new("Invalid timestamp1", None))?;
let dt2 = DateTime::from_timestamp(timestamp2, 0)
.ok_or_else(|| Error::new("Invalid timestamp2", None))?;
let duration = dt2.signed_duration_since(dt1);
let diff = match unit.as_str() {
"days" | "day" | "d" => duration.num_days() as f64,
"hours" | "hour" | "h" => duration.num_hours() as f64,
"minutes" | "minute" | "m" => duration.num_minutes() as f64,
"seconds" | "second" | "s" => duration.num_seconds() as f64,
"weeks" | "week" | "w" => duration.num_weeks() as f64,
"months" | "month" => {
let years_diff = dt2.year() - dt1.year();
let months_diff = dt2.month() as i32 - dt1.month() as i32;
(years_diff * 12 + months_diff) as f64
}
"years" | "year" | "y" => (dt2.year() - dt1.year()) as f64,
_ => return Err(Error::new("DATEDIFF unit must be one of: days, hours, minutes, seconds, weeks, months, years", None)),
};
Ok(Value::Number(diff))
}
_ => Err(Error::new(format!("Unknown datetime function: {}", name), None)),
}
}