This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 8c8e5651d0 feat(spark): implement Spark `make_dt_interval` function
(#17728)
8c8e5651d0 is described below
commit 8c8e5651d0ca3d4b41c1de2ed66d3624263acdbc
Author: David López <[email protected]>
AuthorDate: Sat Sep 27 03:03:36 2025 +0200
feat(spark): implement Spark `make_dt_interval` function (#17728)
* feat(spark): implement Spark make_dt_interval function
* fmt
* delete pub
* test slt
* fmt
* overflow -> null
* sugested changes
* fmt
* only res in slt
* null not void type
* explain types
* explain types fix url
* better comment
---
.../src/function/datetime/make_dt_interval.rs | 485 +++++++++++++++++++++
.../spark/src/function/datetime/make_interval.rs | 2 +-
datafusion/spark/src/function/datetime/mod.rs | 10 +-
.../test_files/spark/datetime/make_dt_interval.slt | 125 +++++-
4 files changed, 614 insertions(+), 8 deletions(-)
diff --git a/datafusion/spark/src/function/datetime/make_dt_interval.rs
b/datafusion/spark/src/function/datetime/make_dt_interval.rs
new file mode 100644
index 0000000000..c44ab69b8b
--- /dev/null
+++ b/datafusion/spark/src/function/datetime/make_dt_interval.rs
@@ -0,0 +1,485 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::any::Any;
+use std::sync::Arc;
+
+use arrow::array::{
+ Array, ArrayRef, AsArray, DurationMicrosecondBuilder, PrimitiveArray,
+};
+use arrow::datatypes::TimeUnit::Microsecond;
+use arrow::datatypes::{DataType, Float64Type, Int32Type};
+use datafusion_common::{
+ exec_err, plan_datafusion_err, DataFusionError, Result, ScalarValue,
+};
+use datafusion_expr::{
+ ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
+};
+use datafusion_functions::utils::make_scalar_function;
+
+#[derive(Debug, PartialEq, Eq, Hash)]
+pub struct SparkMakeDtInterval {
+ signature: Signature,
+}
+
+impl Default for SparkMakeDtInterval {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl SparkMakeDtInterval {
+ pub fn new() -> Self {
+ Self {
+ signature: Signature::user_defined(Volatility::Immutable),
+ }
+ }
+}
+
+impl ScalarUDFImpl for SparkMakeDtInterval {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn name(&self) -> &str {
+ "make_dt_interval"
+ }
+
+ fn signature(&self) -> &Signature {
+ &self.signature
+ }
+
+ /// Note the return type is `DataType::Duration(TimeUnit::Microsecond)`
and not `DataType::Interval(DayTime)` as you might expect.
+ /// This is because `DataType::Interval(DayTime)` has precision only to
the millisecond, whilst Spark's `DayTimeIntervalType` has
+ /// precision to the microsecond. We use
`DataType::Duration(TimeUnit::Microsecond)` in order to not lose any precision.
See the
+ /// [Sail compatibility doc] for reference.
+ ///
+ /// [Sail compatibility doc]:
https://github.com/lakehq/sail/blob/dc5368daa24d40a7758a299e1ba8fc985cb29108/docs/guide/dataframe/data-types/compatibility.md?plain=1#L260
+ fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
+ Ok(DataType::Duration(Microsecond))
+ }
+
+ fn invoke_with_args(&self, args: ScalarFunctionArgs) ->
Result<ColumnarValue> {
+ if args.args.is_empty() {
+ return Ok(ColumnarValue::Scalar(ScalarValue::DurationMicrosecond(
+ Some(0),
+ )));
+ }
+ make_scalar_function(make_dt_interval_kernel, vec![])(&args.args)
+ }
+
+ fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
+ if arg_types.len() > 4 {
+ return exec_err!(
+ "make_dt_interval expects between 0 and 4 arguments, got {}",
+ arg_types.len()
+ );
+ }
+
+ Ok((0..arg_types.len())
+ .map(|i| {
+ if i == 3 {
+ DataType::Float64
+ } else {
+ DataType::Int32
+ }
+ })
+ .collect())
+ }
+}
+
+fn make_dt_interval_kernel(args: &[ArrayRef]) -> Result<ArrayRef,
DataFusionError> {
+ let n_rows = args[0].len();
+ let days = args[0]
+ .as_primitive_opt::<Int32Type>()
+ .ok_or_else(|| plan_datafusion_err!("make_dt_interval arg[0] must be
Int32"))?;
+ let hours: Option<&PrimitiveArray<Int32Type>> = args
+ .get(1)
+ .map(|a| {
+ a.as_primitive_opt::<Int32Type>().ok_or_else(|| {
+ plan_datafusion_err!("make_dt_interval arg[1] must be Int32")
+ })
+ })
+ .transpose()?;
+ let mins: Option<&PrimitiveArray<Int32Type>> = args
+ .get(2)
+ .map(|a| {
+ a.as_primitive_opt::<Int32Type>().ok_or_else(|| {
+ plan_datafusion_err!("make_dt_interval arg[2] must be Int32")
+ })
+ })
+ .transpose()?;
+ let secs: Option<&PrimitiveArray<Float64Type>> = args
+ .get(3)
+ .map(|a| {
+ a.as_primitive_opt::<Float64Type>().ok_or_else(|| {
+ plan_datafusion_err!("make_dt_interval arg[3] must be Float64")
+ })
+ })
+ .transpose()?;
+ let mut builder = DurationMicrosecondBuilder::with_capacity(n_rows);
+
+ for i in 0..n_rows {
+ // if one column is NULL → result NULL
+ let any_null_present = days.is_null(i)
+ || hours.as_ref().is_some_and(|a| a.is_null(i))
+ || mins.as_ref().is_some_and(|a| a.is_null(i))
+ || secs
+ .as_ref()
+ .is_some_and(|a| a.is_null(i) || !a.value(i).is_finite());
+
+ if any_null_present {
+ builder.append_null();
+ continue;
+ }
+
+ // default values 0 or 0.0
+ let d = days.value(i);
+ let h = hours.as_ref().map_or(0, |a| a.value(i));
+ let mi = mins.as_ref().map_or(0, |a| a.value(i));
+ let s = secs.as_ref().map_or(0.0, |a| a.value(i));
+
+ match make_interval_dt_nano(d, h, mi, s) {
+ Some(v) => builder.append_value(v),
+ None => {
+ builder.append_null();
+ continue;
+ }
+ }
+ }
+
+ Ok(Arc::new(builder.finish()))
+}
+fn make_interval_dt_nano(day: i32, hour: i32, min: i32, sec: f64) ->
Option<i64> {
+ const HOURS_PER_DAY: i32 = 24;
+ const MINS_PER_HOUR: i32 = 60;
+ const SECS_PER_MINUTE: i64 = 60;
+ const MICROS_PER_SEC: i64 = 1_000_000;
+
+ let total_hours: i32 = day
+ .checked_mul(HOURS_PER_DAY)
+ .and_then(|v| v.checked_add(hour))?;
+
+ let total_mins: i32 = total_hours
+ .checked_mul(MINS_PER_HOUR)
+ .and_then(|v| v.checked_add(min))?;
+
+ let mut sec_whole: i64 = sec.trunc() as i64;
+ let sec_frac: f64 = sec - (sec_whole as f64);
+ let mut frac_us: i64 = (sec_frac * (MICROS_PER_SEC as f64)).round() as i64;
+
+ if frac_us.abs() >= MICROS_PER_SEC {
+ if frac_us > 0 {
+ frac_us -= MICROS_PER_SEC;
+ sec_whole = sec_whole.checked_add(1)?;
+ } else {
+ frac_us += MICROS_PER_SEC;
+ sec_whole = sec_whole.checked_sub(1)?;
+ }
+ }
+
+ let total_secs: i64 = (total_mins as i64)
+ .checked_mul(SECS_PER_MINUTE)
+ .and_then(|v| v.checked_add(sec_whole))?;
+
+ let total_us = total_secs
+ .checked_mul(MICROS_PER_SEC)
+ .and_then(|v| v.checked_add(frac_us))?;
+
+ Some(total_us)
+}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use arrow::array::{DurationMicrosecondArray, Float64Array, Int32Array};
+ use arrow::datatypes::DataType::Duration;
+ use arrow::datatypes::Field;
+ use arrow::datatypes::TimeUnit::Microsecond;
+ use datafusion_common::{DataFusionError, Result};
+ use datafusion_expr::{ColumnarValue, ScalarFunctionArgs};
+
+ use super::*;
+
+ fn run_make_dt_interval(arrs: Vec<ArrayRef>) -> Result<ArrayRef> {
+ make_dt_interval_kernel(&arrs)
+ }
+
+ #[test]
+ fn nulls_propagate_per_row() -> Result<()> {
+ let days = Arc::new(Int32Array::from(vec![
+ None,
+ Some(2),
+ Some(3),
+ Some(4),
+ Some(5),
+ Some(6),
+ Some(7),
+ ])) as ArrayRef;
+
+ let hours = Arc::new(Int32Array::from(vec![
+ Some(1),
+ None,
+ Some(3),
+ Some(4),
+ Some(5),
+ Some(6),
+ Some(7),
+ ])) as ArrayRef;
+
+ let mins = Arc::new(Int32Array::from(vec![
+ Some(1),
+ Some(2),
+ None,
+ Some(4),
+ Some(5),
+ Some(6),
+ Some(7),
+ ])) as ArrayRef;
+
+ let secs = Arc::new(Float64Array::from(vec![
+ Some(1.0),
+ Some(2.0),
+ Some(3.0),
+ None,
+ Some(f64::NAN),
+ Some(f64::INFINITY),
+ Some(f64::NEG_INFINITY),
+ ])) as ArrayRef;
+
+ let out = run_make_dt_interval(vec![days, hours, mins, secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ for i in 0..out.len() {
+ assert!(out.is_null(i), "row {i} should be NULL");
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn error_months_overflow_should_be_null() -> Result<()> {
+ // months = year*12 + month → NULL
+
+ let days = Arc::new(Int32Array::from(vec![Some(i32::MAX)])) as
ArrayRef;
+
+ let hours = Arc::new(Int32Array::from(vec![Some(1)])) as ArrayRef;
+
+ let mins = Arc::new(Int32Array::from(vec![Some(1)])) as ArrayRef;
+
+ let secs = Arc::new(Float64Array::from(vec![Some(1.0)])) as ArrayRef;
+
+ let out = run_make_dt_interval(vec![days, hours, mins, secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ for i in 0..out.len() {
+ assert!(out.is_null(i), "row {i} should be NULL");
+ }
+
+ Ok(())
+ }
+
+ fn invoke_make_dt_interval_with_args(
+ args: Vec<ColumnarValue>,
+ number_rows: usize,
+ ) -> Result<ColumnarValue, DataFusionError> {
+ let arg_fields = args
+ .iter()
+ .map(|arg| Field::new("a", arg.data_type(), true).into())
+ .collect::<Vec<_>>();
+ let args = ScalarFunctionArgs {
+ args,
+ arg_fields,
+ number_rows,
+ return_field: Field::new("f", Duration(Microsecond), true).into(),
+ config_options: Arc::new(Default::default()),
+ };
+ SparkMakeDtInterval::new().invoke_with_args(args)
+ }
+
+ #[test]
+ fn zero_args_returns_zero_duration() -> Result<()> {
+ let number_rows: usize = 3;
+
+ let res: ColumnarValue = invoke_make_dt_interval_with_args(vec![],
number_rows)?;
+ let arr = res.into_array(number_rows)?;
+ let arr = arr
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ assert_eq!(arr.len(), number_rows);
+ for i in 0..number_rows {
+ assert!(!arr.is_null(i));
+ assert_eq!(arr.value(i), 0_i64);
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn one_day_minus_24_hours_equals_zero() -> Result<()> {
+ let arr_days = Arc::new(Int32Array::from(vec![Some(1), Some(-1)])) as
ArrayRef;
+ let arr_hours = Arc::new(Int32Array::from(vec![Some(-24), Some(24)]))
as ArrayRef;
+ let arr_mins = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let arr_secs =
+ Arc::new(Float64Array::from(vec![Some(0.0), Some(0.0)])) as
ArrayRef;
+
+ let out = run_make_dt_interval(vec![arr_days, arr_hours, arr_mins,
arr_secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ assert_eq!(out.len(), 2);
+ assert_eq!(out.null_count(), 0);
+ assert_eq!(out.value(0), 0_i64);
+ assert_eq!(out.value(1), 0_i64);
+ Ok(())
+ }
+
+ #[test]
+ fn one_hour_minus_60_mins_equals_zero() -> Result<()> {
+ let arr_days = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let arr_hours = Arc::new(Int32Array::from(vec![Some(-1), Some(1)])) as
ArrayRef;
+ let arr_mins = Arc::new(Int32Array::from(vec![Some(60), Some(-60)]))
as ArrayRef;
+ let arr_secs =
+ Arc::new(Float64Array::from(vec![Some(0.0), Some(0.0)])) as
ArrayRef;
+
+ let out = run_make_dt_interval(vec![arr_days, arr_hours, arr_mins,
arr_secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ assert_eq!(out.len(), 2);
+ assert_eq!(out.null_count(), 0);
+ assert_eq!(out.value(0), 0_i64);
+ assert_eq!(out.value(1), 0_i64);
+ Ok(())
+ }
+
+ #[test]
+ fn one_mins_minus_60_secs_equals_zero() -> Result<()> {
+ let arr_days = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let arr_hours = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let arr_mins = Arc::new(Int32Array::from(vec![Some(-1), Some(1)])) as
ArrayRef;
+ let arr_secs =
+ Arc::new(Float64Array::from(vec![Some(60.0), Some(-60.0)])) as
ArrayRef;
+
+ let out = run_make_dt_interval(vec![arr_days, arr_hours, arr_mins,
arr_secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ assert_eq!(out.len(), 2);
+ assert_eq!(out.null_count(), 0);
+ assert_eq!(out.value(0), 0_i64);
+ assert_eq!(out.value(1), 0_i64);
+ Ok(())
+ }
+
+ #[test]
+ fn frac_carries_up_to_next_second_positive() -> Result<()> {
+ // 0.9999995s → 1_000_000 µs (carry a +1s)
+ let days = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let hours = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let mins = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let secs = Arc::new(Float64Array::from(vec![
+ Some(0.999_999_5),
+ Some(0.999_999_4),
+ ])) as ArrayRef;
+
+ let out = run_make_dt_interval(vec![days, hours, mins, secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ assert_eq!(out.len(), 2);
+ assert_eq!(out.value(0), 1_000_000);
+ assert_eq!(out.value(1), 999_999);
+ Ok(())
+ }
+
+ #[test]
+ fn frac_carries_down_to_prev_second_negative() -> Result<()> {
+ // -0.9999995s → -1_000_000 µs (carry a −1s)
+ let days = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let hours = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let mins = Arc::new(Int32Array::from(vec![Some(0), Some(0)])) as
ArrayRef;
+ let secs = Arc::new(Float64Array::from(vec![
+ Some(-0.999_999_5),
+ Some(-0.999_999_4),
+ ])) as ArrayRef;
+
+ let out = run_make_dt_interval(vec![days, hours, mins, secs])?;
+ let out = out
+ .as_any()
+ .downcast_ref::<DurationMicrosecondArray>()
+ .ok_or_else(|| {
+ DataFusionError::Internal("expected
DurationMicrosecondArray".into())
+ })?;
+
+ assert_eq!(out.len(), 2);
+ assert_eq!(out.value(0), -1_000_000);
+ assert_eq!(out.value(1), -999_999);
+ Ok(())
+ }
+
+ #[test]
+ fn no_more_than_4_params() -> Result<()> {
+ let udf = SparkMakeDtInterval::new();
+
+ let arg_types = vec![
+ DataType::Int32,
+ DataType::Int32,
+ DataType::Int32,
+ DataType::Float64,
+ DataType::Int32,
+ ];
+
+ let res = udf.coerce_types(&arg_types);
+
+ assert!(
+ matches!(res, Err(DataFusionError::Execution(_))),
+ "make_dt_interval expects between 0 and 4 arguments, got 5"
+ );
+
+ Ok(())
+ }
+}
diff --git a/datafusion/spark/src/function/datetime/make_interval.rs
b/datafusion/spark/src/function/datetime/make_interval.rs
index ae0e63cc99..c66f97ff5c 100644
--- a/datafusion/spark/src/function/datetime/make_interval.rs
+++ b/datafusion/spark/src/function/datetime/make_interval.rs
@@ -195,7 +195,7 @@ fn make_interval_kernel(args: &[ArrayRef]) ->
Result<ArrayRef, DataFusionError>
Ok(Arc::new(builder.finish()))
}
-pub fn make_interval_month_day_nano(
+fn make_interval_month_day_nano(
year: i32,
month: i32,
week: i32,
diff --git a/datafusion/spark/src/function/datetime/mod.rs
b/datafusion/spark/src/function/datetime/mod.rs
index c4dee81a2c..a6adc99607 100644
--- a/datafusion/spark/src/function/datetime/mod.rs
+++ b/datafusion/spark/src/function/datetime/mod.rs
@@ -18,6 +18,7 @@
pub mod date_add;
pub mod date_sub;
pub mod last_day;
+pub mod make_dt_interval;
pub mod make_interval;
pub mod next_day;
@@ -28,6 +29,7 @@ use std::sync::Arc;
make_udf_function!(date_add::SparkDateAdd, date_add);
make_udf_function!(date_sub::SparkDateSub, date_sub);
make_udf_function!(last_day::SparkLastDay, last_day);
+make_udf_function!(make_dt_interval::SparkMakeDtInterval, make_dt_interval);
make_udf_function!(make_interval::SparkMakeInterval, make_interval);
make_udf_function!(next_day::SparkNextDay, next_day);
@@ -49,10 +51,15 @@ pub mod expr_fn {
"Returns the last day of the month which the date belongs to.",
arg1
));
+ export_functions!((
+ make_dt_interval,
+ "Make a day time interval from given days, hours, mins and secs
(return type is actually a Duration(Microsecond))",
+ days hours mins secs
+ ));
export_functions!((
make_interval,
"Make interval from years, months, weeks, days, hours, mins and secs.",
- arg1 arg2
+ years months weeks days hours mins secs
));
// TODO: add once ANSI support is added:
// "When both of the input parameters are not NULL and day_of_week is an
invalid input, the function throws SparkIllegalArgumentException if
spark.sql.ansi.enabled is set to true, otherwise NULL."
@@ -68,6 +75,7 @@ pub fn functions() -> Vec<Arc<ScalarUDF>> {
date_add(),
date_sub(),
last_day(),
+ make_dt_interval(),
make_interval(),
next_day(),
]
diff --git
a/datafusion/sqllogictest/test_files/spark/datetime/make_dt_interval.slt
b/datafusion/sqllogictest/test_files/spark/datetime/make_dt_interval.slt
index e5c69cfbb8..dc6c33caa9 100644
--- a/datafusion/sqllogictest/test_files/spark/datetime/make_dt_interval.slt
+++ b/datafusion/sqllogictest/test_files/spark/datetime/make_dt_interval.slt
@@ -23,15 +23,128 @@
## Original Query: SELECT make_dt_interval(1, 12, 30, 01.001001);
## PySpark 3.5.5 Result: {'make_dt_interval(1, 12, 30, 1.001001)':
datetime.timedelta(days=1, seconds=45001, microseconds=1001),
'typeof(make_dt_interval(1, 12, 30, 1.001001))': 'interval day to second',
'typeof(1)': 'int', 'typeof(12)': 'int', 'typeof(30)': 'int',
'typeof(1.001001)': 'decimal(7,6)'}
-#query
-#SELECT make_dt_interval(1::int, 12::int, 30::int, 1.001001::decimal(7,6));
+query ?
+SELECT make_dt_interval(1::int, 12::int, 30::int, 1.001001::decimal(7,6));
+----
+1 days 12 hours 30 mins 1.001001 secs
## Original Query: SELECT make_dt_interval(100, null, 3);
## PySpark 3.5.5 Result: {'make_dt_interval(100, NULL, 3, 0.000000)': None,
'typeof(make_dt_interval(100, NULL, 3, 0.000000))': 'interval day to second',
'typeof(100)': 'int', 'typeof(NULL)': 'void', 'typeof(3)': 'int'}
-#query
-#SELECT make_dt_interval(100::int, NULL::void, 3::int);
+query ?
+SELECT make_dt_interval(100::int, NULL, 3::int);
+----
+NULL
## Original Query: SELECT make_dt_interval(2);
## PySpark 3.5.5 Result: {'make_dt_interval(2, 0, 0, 0.000000)':
datetime.timedelta(days=2), 'typeof(make_dt_interval(2, 0, 0, 0.000000))':
'interval day to second', 'typeof(2)': 'int'}
-#query
-#SELECT make_dt_interval(2::int);
+query ?
+SELECT make_dt_interval(2::int);
+----
+2 days 0 hours 0 mins 0.000000 secs
+
+# null
+query ?
+SELECT (make_dt_interval(null, 0, 0, 0))
+----
+NULL
+
+query ?
+SELECT (make_dt_interval(0, null, 0, 0))
+----
+NULL
+
+query ?
+SELECT (make_dt_interval(0, 0, null, 0))
+----
+NULL
+
+query ?
+SELECT (make_dt_interval(0, 0, 0, null))
+----
+NULL
+
+# missing params
+query ?
+SELECT (make_dt_interval()) AS make_dt_interval
+----
+0 days 0 hours 0 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(1)) AS make_dt_interval
+----
+1 days 0 hours 0 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(1, 1)) AS make_dt_interval
+----
+1 days 1 hours 0 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(1, 1, 1)) AS make_dt_interval
+----
+1 days 1 hours 1 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(1, 1, 1, 1)) AS make_dt_interval
+----
+1 days 1 hours 1 mins 1.000000 secs
+
+
+# all 0 values
+query ?
+SELECT (make_dt_interval(0, 0, 0, 0))
+----
+0 days 0 hours 0 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(-1, 24, 0, 0)) df
+----
+0 days 0 hours 0 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(1, -24, 0, 0)) dt
+----
+0 days 0 hours 0 mins 0.000000 secs
+
+query ?
+SELECT (make_dt_interval(0, 0, 0, 0.1))
+----
+0 days 0 hours 0 mins 0.100000 secs
+
+
+# doctest
https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.make_dt_interval.html
+# extract only the value make_dt_interval
+
+query ?
+SELECT MAKE_DT_INTERVAL(day) AS interval_val
+FROM VALUES (1, 12, 30, 1.001001) AS t(day, hour, min, sec);
+----
+1 days 0 hours 0 mins 0.000000 secs
+
+query ?
+SELECT MAKE_DT_INTERVAL(day, hour) AS interval_val
+FROM VALUES (1, 12, 30, 1.001001) AS t(day, hour, min, sec);
+----
+1 days 12 hours 0 mins 0.000000 secs
+
+query ?
+SELECT MAKE_DT_INTERVAL(day, hour, min) AS interval_val
+FROM VALUES (1, 12, 30, 1.001001) AS t(day, hour, min, sec);
+----
+1 days 12 hours 30 mins 0.000000 secs
+
+query ?
+SELECT MAKE_DT_INTERVAL(day, hour, min, sec) AS interval_val
+FROM VALUES (1, 12, 30, 1.001001) AS t(day, hour, min, sec);
+----
+1 days 12 hours 30 mins 1.001001 secs
+
+query ?
+SELECT MAKE_DT_INTERVAL(1, 12, 30, 1.001001)
+----
+1 days 12 hours 30 mins 1.001001 secs
+
+query ?
+SELECT MAKE_DT_INTERVAL(1, 12, 30, 1.001001);
+----
+1 days 12 hours 30 mins 1.001001 secs
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]