Skip to main content

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::sync::Arc;
19
20use arrow::array::builder::PrimitiveBuilder;
21use arrow::array::cast::AsArray;
22use arrow::array::types::{Date32Type, Int32Type};
23use arrow::array::{Array, PrimitiveArray};
24use arrow::datatypes::DataType;
25use arrow::datatypes::DataType::Date32;
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    Coercion, ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
32    TypeSignatureClass, Volatility,
33};
34use datafusion_macros::user_doc;
35
36#[user_doc(
37    doc_section(label = "Time and Date Functions"),
38    description = "Make a date from year/month/day component parts.",
39    syntax_example = "make_date(year, month, day)",
40    sql_example = r#"```sql
41> select make_date(2023, 1, 31);
42+-------------------------------------------+
43| make_date(Int64(2023),Int64(1),Int64(31)) |
44+-------------------------------------------+
45| 2023-01-31                                |
46+-------------------------------------------+
47> select make_date('2023', '01', '31');
48+-----------------------------------------------+
49| make_date(Utf8("2023"),Utf8("01"),Utf8("31")) |
50+-----------------------------------------------+
51| 2023-01-31                                    |
52+-----------------------------------------------+
53```
54
55Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
56"#,
57    argument(
58        name = "year",
59        description = "Year to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
60    ),
61    argument(
62        name = "month",
63        description = "Month to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
64    ),
65    argument(
66        name = "day",
67        description = "Day to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
68    )
69)]
70#[derive(Debug, PartialEq, Eq, Hash)]
71pub struct MakeDateFunc {
72    signature: Signature,
73}
74
75impl Default for MakeDateFunc {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81impl MakeDateFunc {
82    pub fn new() -> Self {
83        let int = Coercion::new_implicit(
84            TypeSignatureClass::Native(logical_int32()),
85            vec![
86                TypeSignatureClass::Integer,
87                TypeSignatureClass::Native(logical_string()),
88            ],
89            NativeType::Int32,
90        );
91        Self {
92            signature: Signature::coercible(vec![int; 3], Volatility::Immutable),
93        }
94    }
95}
96
97impl ScalarUDFImpl for MakeDateFunc {
98    fn name(&self) -> &str {
99        "make_date"
100    }
101
102    fn signature(&self) -> &Signature {
103        &self.signature
104    }
105
106    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
107        Ok(Date32)
108    }
109
110    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
111        let [years, months, days] = take_function_args(self.name(), args.args)?;
112
113        match (years, months, days) {
114            (ColumnarValue::Scalar(y), _, _) if y.is_null() => {
115                Ok(ColumnarValue::Scalar(ScalarValue::Date32(None)))
116            }
117            (_, ColumnarValue::Scalar(m), _) if m.is_null() => {
118                Ok(ColumnarValue::Scalar(ScalarValue::Date32(None)))
119            }
120            (_, _, ColumnarValue::Scalar(d)) if d.is_null() => {
121                Ok(ColumnarValue::Scalar(ScalarValue::Date32(None)))
122            }
123            (
124                ColumnarValue::Scalar(ScalarValue::Int32(Some(years))),
125                ColumnarValue::Scalar(ScalarValue::Int32(Some(months))),
126                ColumnarValue::Scalar(ScalarValue::Int32(Some(days))),
127            ) => {
128                let mut value = 0;
129                make_date_inner(years, months, days, |days: i32| value = days)?;
130                Ok(ColumnarValue::Scalar(ScalarValue::Date32(Some(value))))
131            }
132            (years, months, days) => {
133                let len = args.number_rows;
134                let years = years.into_array(len)?;
135                let months = months.into_array(len)?;
136                let days = days.into_array(len)?;
137
138                let years = years.as_primitive::<Int32Type>();
139                let months = months.as_primitive::<Int32Type>();
140                let days = days.as_primitive::<Int32Type>();
141
142                let mut builder: PrimitiveBuilder<Date32Type> =
143                    PrimitiveArray::builder(len);
144
145                for i in 0..len {
146                    // match postgresql behaviour which returns null for any null input
147                    if years.is_null(i) || months.is_null(i) || days.is_null(i) {
148                        builder.append_null();
149                    } else {
150                        make_date_inner(
151                            years.value(i),
152                            months.value(i),
153                            days.value(i),
154                            |days: i32| builder.append_value(days),
155                        )?;
156                    }
157                }
158
159                Ok(ColumnarValue::Array(Arc::new(builder.finish())))
160            }
161        }
162    }
163
164    fn documentation(&self) -> Option<&Documentation> {
165        self.doc()
166    }
167}
168
169/// Converts the year/month/day fields to an `i32` representing the days from
170/// the unix epoch and invokes `date_consumer_fn` with the value
171fn make_date_inner<F: FnMut(i32)>(
172    year: i32,
173    month: i32,
174    day: i32,
175    mut date_consumer_fn: F,
176) -> Result<()> {
177    let m = match month {
178        1..=12 => month as u32,
179        _ => return exec_err!("Month value '{month:?}' is out of range"),
180    };
181    let d = match day {
182        1..=31 => day as u32,
183        _ => return exec_err!("Day value '{day:?}' is out of range"),
184    };
185
186    if let Some(date) = NaiveDate::from_ymd_opt(year, m, d) {
187        // The number of days until the start of the unix epoch in the proleptic Gregorian calendar
188        // (with January 1, Year 1 (CE) as day 1). See [Datelike::num_days_from_ce].
189        const UNIX_DAYS_FROM_CE: i32 = 719_163;
190
191        // since the epoch for the date32 datatype is the unix epoch
192        // we need to subtract the unix epoch from the current date
193        // note that this can result in a negative value
194        date_consumer_fn(date.num_days_from_ce() - UNIX_DAYS_FROM_CE);
195        Ok(())
196    } else {
197        exec_err!("Unable to parse date from {year}, {month}, {day}")
198    }
199}