use chrono::{Datelike, NaiveDate};
use crate::eval::functions::check_arity;
use crate::eval::functions::date::serial::date_to_serial;
use crate::types::{ErrorKind, Value};
fn has_two_digit_slash_year(s: &str) -> bool {
let parts: Vec<&str> = s.split('/').collect();
if parts.len() == 3 {
let y = parts[2].trim();
y.len() <= 2 && y.chars().all(|c| c.is_ascii_digit())
} else {
false
}
}
pub fn datevalue_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 1, 1) {
return err;
}
let text = match &args[0] {
Value::Text(s) => s.clone(),
Value::Error(_) => return args[0].clone(),
_ => return Value::Error(ErrorKind::Value),
};
if text.is_empty() {
return Value::Error(ErrorKind::Value);
}
let date_part: &str = if let Some(idx) = text.find(' ') {
let after = &text[idx + 1..];
let looks_like_time = after.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false)
&& after.contains(':');
if looks_like_time { &text[..idx] } else { text.as_str() }
} else {
text.as_str()
};
let formats_4y = [
"%m/%d/%Y",
"%Y-%m-%d",
"%Y/%m/%d",
"%d-%b-%Y",
"%B %d, %Y",
"%b %d, %Y",
"%B %d %Y",
"%b %d %Y",
];
for fmt in &formats_4y {
if *fmt == "%m/%d/%Y" && has_two_digit_slash_year(date_part) {
continue;
}
if let Ok(date) = NaiveDate::parse_from_str(date_part, fmt) {
return Value::Number(date_to_serial(date));
}
}
let formats_2y: &[&str] = &["%m/%d/%y", "%d-%b-%y"];
for fmt in formats_2y {
if let Ok(date) = NaiveDate::parse_from_str(date_part, fmt) {
let y = date.year();
let century_year = if y <= 29 { y + 2000 } else if y <= 99 { y + 1900 } else { y };
if let Some(remapped) = NaiveDate::from_ymd_opt(century_year, date.month(), date.day()) {
return Value::Number(date_to_serial(remapped));
}
}
}
Value::Error(ErrorKind::Value)
}
#[cfg(test)]
mod tests;