datafusion_functions/datetime/
make_date.rs1use std::any::Any;
19use std::sync::Arc;
20
21use arrow::array::builder::PrimitiveBuilder;
22use arrow::array::cast::AsArray;
23use arrow::array::types::{Date32Type, Int32Type};
24use arrow::array::PrimitiveArray;
25use arrow::datatypes::DataType;
26use arrow::datatypes::DataType::{Date32, Int32, Int64, UInt32, UInt64, Utf8, Utf8View};
27use chrono::prelude::*;
28
29use datafusion_common::{exec_err, utils::take_function_args, Result, ScalarValue};
30use datafusion_expr::{
31 ColumnarValue, Documentation, ScalarUDFImpl, Signature, Volatility,
32};
33use datafusion_macros::user_doc;
34
35#[user_doc(
36 doc_section(label = "Time and Date Functions"),
37 description = "Make a date from year/month/day component parts.",
38 syntax_example = "make_date(year, month, day)",
39 sql_example = r#"```sql
40> select make_date(2023, 1, 31);
41+-------------------------------------------+
42| make_date(Int64(2023),Int64(1),Int64(31)) |
43+-------------------------------------------+
44| 2023-01-31 |
45+-------------------------------------------+
46> select make_date('2023', '01', '31');
47+-----------------------------------------------+
48| make_date(Utf8("2023"),Utf8("01"),Utf8("31")) |
49+-----------------------------------------------+
50| 2023-01-31 |
51+-----------------------------------------------+
52```
53
54Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/date_time_functions.rs)
55"#,
56 argument(
57 name = "year",
58 description = "Year to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
59 ),
60 argument(
61 name = "month",
62 description = "Month to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
63 ),
64 argument(
65 name = "day",
66 description = "Day to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
67 )
68)]
69#[derive(Debug, PartialEq, Eq, Hash)]
70pub struct MakeDateFunc {
71 signature: Signature,
72}
73
74impl Default for MakeDateFunc {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl MakeDateFunc {
81 pub fn new() -> Self {
82 Self {
83 signature: Signature::uniform(
84 3,
85 vec![Int32, Int64, UInt32, UInt64, Utf8, Utf8View],
86 Volatility::Immutable,
87 ),
88 }
89 }
90}
91
92impl ScalarUDFImpl for MakeDateFunc {
93 fn as_any(&self) -> &dyn Any {
94 self
95 }
96
97 fn name(&self) -> &str {
98 "make_date"
99 }
100
101 fn signature(&self) -> &Signature {
102 &self.signature
103 }
104
105 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
106 Ok(Date32)
107 }
108
109 fn invoke_with_args(
110 &self,
111 args: datafusion_expr::ScalarFunctionArgs,
112 ) -> Result<ColumnarValue> {
113 let args = args.args;
116 let len = args
117 .iter()
118 .fold(Option::<usize>::None, |acc, arg| match arg {
119 ColumnarValue::Scalar(_) => acc,
120 ColumnarValue::Array(a) => Some(a.len()),
121 });
122
123 let [years, months, days] = take_function_args(self.name(), args)?;
124
125 if matches!(years, ColumnarValue::Scalar(ScalarValue::Null))
126 || matches!(months, ColumnarValue::Scalar(ScalarValue::Null))
127 || matches!(days, ColumnarValue::Scalar(ScalarValue::Null))
128 {
129 return Ok(ColumnarValue::Scalar(ScalarValue::Null));
130 }
131
132 let years = years.cast_to(&Int32, None)?;
133 let months = months.cast_to(&Int32, None)?;
134 let days = days.cast_to(&Int32, None)?;
135
136 let scalar_value_fn = |col: &ColumnarValue| -> Result<i32> {
137 let ColumnarValue::Scalar(s) = col else {
138 return exec_err!("Expected scalar value");
139 };
140 let ScalarValue::Int32(Some(i)) = s else {
141 return exec_err!("Unable to parse date from null/empty value");
142 };
143 Ok(*i)
144 };
145
146 let value = if let Some(array_size) = len {
147 let to_primitive_array_fn =
148 |col: &ColumnarValue| -> PrimitiveArray<Int32Type> {
149 match col {
150 ColumnarValue::Array(a) => {
151 a.as_primitive::<Int32Type>().to_owned()
152 }
153 _ => {
154 let v = scalar_value_fn(col).unwrap();
155 PrimitiveArray::<Int32Type>::from_value(v, array_size)
156 }
157 }
158 };
159
160 let years = to_primitive_array_fn(&years);
161 let months = to_primitive_array_fn(&months);
162 let days = to_primitive_array_fn(&days);
163
164 let mut builder: PrimitiveBuilder<Date32Type> =
165 PrimitiveArray::builder(array_size);
166 for i in 0..array_size {
167 make_date_inner(
168 years.value(i),
169 months.value(i),
170 days.value(i),
171 |days: i32| builder.append_value(days),
172 )?;
173 }
174
175 let arr = builder.finish();
176
177 ColumnarValue::Array(Arc::new(arr))
178 } else {
179 let mut value = 0;
182 make_date_inner(
183 scalar_value_fn(&years)?,
184 scalar_value_fn(&months)?,
185 scalar_value_fn(&days)?,
186 |days: i32| value = days,
187 )?;
188
189 ColumnarValue::Scalar(ScalarValue::Date32(Some(value)))
190 };
191
192 Ok(value)
193 }
194 fn documentation(&self) -> Option<&Documentation> {
195 self.doc()
196 }
197}
198
199fn make_date_inner<F: FnMut(i32)>(
202 year: i32,
203 month: i32,
204 day: i32,
205 mut date_consumer_fn: F,
206) -> Result<()> {
207 let Ok(m) = u32::try_from(month) else {
208 return exec_err!("Month value '{month:?}' is out of range");
209 };
210 let Ok(d) = u32::try_from(day) else {
211 return exec_err!("Day value '{day:?}' is out of range");
212 };
213
214 if let Some(date) = NaiveDate::from_ymd_opt(year, m, d) {
215 const UNIX_DAYS_FROM_CE: i32 = 719_163;
218
219 date_consumer_fn(date.num_days_from_ce() - UNIX_DAYS_FROM_CE);
223 Ok(())
224 } else {
225 exec_err!("Unable to parse date from {year}, {month}, {day}")
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::datetime::make_date::MakeDateFunc;
232 use arrow::array::{Array, Date32Array, Int32Array, Int64Array, UInt32Array};
233 use arrow::datatypes::{DataType, Field};
234 use datafusion_common::config::ConfigOptions;
235 use datafusion_common::{DataFusionError, ScalarValue};
236 use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
237 use std::sync::Arc;
238
239 fn invoke_make_date_with_args(
240 args: Vec<ColumnarValue>,
241 number_rows: usize,
242 ) -> Result<ColumnarValue, DataFusionError> {
243 let arg_fields = args
244 .iter()
245 .map(|arg| Field::new("a", arg.data_type(), true).into())
246 .collect::<Vec<_>>();
247 let args = datafusion_expr::ScalarFunctionArgs {
248 args,
249 arg_fields,
250 number_rows,
251 return_field: Field::new("f", DataType::Date32, true).into(),
252 config_options: Arc::new(ConfigOptions::default()),
253 lambdas: None,
254 };
255 MakeDateFunc::new().invoke_with_args(args)
256 }
257
258 #[test]
259 fn test_make_date() {
260 let res = invoke_make_date_with_args(
261 vec![
262 ColumnarValue::Scalar(ScalarValue::Int32(Some(2024))),
263 ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
264 ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
265 ],
266 1,
267 )
268 .expect("that make_date parsed values without error");
269
270 if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
271 assert_eq!(19736, date.unwrap());
272 } else {
273 panic!("Expected a scalar value")
274 }
275
276 let res = invoke_make_date_with_args(
277 vec![
278 ColumnarValue::Scalar(ScalarValue::Int64(Some(2024))),
279 ColumnarValue::Scalar(ScalarValue::UInt64(Some(1))),
280 ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
281 ],
282 1,
283 )
284 .expect("that make_date parsed values without error");
285
286 if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
287 assert_eq!(19736, date.unwrap());
288 } else {
289 panic!("Expected a scalar value")
290 }
291
292 let res = invoke_make_date_with_args(
293 vec![
294 ColumnarValue::Scalar(ScalarValue::Utf8(Some("2024".to_string()))),
295 ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("1".to_string()))),
296 ColumnarValue::Scalar(ScalarValue::Utf8(Some("14".to_string()))),
297 ],
298 1,
299 )
300 .expect("that make_date parsed values without error");
301
302 if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
303 assert_eq!(19736, date.unwrap());
304 } else {
305 panic!("Expected a scalar value")
306 }
307
308 let years = Arc::new((2021..2025).map(Some).collect::<Int64Array>());
309 let months = Arc::new((1..5).map(Some).collect::<Int32Array>());
310 let days = Arc::new((11..15).map(Some).collect::<UInt32Array>());
311 let batch_len = years.len();
312 let res = invoke_make_date_with_args(
313 vec![
314 ColumnarValue::Array(years),
315 ColumnarValue::Array(months),
316 ColumnarValue::Array(days),
317 ],
318 batch_len,
319 )
320 .unwrap();
321
322 if let ColumnarValue::Array(array) = res {
323 assert_eq!(array.len(), 4);
324 let mut builder = Date32Array::builder(4);
325 builder.append_value(18_638);
326 builder.append_value(19_035);
327 builder.append_value(19_429);
328 builder.append_value(19_827);
329 assert_eq!(&builder.finish() as &dyn Array, array.as_ref());
330 } else {
331 panic!("Expected a columnar array")
332 }
333
334 let res = invoke_make_date_with_args(
340 vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
341 1,
342 );
343 assert_eq!(
344 res.err().unwrap().strip_backtrace(),
345 "Execution error: make_date function requires 3 arguments, got 1"
346 );
347
348 let res = invoke_make_date_with_args(
350 vec![
351 ColumnarValue::Scalar(ScalarValue::IntervalYearMonth(Some(1))),
352 ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
353 ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
354 ],
355 1,
356 );
357 assert_eq!(
358 res.err().unwrap().strip_backtrace(),
359 "Arrow error: Cast error: Casting from Interval(YearMonth) to Int32 not supported"
360 );
361
362 let res = invoke_make_date_with_args(
364 vec![
365 ColumnarValue::Scalar(ScalarValue::Int32(Some(2023))),
366 ColumnarValue::Scalar(ScalarValue::UInt64(Some(u64::MAX))),
367 ColumnarValue::Scalar(ScalarValue::Int32(Some(22))),
368 ],
369 1,
370 );
371 assert_eq!(
372 res.err().unwrap().strip_backtrace(),
373 "Arrow error: Cast error: Can't cast value 18446744073709551615 to type Int32"
374 );
375
376 let res = invoke_make_date_with_args(
378 vec![
379 ColumnarValue::Scalar(ScalarValue::Int32(Some(2023))),
380 ColumnarValue::Scalar(ScalarValue::Int32(Some(22))),
381 ColumnarValue::Scalar(ScalarValue::UInt32(Some(u32::MAX))),
382 ],
383 1,
384 );
385 assert_eq!(
386 res.err().unwrap().strip_backtrace(),
387 "Arrow error: Cast error: Can't cast value 4294967295 to type Int32"
388 );
389 }
390
391 #[test]
392 fn test_make_date_null_param() {
393 let res = invoke_make_date_with_args(
394 vec![
395 ColumnarValue::Scalar(ScalarValue::Null),
396 ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
397 ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
398 ],
399 1,
400 )
401 .expect("that make_date parsed values without error");
402
403 assert!(matches!(res, ColumnarValue::Scalar(ScalarValue::Null)));
404 }
405}