diff --git a/core/function.rs b/core/function.rs index 4c235cca5..41613c8c8 100644 --- a/core/function.rs +++ b/core/function.rs @@ -293,6 +293,7 @@ pub enum ScalarFunc { StrfTime, Printf, Likely, + TimeDiff, } impl Display for ScalarFunc { @@ -348,6 +349,7 @@ impl Display for ScalarFunc { Self::StrfTime => "strftime".to_string(), Self::Printf => "printf".to_string(), Self::Likely => "likely".to_string(), + Self::TimeDiff => "timediff".to_string(), }; write!(f, "{}", str) } @@ -555,6 +557,12 @@ impl Func { } Ok(Self::Agg(AggFunc::Total)) } + "timediff" => { + if arg_count != 2 { + crate::bail_parse_error!("wrong number of arguments to function {}()", name) + } + Ok(Self::Scalar(ScalarFunc::TimeDiff)) + } #[cfg(feature = "json")] "jsonb_group_array" => Ok(Self::Agg(AggFunc::JsonbGroupArray)), #[cfg(feature = "json")] diff --git a/core/functions/datetime.rs b/core/functions/datetime.rs index 294fbfb2d..e9988f03f 100644 --- a/core/functions/datetime.rs +++ b/core/functions/datetime.rs @@ -656,6 +656,61 @@ fn parse_modifier(modifier: &str) -> Result { } } +pub fn exec_timediff(values: &[Register]) -> OwnedValue { + if values.len() < 2 { + return OwnedValue::Null; + } + + let start = parse_naive_date_time(values[0].get_owned_value()); + let end = parse_naive_date_time(values[1].get_owned_value()); + + match (start, end) { + (Some(start), Some(end)) => { + let duration = start.signed_duration_since(end); + format_time_duration(&duration) + } + _ => OwnedValue::Null, + } +} + +fn format_time_duration(duration: &chrono::Duration) -> OwnedValue { + let is_negative = duration.num_seconds() < 0; + + let abs_duration = if is_negative { + -duration.clone() + } else { + duration.clone() + }; + let total_seconds = abs_duration.num_seconds(); + let hours = total_seconds / 3600; + let minutes = (total_seconds % 3600) / 60; + let seconds = total_seconds % 60; + + let total_millis = abs_duration.num_milliseconds(); + let millis = total_millis % 1000; + + let result = if millis > 0 { + format!( + "{}{:02}:{:02}:{:02}.{:03}", + if is_negative { "-" } else { "" }, + hours, + minutes, + seconds, + millis + ) + } else { + format!( + "{}{:02}:{:02}:{:02}", + if is_negative { "-" } else { "" }, + hours, + minutes, + seconds + ) + }; + + OwnedValue::build_text(&result) +} + #[cfg(test)] mod tests { use super::*; @@ -1642,4 +1697,67 @@ mod tests { #[test] fn test_strftime() {} + + #[test] + fn test_exec_timediff() { + let start = OwnedValue::build_text("12:00:00"); + let end = OwnedValue::build_text("14:30:45"); + let expected = OwnedValue::build_text("-02:30:45"); + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::build_text("14:30:45"); + let end = OwnedValue::build_text("12:00:00"); + let expected = OwnedValue::build_text("02:30:45"); + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::build_text("12:00:01.300"); + let end = OwnedValue::build_text("12:00:00.500"); + let expected = OwnedValue::build_text("00:00:00.800"); + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::build_text("13:30:00"); + let end = OwnedValue::build_text("16:45:30"); + let expected = OwnedValue::build_text("-03:15:30"); + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::build_text("2023-05-10 23:30:00"); + let end = OwnedValue::build_text("2023-05-11 01:15:00"); + let expected = OwnedValue::build_text("-01:45:00"); + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::Null; + let end = OwnedValue::build_text("12:00:00"); + let expected = OwnedValue::Null; + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::build_text("not a time"); + let end = OwnedValue::build_text("12:00:00"); + let expected = OwnedValue::Null; + assert_eq!( + exec_timediff(&[Register::OwnedValue(start), Register::OwnedValue(end)]), + expected + ); + + let start = OwnedValue::build_text("12:00:00"); + let expected = OwnedValue::Null; + assert_eq!(exec_timediff(&[Register::OwnedValue(start)]), expected); + } } diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 7bb0dc228..fbd7680f4 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1309,6 +1309,33 @@ pub fn translate_expr( }); Ok(target_register) } + ScalarFunc::TimeDiff => { + let args = expect_arguments_exact!(args, 2, srf); + + let start_reg = program.alloc_registers(2); + translate_expr( + program, + referenced_tables, + &args[0], + start_reg, + resolver, + )?; + translate_expr( + program, + referenced_tables, + &args[1], + start_reg + 1, + resolver, + )?; + + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg, + dest: target_register, + func: func_ctx, + }); + Ok(target_register) + } ScalarFunc::TotalChanges => { if args.is_some() { crate::bail_parse_error!( diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 6827ba83b..e5194536d 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -3406,6 +3406,21 @@ pub fn op_function( let result = exec_time(values); state.registers[*dest] = Register::OwnedValue(result); } + ScalarFunc::TimeDiff => { + if arg_count != 2 { + state.registers[*dest] = Register::OwnedValue(OwnedValue::Null); + } else { + let start = state.registers[*start_reg].get_owned_value().clone(); + let end = state.registers[*start_reg + 1].get_owned_value().clone(); + + let result = crate::functions::datetime::exec_timediff(&[ + Register::OwnedValue(start), + Register::OwnedValue(end), + ]); + + state.registers[*dest] = Register::OwnedValue(result); + } + } ScalarFunc::TotalChanges => { let res = &program.connection.upgrade().unwrap().total_changes; let total_changes = res.get();