datafusion_functions/datetime/
make_date.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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        // first, identify if any of the arguments is an Array. If yes, store its `len`,
114        // as any scalar will need to be converted to an array of len `len`.
115        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            // For scalar only columns the operation is faster without using the PrimitiveArray.
180            // Also, keep the output as scalar since all inputs are scalar.
181            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
199/// Converts the year/month/day fields to an `i32` representing the days from
200/// the unix epoch and invokes `date_consumer_fn` with the value
201fn 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        // The number of days until the start of the unix epoch in the proleptic Gregorian calendar
216        // (with January 1, Year 1 (CE) as day 1). See [Datelike::num_days_from_ce].
217        const UNIX_DAYS_FROM_CE: i32 = 719_163;
218
219        // since the epoch for the date32 datatype is the unix epoch
220        // we need to subtract the unix epoch from the current date
221        // note that this can result in a negative value
222        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        };
254        MakeDateFunc::new().invoke_with_args(args)
255    }
256
257    #[test]
258    fn test_make_date() {
259        let res = invoke_make_date_with_args(
260            vec![
261                ColumnarValue::Scalar(ScalarValue::Int32(Some(2024))),
262                ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
263                ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
264            ],
265            1,
266        )
267        .expect("that make_date parsed values without error");
268
269        if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
270            assert_eq!(19736, date.unwrap());
271        } else {
272            panic!("Expected a scalar value")
273        }
274
275        let res = invoke_make_date_with_args(
276            vec![
277                ColumnarValue::Scalar(ScalarValue::Int64(Some(2024))),
278                ColumnarValue::Scalar(ScalarValue::UInt64(Some(1))),
279                ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
280            ],
281            1,
282        )
283        .expect("that make_date parsed values without error");
284
285        if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
286            assert_eq!(19736, date.unwrap());
287        } else {
288            panic!("Expected a scalar value")
289        }
290
291        let res = invoke_make_date_with_args(
292            vec![
293                ColumnarValue::Scalar(ScalarValue::Utf8(Some("2024".to_string()))),
294                ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("1".to_string()))),
295                ColumnarValue::Scalar(ScalarValue::Utf8(Some("14".to_string()))),
296            ],
297            1,
298        )
299        .expect("that make_date parsed values without error");
300
301        if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
302            assert_eq!(19736, date.unwrap());
303        } else {
304            panic!("Expected a scalar value")
305        }
306
307        let years = Arc::new((2021..2025).map(Some).collect::<Int64Array>());
308        let months = Arc::new((1..5).map(Some).collect::<Int32Array>());
309        let days = Arc::new((11..15).map(Some).collect::<UInt32Array>());
310        let batch_len = years.len();
311        let res = invoke_make_date_with_args(
312            vec![
313                ColumnarValue::Array(years),
314                ColumnarValue::Array(months),
315                ColumnarValue::Array(days),
316            ],
317            batch_len,
318        )
319        .unwrap();
320
321        if let ColumnarValue::Array(array) = res {
322            assert_eq!(array.len(), 4);
323            let mut builder = Date32Array::builder(4);
324            builder.append_value(18_638);
325            builder.append_value(19_035);
326            builder.append_value(19_429);
327            builder.append_value(19_827);
328            assert_eq!(&builder.finish() as &dyn Array, array.as_ref());
329        } else {
330            panic!("Expected a columnar array")
331        }
332
333        //
334        // Fallible test cases
335        //
336
337        // invalid number of arguments
338        let res = invoke_make_date_with_args(
339            vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
340            1,
341        );
342        assert_eq!(
343            res.err().unwrap().strip_backtrace(),
344            "Execution error: make_date function requires 3 arguments, got 1"
345        );
346
347        // invalid type
348        let res = invoke_make_date_with_args(
349            vec![
350                ColumnarValue::Scalar(ScalarValue::IntervalYearMonth(Some(1))),
351                ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
352                ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
353            ],
354            1,
355        );
356        assert_eq!(
357            res.err().unwrap().strip_backtrace(),
358            "Arrow error: Cast error: Casting from Interval(YearMonth) to Int32 not supported"
359        );
360
361        // overflow of month
362        let res = invoke_make_date_with_args(
363            vec![
364                ColumnarValue::Scalar(ScalarValue::Int32(Some(2023))),
365                ColumnarValue::Scalar(ScalarValue::UInt64(Some(u64::MAX))),
366                ColumnarValue::Scalar(ScalarValue::Int32(Some(22))),
367            ],
368            1,
369        );
370        assert_eq!(
371            res.err().unwrap().strip_backtrace(),
372            "Arrow error: Cast error: Can't cast value 18446744073709551615 to type Int32"
373        );
374
375        // overflow of day
376        let res = invoke_make_date_with_args(
377            vec![
378                ColumnarValue::Scalar(ScalarValue::Int32(Some(2023))),
379                ColumnarValue::Scalar(ScalarValue::Int32(Some(22))),
380                ColumnarValue::Scalar(ScalarValue::UInt32(Some(u32::MAX))),
381            ],
382            1,
383        );
384        assert_eq!(
385            res.err().unwrap().strip_backtrace(),
386            "Arrow error: Cast error: Can't cast value 4294967295 to type Int32"
387        );
388    }
389
390    #[test]
391    fn test_make_date_null_param() {
392        let res = invoke_make_date_with_args(
393            vec![
394                ColumnarValue::Scalar(ScalarValue::Null),
395                ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
396                ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
397            ],
398            1,
399        )
400        .expect("that make_date parsed values without error");
401
402        assert!(matches!(res, ColumnarValue::Scalar(ScalarValue::Null)));
403    }
404}