use crate::error::StdlibError;
use crate::module::StdlibModule;
use crate::value::Value;
const MS_PER_DAY: f64 = 86_400_000.0;
const MS_PER_SECOND: f64 = 1_000.0;
pub struct TimeModule;
impl TimeModule {
pub fn new() -> Self {
Self
}
}
impl Default for TimeModule {
fn default() -> Self {
Self::new()
}
}
impl StdlibModule for TimeModule {
fn name(&self) -> &'static str {
"time"
}
fn has_function(&self, function: &str) -> bool {
matches!(
function,
"now" | "format" | "diff" | "day_of_week" | "start_of_day"
)
}
fn call(&self, function: &str, args: Vec<Value>) -> Result<Value, StdlibError> {
match function {
"now" => self.now(args),
"format" => self.format(args),
"diff" => self.diff(args),
"day_of_week" => self.day_of_week(args),
"start_of_day" => self.start_of_day(args),
_ => Err(StdlibError::unknown_function("time", function)),
}
}
}
impl TimeModule {
fn now(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if !args.is_empty() {
return Err(StdlibError::wrong_args("time.now", 0, args.len()));
}
Ok(Value::Number(0.0))
}
fn format(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if args.len() != 2 {
return Err(StdlibError::wrong_args("time.format", 2, args.len()));
}
let ts = extract_number("time.format", &args[0], 1)?;
let pattern = extract_string("time.format", &args[1], 2)?;
let (year, month, day, hour, min, sec) = timestamp_to_parts(ts);
let result = pattern
.replace("YYYY", &format!("{:04}", year))
.replace("MM", &format!("{:02}", month))
.replace("DD", &format!("{:02}", day))
.replace("HH", &format!("{:02}", hour))
.replace("mm", &format!("{:02}", min))
.replace("ss", &format!("{:02}", sec));
Ok(Value::String(result))
}
fn diff(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if args.len() != 2 {
return Err(StdlibError::wrong_args("time.diff", 2, args.len()));
}
let a = extract_number("time.diff", &args[0], 1)?;
let b = extract_number("time.diff", &args[1], 2)?;
Ok(Value::Number(a - b))
}
fn day_of_week(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if args.len() != 1 {
return Err(StdlibError::wrong_args("time.day_of_week", 1, args.len()));
}
let ts = extract_number("time.day_of_week", &args[0], 1)?;
let days = (ts / MS_PER_DAY).floor() as i64;
let dow = ((days % 7 + 4) % 7 + 7) % 7;
Ok(Value::Number(dow as f64))
}
fn start_of_day(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
if args.len() != 1 {
return Err(StdlibError::wrong_args("time.start_of_day", 1, args.len()));
}
let ts = extract_number("time.start_of_day", &args[0], 1)?;
let day_start = (ts / MS_PER_DAY).floor() * MS_PER_DAY;
Ok(Value::Number(day_start))
}
}
fn timestamp_to_parts(ts: f64) -> (i64, u32, u32, u32, u32, u32) {
let total_ms = ts as i64;
let total_sec = total_ms.div_euclid(MS_PER_SECOND as i64);
let sec = total_sec.rem_euclid(60) as u32;
let total_min = total_sec.div_euclid(60);
let min = total_min.rem_euclid(60) as u32;
let total_hour = total_min.div_euclid(60);
let hour = total_hour.rem_euclid(24) as u32;
let days = total_hour.div_euclid(24);
let (year, month, day) = days_to_civil(days);
(year, month, day, hour, min, sec)
}
fn days_to_civil(days: i64) -> (i64, u32, u32) {
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u32; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
(y, m, d)
}
fn extract_number(func: &str, val: &Value, pos: usize) -> Result<f64, StdlibError> {
match val {
Value::Number(n) => Ok(*n),
_ => Err(StdlibError::type_mismatch(
func,
pos,
"number",
val.type_name(),
)),
}
}
fn extract_string<'a>(func: &str, val: &'a Value, pos: usize) -> Result<&'a str, StdlibError> {
match val {
Value::String(s) => Ok(s),
_ => Err(StdlibError::type_mismatch(
func,
pos,
"string",
val.type_name(),
)),
}
}