use crate::shared::models::UserSession; use crate::shared::state::AppState; use chrono::{Datelike, Local, Timelike, Utc}; use log::debug; use rhai::{Dynamic, Engine, Map}; use std::sync::Arc; fn create_datetime_map(local: chrono::DateTime) -> Map { let mut map = Map::new(); map.insert("year".into(), Dynamic::from(i64::from(local.year()))); map.insert("month".into(), Dynamic::from(i64::from(local.month()))); map.insert("day".into(), Dynamic::from(i64::from(local.day()))); map.insert("hour".into(), Dynamic::from(i64::from(local.hour()))); map.insert("minute".into(), Dynamic::from(i64::from(local.minute()))); map.insert("second".into(), Dynamic::from(i64::from(local.second()))); map.insert( "weekday".into(), Dynamic::from(i64::from(local.weekday().num_days_from_sunday()) + 1), ); let weekday_name = match local.weekday().num_days_from_sunday() { 0 => "Sunday", 1 => "Monday", 2 => "Tuesday", 3 => "Wednesday", 4 => "Thursday", 5 => "Friday", 6 => "Saturday", _ => "Unknown", }; map.insert( "weekday_name".into(), Dynamic::from(weekday_name.to_string()), ); let month_name = match local.month() { 1 => "January", 2 => "February", 3 => "March", 4 => "April", 5 => "May", 6 => "June", 7 => "July", 8 => "August", 9 => "September", 10 => "October", 11 => "November", 12 => "December", _ => "Unknown", }; map.insert("month_name".into(), Dynamic::from(month_name.to_string())); map.insert("timestamp".into(), Dynamic::from(local.timestamp())); map.insert( "formatted".into(), Dynamic::from(local.format("%Y-%m-%d %H:%M:%S").to_string()), ); map.insert( "date".into(), Dynamic::from(local.format("%Y-%m-%d").to_string()), ); map.insert( "time".into(), Dynamic::from(local.format("%H:%M:%S").to_string()), ); map.insert( "iso".into(), Dynamic::from(local.format("%Y-%m-%dT%H:%M:%S%z").to_string()), ); let quarter = ((local.month() - 1) / 3) + 1; map.insert("quarter".into(), Dynamic::from(i64::from(quarter))); map.insert( "day_of_year".into(), Dynamic::from(i64::from(local.ordinal())), ); let is_weekend = local.weekday().num_days_from_sunday() == 0 || local.weekday().num_days_from_sunday() == 6; map.insert("is_weekend".into(), Dynamic::from(is_weekend)); let is_pm = local.hour() >= 12; map.insert("is_pm".into(), Dynamic::from(is_pm)); map.insert( "ampm".into(), Dynamic::from(if is_pm { "PM" } else { "AM" }.to_string()), ); let hour12 = if local.hour() == 0 { 12 } else if local.hour() > 12 { local.hour() - 12 } else { local.hour() }; map.insert("hour12".into(), Dynamic::from(i64::from(hour12))); map } pub fn now_keyword(_state: &Arc, _user: UserSession, engine: &mut Engine) { engine.register_fn("NOW", || -> Map { create_datetime_map(Local::now()) }); engine.register_fn("now", || -> Map { create_datetime_map(Local::now()) }); engine.register_fn("NOW_UTC", || -> Map { let utc = Utc::now(); let local = utc.with_timezone(&Local); create_datetime_map(local) }); engine.register_fn("NOW_STR", || -> String { Local::now().format("%Y-%m-%d %H:%M:%S").to_string() }); engine.register_fn("now_str", || -> String { Local::now().format("%Y-%m-%d %H:%M:%S").to_string() }); debug!("Registered NOW keyword with .property access"); } pub fn today_keyword(_state: &Arc, _user: UserSession, engine: &mut Engine) { engine.register_fn("TODAY", || -> Map { let now = Local::now(); let mut map = Map::new(); map.insert("year".into(), Dynamic::from(i64::from(now.year()))); map.insert("month".into(), Dynamic::from(i64::from(now.month()))); map.insert("day".into(), Dynamic::from(i64::from(now.day()))); map.insert( "weekday".into(), Dynamic::from(i64::from(now.weekday().num_days_from_sunday()) + 1), ); map.insert( "formatted".into(), Dynamic::from(now.format("%Y-%m-%d").to_string()), ); map.insert( "day_of_year".into(), Dynamic::from(i64::from(now.ordinal())), ); let is_weekend = now.weekday().num_days_from_sunday() == 0 || now.weekday().num_days_from_sunday() == 6; map.insert("is_weekend".into(), Dynamic::from(is_weekend)); let quarter = ((now.month() - 1) / 3) + 1; map.insert("quarter".into(), Dynamic::from(i64::from(quarter))); map }); engine.register_fn("today", || -> Map { let now = Local::now(); let mut map = Map::new(); map.insert("year".into(), Dynamic::from(i64::from(now.year()))); map.insert("month".into(), Dynamic::from(i64::from(now.month()))); map.insert("day".into(), Dynamic::from(i64::from(now.day()))); map.insert( "weekday".into(), Dynamic::from(i64::from(now.weekday().num_days_from_sunday()) + 1), ); map.insert( "formatted".into(), Dynamic::from(now.format("%Y-%m-%d").to_string()), ); map }); engine.register_fn("TODAY_STR", || -> String { Local::now().format("%Y-%m-%d").to_string() }); engine.register_fn("today_str", || -> String { Local::now().format("%Y-%m-%d").to_string() }); debug!("Registered TODAY keyword with .property access"); } pub fn time_keyword(_state: &Arc, _user: UserSession, engine: &mut Engine) { engine.register_fn("TIME", || -> Map { let now = Local::now(); let mut map = Map::new(); map.insert("hour".into(), Dynamic::from(i64::from(now.hour()))); map.insert("minute".into(), Dynamic::from(i64::from(now.minute()))); map.insert("second".into(), Dynamic::from(i64::from(now.second()))); map.insert( "formatted".into(), Dynamic::from(now.format("%H:%M:%S").to_string()), ); let is_pm = now.hour() >= 12; map.insert("is_pm".into(), Dynamic::from(is_pm)); map.insert( "ampm".into(), Dynamic::from(if is_pm { "PM" } else { "AM" }.to_string()), ); let hour12 = if now.hour() == 0 { 12 } else if now.hour() > 12 { now.hour() - 12 } else { now.hour() }; map.insert("hour12".into(), Dynamic::from(i64::from(hour12))); map }); engine.register_fn("time", || -> Map { let now = Local::now(); let mut map = Map::new(); map.insert("hour".into(), Dynamic::from(i64::from(now.hour()))); map.insert("minute".into(), Dynamic::from(i64::from(now.minute()))); map.insert("second".into(), Dynamic::from(i64::from(now.second()))); map.insert( "formatted".into(), Dynamic::from(now.format("%H:%M:%S").to_string()), ); map }); engine.register_fn("TIME_STR", || -> String { Local::now().format("%H:%M:%S").to_string() }); engine.register_fn("TIMESTAMP", || -> i64 { Utc::now().timestamp() }); engine.register_fn("timestamp", || -> i64 { Utc::now().timestamp() }); debug!("Registered TIME keyword with .property access"); } pub fn timestamp_keyword(_state: &Arc, _user: UserSession, engine: &mut Engine) { engine.register_fn("UNIX_TIMESTAMP", || -> i64 { Utc::now().timestamp() }); engine.register_fn("TIMESTAMP_MS", || -> i64 { Utc::now().timestamp_millis() }); debug!("Registered TIMESTAMP keyword"); } #[cfg(test)] mod tests { use super::*; use chrono::Local; #[test] fn test_create_datetime_map() { let now = Local::now(); let map = create_datetime_map(now); assert!(map.contains_key("year")); assert!(map.contains_key("month")); assert!(map.contains_key("day")); assert!(map.contains_key("hour")); assert!(map.contains_key("minute")); assert!(map.contains_key("second")); assert!(map.contains_key("weekday")); assert!(map.contains_key("timestamp")); assert!(map.contains_key("formatted")); assert!(map.contains_key("is_weekend")); assert!(map.contains_key("quarter")); } #[test] fn test_year_extraction() { let now = Local::now(); let map = create_datetime_map(now); let year = map.get("year").unwrap().as_int().unwrap(); assert!(year >= 2024); } #[test] fn test_month_range() { let now = Local::now(); let map = create_datetime_map(now); let month = map.get("month").unwrap().as_int().unwrap(); assert!((1..=12).contains(&month)); } #[test] fn test_hour12_range() { let now = Local::now(); let map = create_datetime_map(now); let hour12 = map.get("hour12").unwrap().as_int().unwrap(); assert!((1..=12).contains(&hour12)); } #[test] fn test_quarter_calculation() { let now = Local::now(); let map = create_datetime_map(now); let quarter = map.get("quarter").unwrap().as_int().unwrap(); assert!((1..=4).contains(&quarter)); } }