datafusion_functions/datetime/
from_unixtime.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::datatypes::DataType;
22use arrow::datatypes::DataType::{Int64, Timestamp, Utf8};
23use arrow::datatypes::TimeUnit::Second;
24use datafusion_common::{exec_err, internal_err, Result, ScalarValue};
25use datafusion_expr::TypeSignature::Exact;
26use datafusion_expr::{
27    ColumnarValue, Documentation, ReturnInfo, ReturnTypeArgs, ScalarUDFImpl, Signature,
28    Volatility,
29};
30use datafusion_macros::user_doc;
31
32#[user_doc(
33    doc_section(label = "Time and Date Functions"),
34    description = "Converts an integer to RFC3339 timestamp format (`YYYY-MM-DDT00:00:00.000000000Z`). Integers and unsigned integers are interpreted as nanoseconds since the unix epoch (`1970-01-01T00:00:00Z`) return the corresponding timestamp.",
35    syntax_example = "from_unixtime(expression[, timezone])",
36    sql_example = r#"```sql
37> select from_unixtime(1599572549, 'America/New_York');
38+-----------------------------------------------------------+
39| from_unixtime(Int64(1599572549),Utf8("America/New_York")) |
40+-----------------------------------------------------------+
41| 2020-09-08T09:42:29-04:00                                 |
42+-----------------------------------------------------------+
43```"#,
44    standard_argument(name = "expression",),
45    argument(
46        name = "timezone",
47        description = "Optional timezone to use when converting the integer to a timestamp. If not provided, the default timezone is UTC."
48    )
49)]
50#[derive(Debug)]
51pub struct FromUnixtimeFunc {
52    signature: Signature,
53}
54
55impl Default for FromUnixtimeFunc {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl FromUnixtimeFunc {
62    pub fn new() -> Self {
63        Self {
64            signature: Signature::one_of(
65                vec![Exact(vec![Int64, Utf8]), Exact(vec![Int64])],
66                Volatility::Immutable,
67            ),
68        }
69    }
70}
71
72impl ScalarUDFImpl for FromUnixtimeFunc {
73    fn as_any(&self) -> &dyn Any {
74        self
75    }
76
77    fn name(&self) -> &str {
78        "from_unixtime"
79    }
80
81    fn signature(&self) -> &Signature {
82        &self.signature
83    }
84
85    fn return_type_from_args(&self, args: ReturnTypeArgs) -> Result<ReturnInfo> {
86        // Length check handled in the signature
87        debug_assert!(matches!(args.scalar_arguments.len(), 1 | 2));
88
89        if args.scalar_arguments.len() == 1 {
90            Ok(ReturnInfo::new_nullable(Timestamp(Second, None)))
91        } else {
92            args.scalar_arguments[1]
93                .and_then(|sv| {
94                    sv.try_as_str()
95                        .flatten()
96                        .filter(|s| !s.is_empty())
97                        .map(|tz| {
98                            ReturnInfo::new_nullable(Timestamp(
99                                Second,
100                                Some(Arc::from(tz.to_string())),
101                            ))
102                        })
103                })
104                .map_or_else(
105                    || {
106                        exec_err!(
107                            "{} requires its second argument to be a constant string",
108                            self.name()
109                        )
110                    },
111                    Ok,
112                )
113        }
114    }
115
116    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
117        internal_err!("call return_type_from_args instead")
118    }
119
120    fn invoke_with_args(
121        &self,
122        args: datafusion_expr::ScalarFunctionArgs,
123    ) -> Result<ColumnarValue> {
124        let args = args.args;
125        let len = args.len();
126        if len != 1 && len != 2 {
127            return exec_err!(
128                "from_unixtime function requires 1 or 2 argument, got {}",
129                args.len()
130            );
131        }
132
133        if args[0].data_type() != Int64 {
134            return exec_err!(
135                "Unsupported data type {:?} for function from_unixtime",
136                args[0].data_type()
137            );
138        }
139
140        match len {
141            1 => args[0].cast_to(&Timestamp(Second, None), None),
142            2 => match &args[1] {
143                ColumnarValue::Scalar(ScalarValue::Utf8(Some(tz))) => args[0]
144                    .cast_to(&Timestamp(Second, Some(Arc::from(tz.to_string()))), None),
145                _ => {
146                    exec_err!(
147                        "Unsupported data type {:?} for function from_unixtime",
148                        args[1].data_type()
149                    )
150                }
151            },
152            _ => unreachable!(),
153        }
154    }
155
156    fn documentation(&self) -> Option<&Documentation> {
157        self.doc()
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use crate::datetime::from_unixtime::FromUnixtimeFunc;
164    use arrow::datatypes::DataType;
165    use arrow::datatypes::TimeUnit::Second;
166    use datafusion_common::ScalarValue;
167    use datafusion_common::ScalarValue::Int64;
168    use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
169    use std::sync::Arc;
170
171    #[test]
172    fn test_without_timezone() {
173        let args = datafusion_expr::ScalarFunctionArgs {
174            args: vec![ColumnarValue::Scalar(Int64(Some(1729900800)))],
175            number_rows: 1,
176            return_type: &DataType::Timestamp(Second, None),
177        };
178        let result = FromUnixtimeFunc::new().invoke_with_args(args).unwrap();
179
180        match result {
181            ColumnarValue::Scalar(ScalarValue::TimestampSecond(Some(sec), None)) => {
182                assert_eq!(sec, 1729900800);
183            }
184            _ => panic!("Expected scalar value"),
185        }
186    }
187
188    #[test]
189    fn test_with_timezone() {
190        let args = datafusion_expr::ScalarFunctionArgs {
191            args: vec![
192                ColumnarValue::Scalar(Int64(Some(1729900800))),
193                ColumnarValue::Scalar(ScalarValue::Utf8(Some(
194                    "America/New_York".to_string(),
195                ))),
196            ],
197            number_rows: 2,
198            return_type: &DataType::Timestamp(
199                Second,
200                Some(Arc::from("America/New_York")),
201            ),
202        };
203        let result = FromUnixtimeFunc::new().invoke_with_args(args).unwrap();
204
205        match result {
206            ColumnarValue::Scalar(ScalarValue::TimestampSecond(Some(sec), Some(tz))) => {
207                assert_eq!(sec, 1729900800);
208                assert_eq!(tz.to_string(), "America/New_York");
209            }
210            _ => panic!("Expected scalar value"),
211        }
212    }
213}