datafusion_functions/datetime/
make_time.rs1use std::sync::Arc;
19
20use arrow::array::cast::AsArray;
21use arrow::array::types::Int32Type;
22use arrow::array::{Array, PrimitiveArray};
23use arrow::buffer::NullBuffer;
24use arrow::datatypes::DataType::Time32;
25use arrow::datatypes::{DataType, Time32SecondType, TimeUnit};
26use chrono::prelude::*;
27
28use datafusion_common::types::{NativeType, logical_int32, logical_string};
29use datafusion_common::{Result, ScalarValue, exec_err, utils::take_function_args};
30use datafusion_expr::{
31 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
32 Volatility,
33};
34use datafusion_expr_common::signature::{Coercion, TypeSignatureClass};
35use datafusion_macros::user_doc;
36
37#[user_doc(
38 doc_section(label = "Time and Date Functions"),
39 description = "Make a time from hour/minute/second component parts.",
40 syntax_example = "make_time(hour, minute, second)",
41 sql_example = r#"```sql
42> select make_time(13, 23, 1);
43+-------------------------------------------+
44| make_time(Int64(13),Int64(23),Int64(1)) |
45+-------------------------------------------+
46| 13:23:01 |
47+-------------------------------------------+
48> select make_time('23', '01', '31');
49+-----------------------------------------------+
50| make_time(Utf8("23"),Utf8("01"),Utf8("31")) |
51+-----------------------------------------------+
52| 23:01:31 |
53+-----------------------------------------------+
54```
55
56Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
57"#,
58 argument(
59 name = "hour",
60 description = "Hour to use when making the time. Can be a constant, column or function, and any combination of arithmetic operators."
61 ),
62 argument(
63 name = "minute",
64 description = "Minute to use when making the time. Can be a constant, column or function, and any combination of arithmetic operators."
65 ),
66 argument(
67 name = "second",
68 description = "Second to use when making the time. Can be a constant, column or function, and any combination of arithmetic operators."
69 )
70)]
71#[derive(Debug, PartialEq, Eq, Hash)]
72pub struct MakeTimeFunc {
73 signature: Signature,
74}
75
76impl Default for MakeTimeFunc {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl MakeTimeFunc {
83 pub fn new() -> Self {
84 let int = Coercion::new_implicit(
85 TypeSignatureClass::Native(logical_int32()),
86 vec![
87 TypeSignatureClass::Integer,
88 TypeSignatureClass::Native(logical_string()),
89 ],
90 NativeType::Int32,
91 );
92 Self {
93 signature: Signature::coercible(vec![int; 3], Volatility::Immutable),
94 }
95 }
96}
97
98impl ScalarUDFImpl for MakeTimeFunc {
99 fn name(&self) -> &str {
100 "make_time"
101 }
102
103 fn signature(&self) -> &Signature {
104 &self.signature
105 }
106
107 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
108 Ok(Time32(TimeUnit::Second))
109 }
110
111 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
112 let [hours, minutes, seconds] = take_function_args(self.name(), args.args)?;
113
114 match (hours, minutes, seconds) {
115 (ColumnarValue::Scalar(h), _, _) if h.is_null() => {
116 Ok(ColumnarValue::Scalar(ScalarValue::Time32Second(None)))
117 }
118 (_, ColumnarValue::Scalar(m), _) if m.is_null() => {
119 Ok(ColumnarValue::Scalar(ScalarValue::Time32Second(None)))
120 }
121 (_, _, ColumnarValue::Scalar(s)) if s.is_null() => {
122 Ok(ColumnarValue::Scalar(ScalarValue::Time32Second(None)))
123 }
124 (
125 ColumnarValue::Scalar(ScalarValue::Int32(Some(hours))),
126 ColumnarValue::Scalar(ScalarValue::Int32(Some(minutes))),
127 ColumnarValue::Scalar(ScalarValue::Int32(Some(seconds))),
128 ) => {
129 let mut value = 0;
130 make_time_inner(hours, minutes, seconds, |seconds: i32| value = seconds)?;
131 Ok(ColumnarValue::Scalar(ScalarValue::Time32Second(Some(
132 value,
133 ))))
134 }
135 (hours, minutes, seconds) => {
136 let len = args.number_rows;
137 let hours = hours.into_array(len)?;
138 let minutes = minutes.into_array(len)?;
139 let seconds = seconds.into_array(len)?;
140
141 let hours = hours.as_primitive::<Int32Type>();
142 let minutes = minutes.as_primitive::<Int32Type>();
143 let seconds = seconds.as_primitive::<Int32Type>();
144
145 let nulls = NullBuffer::union_many([
146 hours.nulls(),
147 minutes.nulls(),
148 seconds.nulls(),
149 ]);
150
151 let mut values = Vec::with_capacity(len);
152 for i in 0..len {
153 if nulls.as_ref().is_some_and(|n| n.is_null(i)) {
155 values.push(0);
156 } else {
157 make_time_inner(
158 hours.value(i),
159 minutes.value(i),
160 seconds.value(i),
161 |seconds: i32| values.push(seconds),
162 )?;
163 }
164 }
165
166 Ok(ColumnarValue::Array(Arc::new(PrimitiveArray::<
167 Time32SecondType,
168 >::new(
169 values.into(), nulls
170 ))))
171 }
172 }
173 }
174
175 fn documentation(&self) -> Option<&Documentation> {
176 self.doc()
177 }
178}
179
180fn make_time_inner<F: FnMut(i32)>(
183 hour: i32,
184 minute: i32,
185 second: i32,
186 mut time_consumer_fn: F,
187) -> Result<()> {
188 let h = match hour {
189 0..=24 => hour as u32,
190 _ => return exec_err!("Hour value '{hour:?}' is out of range"),
191 };
192 let m = match minute {
193 0..=60 => minute as u32,
194 _ => return exec_err!("Minute value '{minute:?}' is out of range"),
195 };
196 let s = match second {
197 0..=60 => second as u32,
198 _ => return exec_err!("Second value '{second:?}' is out of range"),
199 };
200
201 if let Some(time) = NaiveTime::from_hms_opt(h, m, s) {
202 time_consumer_fn(time.num_seconds_from_midnight() as i32);
203 Ok(())
204 } else {
205 exec_err!("Unable to parse time from {hour}, {minute}, {second}")
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use crate::datetime::make_time::MakeTimeFunc;
212 use arrow::array::{Array, Int32Array, Time32SecondArray};
213 use arrow::datatypes::TimeUnit::Second;
214 use arrow::datatypes::{DataType, Field};
215 use datafusion_common::DataFusionError;
216 use datafusion_common::config::ConfigOptions;
217 use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl};
218 use std::sync::Arc;
219
220 fn invoke_make_time_with_args(
221 args: Vec<ColumnarValue>,
222 number_rows: usize,
223 ) -> Result<ColumnarValue, DataFusionError> {
224 let arg_fields = args
225 .iter()
226 .map(|arg| Field::new("a", arg.data_type(), true).into())
227 .collect::<Vec<_>>();
228 let args = ScalarFunctionArgs {
229 args,
230 arg_fields,
231 number_rows,
232 return_field: Field::new("f", DataType::Time32(Second), true).into(),
233 config_options: Arc::new(ConfigOptions::default()),
234 };
235
236 MakeTimeFunc::new().invoke_with_args(args)
237 }
238
239 #[test]
240 fn test_make_time() {
241 let hours = Arc::new((4..8).map(Some).collect::<Int32Array>());
242 let minutes = Arc::new((1..5).map(Some).collect::<Int32Array>());
243 let seconds = Arc::new((11..15).map(Some).collect::<Int32Array>());
244 let batch_len = hours.len();
245 let res = invoke_make_time_with_args(
246 vec![
247 ColumnarValue::Array(hours),
248 ColumnarValue::Array(minutes),
249 ColumnarValue::Array(seconds),
250 ],
251 batch_len,
252 )
253 .unwrap();
254
255 if let ColumnarValue::Array(array) = res {
256 assert_eq!(array.len(), 4);
257
258 let mut builder = Time32SecondArray::builder(4);
259 builder.append_value(14_471);
260 builder.append_value(18_132);
261 builder.append_value(21_793);
262 builder.append_value(25_454);
263 assert_eq!(&builder.finish() as &dyn Array, array.as_ref());
264 } else {
265 panic!("Expected a columnar array")
266 }
267 }
268}