use crate::executor::Environment;
use crate::value::Value;
fn epoch_secs_to_iso(secs: u64) -> String {
let total_days = (secs / 86400) as i64;
let time_of_day = secs % 86400;
let hours = time_of_day / 3600;
let minutes = (time_of_day % 3600) / 60;
let seconds = time_of_day % 60;
let mut days = total_days;
let mut year = 1970i64;
loop {
let days_in_year = if is_leap_year(year) { 366 } else { 365 };
if days < days_in_year {
break;
}
days -= days_in_year;
year += 1;
}
let days_in_months: [i64; 12] = if is_leap_year(year) {
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
};
let mut month = 0;
for (i, &dim) in days_in_months.iter().enumerate() {
if days < dim {
month = i + 1;
break;
}
days -= dim;
}
if month == 0 {
month = 12;
}
let day = days + 1;
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.000Z",
year, month, day, hours, minutes, seconds
)
}
fn is_leap_year(year: i64) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
pub fn register(env: &mut Environment) {
env.register_builtin("time.now", |_args, _| {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Ok(Value::String(epoch_secs_to_iso(secs)))
});
env.register_builtin("time.timestamp", |_args, _| {
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as f64;
Ok(Value::Number(ts))
});
env.register_builtin("time.format", |args, _| {
if let Some(Value::String(s)) = args.first() {
Ok(Value::String(s.clone()))
} else {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Ok(Value::String(epoch_secs_to_iso(secs)))
}
});
env.register_builtin("time.addDays", |args, _| {
if let Some(Value::String(s)) = args.first() {
let days = args.get(1).and_then(|v| v.as_number()).unwrap_or(0.0) as i64;
if let Some(secs) = parse_iso_to_epoch(s) {
let new_secs = (secs as i64 + days * 86400) as u64;
Ok(Value::String(epoch_secs_to_iso(new_secs)))
} else {
Ok(Value::String(s.clone()))
}
} else {
Ok(Value::Null)
}
});
env.register_builtin("time.diffDays", |args, _| {
let date1 = args.first().and_then(|v| v.as_str()).unwrap_or("");
let date2 = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
match (parse_iso_to_epoch(date1), parse_iso_to_epoch(date2)) {
(Some(d1), Some(d2)) => {
let diff = (d2 as i64 - d1 as i64).abs() / 86400;
Ok(Value::Number(diff as f64))
}
_ => Ok(Value::Number(0.0)),
}
});
}
fn parse_iso_to_epoch(s: &str) -> Option<u64> {
let date_part = s.split('T').next()?;
let parts: Vec<&str> = date_part.split('-').collect();
if parts.len() < 3 {
return None;
}
let year: i64 = parts[0].parse().ok()?;
let month: usize = parts[1].parse().ok()?;
let day: i64 = parts[2].parse().ok()?;
let days_in_months: [i64; 12] = if is_leap_year(year) {
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
};
let mut total_days: i64 = 0;
for y in 1970..year {
total_days += if is_leap_year(y) { 366 } else { 365 };
}
for m in 0..(month - 1) {
total_days += days_in_months[m];
}
total_days += day - 1;
Some((total_days * 86400) as u64)
}