pub(crate) mod arith;
use chrono::Utc;
use crate::{CompiledNode, Engine, Error, Result};
use datavalue::{DataDateTime, DataDuration};
use crate::arena::{ContextStack, DataValue};
use bumpalo::Bump;
#[inline]
pub(crate) fn extract_datetime(av: &DataValue<'_>) -> Option<DataDateTime> {
match av {
DataValue::DateTime(dt) => Some(*dt),
DataValue::String(s) => DataDateTime::parse(s),
DataValue::Object(pairs) => {
for (k, v) in *pairs {
if *k == "datetime" {
if let DataValue::String(s) = v {
return DataDateTime::parse(s);
}
}
}
None
}
_ => None,
}
}
#[inline]
pub(crate) fn extract_duration(av: &DataValue<'_>) -> Option<DataDuration> {
match av {
DataValue::Duration(d) => Some(*d),
DataValue::String(s) => DataDuration::parse(s),
DataValue::Object(pairs) => {
for (k, v) in *pairs {
if *k == "timestamp" {
if let DataValue::String(s) = v {
return DataDuration::parse(s);
}
}
}
None
}
_ => None,
}
}
#[inline]
fn arg_as_str<'a>(av: &'a DataValue<'a>) -> Option<&'a str> {
match av {
DataValue::String(s) => Some(*s),
_ => None,
}
}
#[inline]
fn is_datetime_object(av: &DataValue<'_>) -> bool {
matches!(av, DataValue::Object(pairs) if pairs.iter().any(|(k, _)| *k == "datetime"))
}
#[inline]
fn is_duration_object(av: &DataValue<'_>) -> bool {
matches!(av, DataValue::Object(pairs) if pairs.iter().any(|(k, _)| *k == "timestamp"))
}
#[inline]
pub(crate) fn evaluate_datetime<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Err(Error::invalid_arguments("datetime requires an argument"));
}
let av = engine.dispatch_node(&args[0], ctx, arena)?;
if is_datetime_object(av) {
return Ok(av);
}
if let Some(s) = arg_as_str(av) {
if DataDateTime::parse(s).is_some() {
return Ok(av);
}
}
Err(Error::invalid_arguments("Invalid datetime format"))
}
#[inline]
pub(crate) fn evaluate_timestamp<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.is_empty() {
return Err(Error::invalid_arguments("timestamp requires an argument"));
}
let av = engine.dispatch_node(&args[0], ctx, arena)?;
if is_duration_object(av) {
return Ok(av);
}
if let Some(s) = arg_as_str(av) {
if let Some(duration) = DataDuration::parse(s) {
let s: &'a str = arena.alloc_str(&duration.to_string());
return Ok(arena.alloc(DataValue::String(s)));
}
}
Err(Error::invalid_arguments("Invalid duration format"))
}
#[inline]
fn jsonlogic_to_chrono_format(format: &str) -> String {
format
.replace("yyyy", "%Y")
.replace("MM", "%m")
.replace("dd", "%d")
.replace("HH", "%H")
.replace("mm", "%M")
.replace("ss", "%S")
}
#[inline]
pub(crate) fn evaluate_parse_date<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 2 {
return Err(Error::invalid_arguments(
"parse_date requires date string and format",
));
}
let date_av = engine.dispatch_node(&args[0], ctx, arena)?;
let fmt_av = engine.dispatch_node(&args[1], ctx, arena)?;
if let (Some(date), Some(fmt)) = (arg_as_str(date_av), arg_as_str(fmt_av)) {
let chrono_format = jsonlogic_to_chrono_format(fmt);
if let Some(dt) = DataDateTime::parse_with_format(date, &chrono_format) {
let iso = dt.to_iso_string();
let s: &'a str = arena.alloc_str(&iso);
return Ok(arena.alloc(DataValue::String(s)));
}
}
Err(Error::invalid_arguments("Failed to parse date"))
}
#[inline]
pub(crate) fn evaluate_format_date<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 2 {
return Err(Error::invalid_arguments(
"format_date requires datetime and format",
));
}
let dt_av = engine.dispatch_node(&args[0], ctx, arena)?;
let fmt_av = engine.dispatch_node(&args[1], ctx, arena)?;
let dt: Option<DataDateTime> = crate::operators::datetime::extract_datetime(dt_av);
let fmt: &'a str =
arg_as_str(fmt_av).ok_or_else(|| Error::invalid_arguments("Failed to format date"))?;
if let Some(datetime) = dt {
let chrono_format = if fmt == "z" {
fmt.to_string()
} else {
jsonlogic_to_chrono_format(fmt)
};
let formatted = datetime.format(&chrono_format);
let s: &'a str = arena.alloc_str(&formatted);
return Ok(arena.alloc(DataValue::String(s)));
}
Err(Error::invalid_arguments("Failed to format date"))
}
#[inline]
pub(crate) fn evaluate_date_diff<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if args.len() < 3 {
return Err(Error::invalid_arguments(
"date_diff requires two dates and a unit",
));
}
let d1_av = engine.dispatch_node(&args[0], ctx, arena)?;
let d2_av = engine.dispatch_node(&args[1], ctx, arena)?;
let unit_av = engine.dispatch_node(&args[2], ctx, arena)?;
let resolve_dt = |av: &'a DataValue<'a>| -> Option<DataDateTime> {
crate::operators::datetime::extract_datetime(av)
};
let dt1 = resolve_dt(d1_av);
let dt2 = resolve_dt(d2_av);
let unit = arg_as_str(unit_av);
if let (Some(a), Some(b), Some(u)) = (dt1, dt2, unit) {
let diff = a.diff_in_unit(&b, u);
return Ok(arena.alloc(DataValue::from_i64(diff as i64)));
}
Err(Error::invalid_arguments(
"Failed to calculate date difference",
))
}
#[inline]
pub(crate) fn evaluate_now<'a>(
_args: &[CompiledNode],
_ctx: &mut ContextStack<'a>,
_engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let now = Utc::now();
let data_dt = DataDateTime {
dt: now,
original_offset: Some(0),
};
let s: &'a str = arena.alloc_str(&data_dt.to_iso_string());
Ok(arena.alloc(DataValue::String(s)))
}