Skip to main content

datafusion_functions/datetime/
to_timestamp.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 crate::datetime::common::*;
21use arrow::array::timezone::Tz;
22use arrow::array::{
23    Array, Decimal128Array, Float16Array, Float32Array, Float64Array,
24    TimestampNanosecondArray,
25};
26use arrow::datatypes::DataType::*;
27use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
28use arrow::datatypes::{
29    ArrowTimestampType, DECIMAL128_MAX_PRECISION, DataType, TimestampMicrosecondType,
30    TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType,
31};
32use datafusion_common::config::ConfigOptions;
33use datafusion_common::{Result, ScalarType, ScalarValue, exec_datafusion_err, exec_err};
34use datafusion_expr::{
35    ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl,
36    Signature, Volatility,
37};
38use datafusion_macros::user_doc;
39
40#[user_doc(
41    doc_section(label = "Time and Date Functions"),
42    description = r#"
43Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000000<TZ>`) in the session time zone. Supports strings,
44integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
45if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided. 
46Strings that parse without a time zone are treated as if they are in the
47session time zone, or UTC if no session time zone is set.
48Integers, unsigned integers, and doubles are interpreted as seconds since the unix epoch (`1970-01-01T00:00:00Z`).
49
50Note: `to_timestamp` returns `Timestamp(ns, TimeZone)` where the time zone is the session time zone. The supported range
51for integer input is between`-9223372037` and `9223372036`. Supported range for string input is between
52`1677-09-21T00:12:44.0` and `2262-04-11T23:47:16.0`. Please use `to_timestamp_seconds`
53for the input outside of supported bounds.
54
55The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
56The time zone can be a value like +00:00, 'Europe/London' etc.
57"#,
58    syntax_example = "to_timestamp(expression[, ..., format_n])",
59    sql_example = r#"```sql
60> select to_timestamp('2023-01-31T09:26:56.123456789-05:00');
61+-----------------------------------------------------------+
62| to_timestamp(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
63+-----------------------------------------------------------+
64| 2023-01-31T14:26:56.123456789                             |
65+-----------------------------------------------------------+
66> select to_timestamp('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
67+--------------------------------------------------------------------------------------------------------+
68| to_timestamp(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
69+--------------------------------------------------------------------------------------------------------+
70| 2023-05-17T03:59:00.123456789                                                                          |
71+--------------------------------------------------------------------------------------------------------+
72```
73Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
74"#,
75    argument(
76        name = "expression",
77        description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
78    ),
79    argument(
80        name = "format_n",
81        description = r#"
82Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
83Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
84parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
85Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
86only supported at the end of the string preceded by a space.
87"#
88    )
89)]
90#[derive(Debug, PartialEq, Eq, Hash)]
91pub struct ToTimestampFunc {
92    signature: Signature,
93    timezone: Option<Arc<str>>,
94}
95
96#[user_doc(
97    doc_section(label = "Time and Date Functions"),
98    description = r#"
99Converts a value to a timestamp (`YYYY-MM-DDT00:00:00<TZ>`) in the session time zone. Supports strings,
100integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
101if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided. 
102Strings that parse without a time zone are treated as if they are in the
103session time zone, or UTC if no session time zone is set.
104Integers, unsigned integers, and doubles are interpreted as seconds since the unix epoch (`1970-01-01T00:00:00Z`).
105
106The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
107The time zone can be a value like +00:00, 'Europe/London' etc.
108"#,
109    syntax_example = "to_timestamp_seconds(expression[, ..., format_n])",
110    sql_example = r#"```sql
111> select to_timestamp_seconds('2023-01-31T09:26:56.123456789-05:00');
112+-------------------------------------------------------------------+
113| to_timestamp_seconds(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
114+-------------------------------------------------------------------+
115| 2023-01-31T14:26:56                                               |
116+-------------------------------------------------------------------+
117> select to_timestamp_seconds('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
118+----------------------------------------------------------------------------------------------------------------+
119| to_timestamp_seconds(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
120+----------------------------------------------------------------------------------------------------------------+
121| 2023-05-17T03:59:00                                                                                            |
122+----------------------------------------------------------------------------------------------------------------+
123```
124Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
125"#,
126    argument(
127        name = "expression",
128        description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
129    ),
130    argument(
131        name = "format_n",
132        description = r#"
133Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
134Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
135parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
136Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
137only supported at the end of the string preceded by a space.
138"#
139    )
140)]
141#[derive(Debug, PartialEq, Eq, Hash)]
142pub struct ToTimestampSecondsFunc {
143    signature: Signature,
144    timezone: Option<Arc<str>>,
145}
146
147#[user_doc(
148    doc_section(label = "Time and Date Functions"),
149    description = r#"
150Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000<TZ>`) in the session time zone. Supports strings,
151integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
152if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided. 
153Strings that parse without a time zone are treated as if they are in the
154session time zone, or UTC if no session time zone is set.
155Integers, unsigned integers, and doubles are interpreted as milliseconds since the unix epoch (`1970-01-01T00:00:00Z`).
156
157The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
158The time zone can be a value like +00:00, 'Europe/London' etc.
159"#,
160    syntax_example = "to_timestamp_millis(expression[, ..., format_n])",
161    sql_example = r#"```sql
162> select to_timestamp_millis('2023-01-31T09:26:56.123456789-05:00');
163+------------------------------------------------------------------+
164| to_timestamp_millis(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
165+------------------------------------------------------------------+
166| 2023-01-31T14:26:56.123                                          |
167+------------------------------------------------------------------+
168> select to_timestamp_millis('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
169+---------------------------------------------------------------------------------------------------------------+
170| to_timestamp_millis(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
171+---------------------------------------------------------------------------------------------------------------+
172| 2023-05-17T03:59:00.123                                                                                       |
173+---------------------------------------------------------------------------------------------------------------+
174```
175Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
176"#,
177    argument(
178        name = "expression",
179        description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
180    ),
181    argument(
182        name = "format_n",
183        description = r#"
184Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
185Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
186parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
187Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
188only supported at the end of the string preceded by a space.
189"#
190    )
191)]
192#[derive(Debug, PartialEq, Eq, Hash)]
193pub struct ToTimestampMillisFunc {
194    signature: Signature,
195    timezone: Option<Arc<str>>,
196}
197
198#[user_doc(
199    doc_section(label = "Time and Date Functions"),
200    description = r#"
201Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000000<TZ>`) in the session time zone. Supports strings,
202integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
203if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided. 
204Strings that parse without a time zone are treated as if they are in the
205session time zone, or UTC if no session time zone is set.
206Integers, unsigned integers, and doubles are interpreted as microseconds since the unix epoch (`1970-01-01T00:00:00Z`).
207
208The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
209The time zone can be a value like +00:00, 'Europe/London' etc.
210"#,
211    syntax_example = "to_timestamp_micros(expression[, ..., format_n])",
212    sql_example = r#"```sql
213> select to_timestamp_micros('2023-01-31T09:26:56.123456789-05:00');
214+------------------------------------------------------------------+
215| to_timestamp_micros(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
216+------------------------------------------------------------------+
217| 2023-01-31T14:26:56.123456                                       |
218+------------------------------------------------------------------+
219> select to_timestamp_micros('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
220+---------------------------------------------------------------------------------------------------------------+
221| to_timestamp_micros(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
222+---------------------------------------------------------------------------------------------------------------+
223| 2023-05-17T03:59:00.123456                                                                                    |
224+---------------------------------------------------------------------------------------------------------------+
225```
226Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
227"#,
228    argument(
229        name = "expression",
230        description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
231    ),
232    argument(
233        name = "format_n",
234        description = r#"
235Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
236Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
237parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
238Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
239only supported at the end of the string preceded by a space.
240"#
241    )
242)]
243#[derive(Debug, PartialEq, Eq, Hash)]
244pub struct ToTimestampMicrosFunc {
245    signature: Signature,
246    timezone: Option<Arc<str>>,
247}
248
249#[user_doc(
250    doc_section(label = "Time and Date Functions"),
251    description = r#"
252Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000000000<TZ>`) in the session time zone. Supports strings,
253integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
254if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided. 
255Strings that parse without a time zone are treated as if they are in the
256session time zone. Integers, unsigned integers, and doubles are interpreted as nanoseconds since the unix epoch (`1970-01-01T00:00:00Z`).
257
258The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
259The time zone can be a value like +00:00, 'Europe/London' etc.
260"#,
261    syntax_example = "to_timestamp_nanos(expression[, ..., format_n])",
262    sql_example = r#"```sql
263> select to_timestamp_nanos('2023-01-31T09:26:56.123456789-05:00');
264+-----------------------------------------------------------------+
265| to_timestamp_nanos(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
266+-----------------------------------------------------------------+
267| 2023-01-31T14:26:56.123456789                                   |
268+-----------------------------------------------------------------+
269> select to_timestamp_nanos('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
270+--------------------------------------------------------------------------------------------------------------+
271| to_timestamp_nanos(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
272+--------------------------------------------------------------------------------------------------------------+
273| 2023-05-17T03:59:00.123456789                                                                                |
274+---------------------------------------------------------------------------------------------------------------+
275```
276Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
277"#,
278    argument(
279        name = "expression",
280        description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
281    ),
282    argument(
283        name = "format_n",
284        description = r#"
285Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
286Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
287parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
288Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
289only supported at the end of the string preceded by a space.
290"#
291    )
292)]
293#[derive(Debug, PartialEq, Eq, Hash)]
294pub struct ToTimestampNanosFunc {
295    signature: Signature,
296    timezone: Option<Arc<str>>,
297}
298
299/// Macro to generate boilerplate constructors and config methods for ToTimestamp* functions.
300/// Generates: Default impl, deprecated new(), new_with_config(), and extracts timezone from ConfigOptions.
301macro_rules! impl_to_timestamp_constructors {
302    ($func:ty) => {
303        impl Default for $func {
304            fn default() -> Self {
305                Self::new_with_config(&ConfigOptions::default())
306            }
307        }
308
309        impl $func {
310            #[deprecated(since = "52.0.0", note = "use `new_with_config` instead")]
311            /// Deprecated constructor retained for backwards compatibility.
312            ///
313            /// Prefer `new_with_config` which allows specifying the
314            /// timezone via [`ConfigOptions`]. This helper now mirrors the
315            /// canonical default offset (None) provided by `ConfigOptions::default()`.
316            pub fn new() -> Self {
317                Self::new_with_config(&ConfigOptions::default())
318            }
319
320            pub fn new_with_config(config: &ConfigOptions) -> Self {
321                Self {
322                    signature: Signature::variadic_any(Volatility::Immutable),
323                    timezone: config
324                        .execution
325                        .time_zone
326                        .as_ref()
327                        .map(|tz| Arc::from(tz.as_str())),
328                }
329            }
330        }
331    };
332}
333
334impl_to_timestamp_constructors!(ToTimestampFunc);
335impl_to_timestamp_constructors!(ToTimestampSecondsFunc);
336impl_to_timestamp_constructors!(ToTimestampMillisFunc);
337impl_to_timestamp_constructors!(ToTimestampMicrosFunc);
338impl_to_timestamp_constructors!(ToTimestampNanosFunc);
339
340fn decimal_to_nanoseconds(value: i128, scale: i8) -> Result<i64> {
341    let nanos_exponent = 9_i16 - scale as i16;
342    let power = 10_i128
343        .checked_pow(nanos_exponent.unsigned_abs() as u32)
344        .ok_or_else(|| {
345            exec_datafusion_err!(
346                "Decimal value {value} with scale {scale} overflows timestamp nanoseconds"
347            )
348        })?;
349
350    let timestamp_nanos = if nanos_exponent >= 0 {
351        value.checked_mul(power).ok_or_else(|| {
352            exec_datafusion_err!(
353                "Decimal value {value} with scale {scale} overflows timestamp nanoseconds"
354            )
355        })?
356    } else {
357        value / power
358    };
359
360    i64::try_from(timestamp_nanos).map_err(|_| {
361        exec_datafusion_err!(
362            "Decimal value {value} with scale {scale} overflows timestamp nanoseconds"
363        )
364    })
365}
366
367fn decimal128_to_timestamp_nanos(
368    arg: &ColumnarValue,
369    tz: Option<Arc<str>>,
370) -> Result<ColumnarValue> {
371    match arg {
372        ColumnarValue::Scalar(ScalarValue::Decimal128(Some(value), _, scale)) => {
373            let timestamp_nanos = decimal_to_nanoseconds(*value, *scale)?;
374            Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
375                Some(timestamp_nanos),
376                tz,
377            )))
378        }
379        ColumnarValue::Scalar(ScalarValue::Decimal128(None, _, _)) => Ok(
380            ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(None, tz)),
381        ),
382        ColumnarValue::Array(arr) => {
383            let decimal_arr = downcast_arg!(arr, Decimal128Array);
384            let scale = decimal_arr.scale();
385            let result: TimestampNanosecondArray = decimal_arr
386                .iter()
387                .map(|v| v.map(|val| decimal_to_nanoseconds(val, scale)).transpose())
388                .collect::<Result<_>>()?;
389            let result = result.with_timezone_opt(tz);
390            Ok(ColumnarValue::Array(Arc::new(result)))
391        }
392        _ => exec_err!("Invalid Decimal128 value for to_timestamp"),
393    }
394}
395
396/// to_timestamp SQL function
397///
398/// Note: `to_timestamp` returns `Timestamp(Nanosecond)` though its arguments are interpreted as **seconds**.
399/// The supported range for integer input is between `-9223372037` and `9223372036`.
400/// Supported range for string input is between `1677-09-21T00:12:44.0` and `2262-04-11T23:47:16.0`.
401/// Please use `to_timestamp_seconds` for the input outside of supported bounds.
402/// Macro to generate the with_updated_config method for ToTimestamp* functions.
403macro_rules! impl_with_updated_config {
404    () => {
405        fn with_updated_config(&self, config: &ConfigOptions) -> Option<ScalarUDF> {
406            Some(Self::new_with_config(config).into())
407        }
408    };
409}
410
411impl ScalarUDFImpl for ToTimestampFunc {
412    fn name(&self) -> &str {
413        "to_timestamp"
414    }
415
416    fn signature(&self) -> &Signature {
417        &self.signature
418    }
419
420    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
421        Ok(Timestamp(Nanosecond, self.timezone.clone()))
422    }
423
424    impl_with_updated_config!();
425
426    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
427        let ScalarFunctionArgs { args, .. } = args;
428
429        if args.is_empty() {
430            return exec_err!(
431                "to_timestamp function requires 1 or more arguments, got {}",
432                args.len()
433            );
434        }
435
436        // validate that any args after the first one are Utf8
437        if args.len() > 1 {
438            validate_data_types(&args, "to_timestamp")?;
439        }
440
441        let tz = self.timezone.clone();
442
443        match args[0].data_type() {
444            Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64 => args[0]
445                .cast_to(&Timestamp(Second, None), None)?
446                .cast_to(&Timestamp(Nanosecond, tz), None),
447            Null | Timestamp(_, _) => args[0].cast_to(&Timestamp(Nanosecond, tz), None),
448            Float16 => match &args[0] {
449                ColumnarValue::Scalar(ScalarValue::Float16(value)) => {
450                    let timestamp_nanos =
451                        value.map(|v| (v.to_f64() * 1_000_000_000.0) as i64);
452                    Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
453                        timestamp_nanos,
454                        tz,
455                    )))
456                }
457                ColumnarValue::Array(arr) => {
458                    let f16_arr = downcast_arg!(arr, Float16Array);
459                    let result: TimestampNanosecondArray =
460                        f16_arr.unary(|x| (x.to_f64() * 1_000_000_000.0) as i64);
461                    Ok(ColumnarValue::Array(Arc::new(result.with_timezone_opt(tz))))
462                }
463                _ => exec_err!("Invalid Float16 value for to_timestamp"),
464            },
465            Float32 => match &args[0] {
466                ColumnarValue::Scalar(ScalarValue::Float32(value)) => {
467                    let timestamp_nanos =
468                        value.map(|v| (v as f64 * 1_000_000_000.0) as i64);
469                    Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
470                        timestamp_nanos,
471                        tz,
472                    )))
473                }
474                ColumnarValue::Array(arr) => {
475                    let f32_arr = downcast_arg!(arr, Float32Array);
476                    let result: TimestampNanosecondArray =
477                        f32_arr.unary(|x| (x as f64 * 1_000_000_000.0) as i64);
478                    Ok(ColumnarValue::Array(Arc::new(result.with_timezone_opt(tz))))
479                }
480                _ => exec_err!("Invalid Float32 value for to_timestamp"),
481            },
482            Float64 => match &args[0] {
483                ColumnarValue::Scalar(ScalarValue::Float64(value)) => {
484                    let timestamp_nanos = value.map(|v| (v * 1_000_000_000.0) as i64);
485                    Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
486                        timestamp_nanos,
487                        tz,
488                    )))
489                }
490                ColumnarValue::Array(arr) => {
491                    let f64_arr = downcast_arg!(arr, Float64Array);
492                    let result: TimestampNanosecondArray =
493                        f64_arr.unary(|x| (x * 1_000_000_000.0) as i64);
494                    Ok(ColumnarValue::Array(Arc::new(result.with_timezone_opt(tz))))
495                }
496                _ => exec_err!("Invalid Float64 value for to_timestamp"),
497            },
498            Decimal32(_, _) | Decimal64(_, _) | Decimal256(_, _) => {
499                let arg =
500                    args[0].cast_to(&Decimal128(DECIMAL128_MAX_PRECISION, 9), None)?;
501                decimal128_to_timestamp_nanos(&arg, tz)
502            }
503            Decimal128(_, _) => decimal128_to_timestamp_nanos(&args[0], tz),
504            Utf8View | LargeUtf8 | Utf8 => {
505                to_timestamp_impl::<TimestampNanosecondType>(&args, "to_timestamp", &tz)
506            }
507            other => {
508                exec_err!("Unsupported data type {other} for function to_timestamp")
509            }
510        }
511    }
512
513    fn documentation(&self) -> Option<&Documentation> {
514        self.doc()
515    }
516}
517
518impl ScalarUDFImpl for ToTimestampSecondsFunc {
519    fn name(&self) -> &str {
520        "to_timestamp_seconds"
521    }
522
523    fn signature(&self) -> &Signature {
524        &self.signature
525    }
526
527    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
528        Ok(Timestamp(Second, self.timezone.clone()))
529    }
530
531    impl_with_updated_config!();
532
533    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
534        let ScalarFunctionArgs { args, .. } = args;
535
536        if args.is_empty() {
537            return exec_err!(
538                "to_timestamp_seconds function requires 1 or more arguments, got {}",
539                args.len()
540            );
541        }
542
543        // validate that any args after the first one are Utf8
544        if args.len() > 1 {
545            validate_data_types(&args, "to_timestamp")?;
546        }
547
548        let tz = self.timezone.clone();
549
550        match args[0].data_type() {
551            Null
552            | Int8
553            | Int16
554            | Int32
555            | Int64
556            | UInt8
557            | UInt16
558            | UInt32
559            | UInt64
560            | Timestamp(_, _)
561            | Decimal32(_, _)
562            | Decimal64(_, _)
563            | Decimal128(_, _)
564            | Decimal256(_, _) => args[0].cast_to(&Timestamp(Second, tz), None),
565            Float16 | Float32 | Float64 => args[0]
566                .cast_to(&Int64, None)?
567                .cast_to(&Timestamp(Second, tz), None),
568            Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampSecondType>(
569                &args,
570                "to_timestamp_seconds",
571                &self.timezone,
572            ),
573            other => {
574                exec_err!(
575                    "Unsupported data type {} for function to_timestamp_seconds",
576                    other
577                )
578            }
579        }
580    }
581
582    fn documentation(&self) -> Option<&Documentation> {
583        self.doc()
584    }
585}
586
587impl ScalarUDFImpl for ToTimestampMillisFunc {
588    fn name(&self) -> &str {
589        "to_timestamp_millis"
590    }
591
592    fn signature(&self) -> &Signature {
593        &self.signature
594    }
595
596    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
597        Ok(Timestamp(Millisecond, self.timezone.clone()))
598    }
599
600    impl_with_updated_config!();
601
602    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
603        let ScalarFunctionArgs { args, .. } = args;
604
605        if args.is_empty() {
606            return exec_err!(
607                "to_timestamp_millis function requires 1 or more arguments, got {}",
608                args.len()
609            );
610        }
611
612        // validate that any args after the first one are Utf8
613        if args.len() > 1 {
614            validate_data_types(&args, "to_timestamp")?;
615        }
616
617        match args[0].data_type() {
618            Null
619            | Int8
620            | Int16
621            | Int32
622            | Int64
623            | UInt8
624            | UInt16
625            | UInt32
626            | UInt64
627            | Timestamp(_, _)
628            | Decimal32(_, _)
629            | Decimal64(_, _)
630            | Decimal128(_, _)
631            | Decimal256(_, _) => {
632                args[0].cast_to(&Timestamp(Millisecond, self.timezone.clone()), None)
633            }
634            Float16 | Float32 | Float64 => args[0]
635                .cast_to(&Int64, None)?
636                .cast_to(&Timestamp(Millisecond, self.timezone.clone()), None),
637            Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampMillisecondType>(
638                &args,
639                "to_timestamp_millis",
640                &self.timezone,
641            ),
642            other => {
643                exec_err!(
644                    "Unsupported data type {} for function to_timestamp_millis",
645                    other
646                )
647            }
648        }
649    }
650
651    fn documentation(&self) -> Option<&Documentation> {
652        self.doc()
653    }
654}
655
656impl ScalarUDFImpl for ToTimestampMicrosFunc {
657    fn name(&self) -> &str {
658        "to_timestamp_micros"
659    }
660
661    fn signature(&self) -> &Signature {
662        &self.signature
663    }
664
665    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
666        Ok(Timestamp(Microsecond, self.timezone.clone()))
667    }
668
669    impl_with_updated_config!();
670
671    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
672        let ScalarFunctionArgs { args, .. } = args;
673
674        if args.is_empty() {
675            return exec_err!(
676                "to_timestamp_micros function requires 1 or more arguments, got {}",
677                args.len()
678            );
679        }
680
681        // validate that any args after the first one are Utf8
682        if args.len() > 1 {
683            validate_data_types(&args, "to_timestamp")?;
684        }
685
686        match args[0].data_type() {
687            Null
688            | Int8
689            | Int16
690            | Int32
691            | Int64
692            | UInt8
693            | UInt16
694            | UInt32
695            | UInt64
696            | Timestamp(_, _)
697            | Decimal32(_, _)
698            | Decimal64(_, _)
699            | Decimal128(_, _)
700            | Decimal256(_, _) => {
701                args[0].cast_to(&Timestamp(Microsecond, self.timezone.clone()), None)
702            }
703            Float16 | Float32 | Float64 => args[0]
704                .cast_to(&Int64, None)?
705                .cast_to(&Timestamp(Microsecond, self.timezone.clone()), None),
706            Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampMicrosecondType>(
707                &args,
708                "to_timestamp_micros",
709                &self.timezone,
710            ),
711            other => {
712                exec_err!(
713                    "Unsupported data type {} for function to_timestamp_micros",
714                    other
715                )
716            }
717        }
718    }
719
720    fn documentation(&self) -> Option<&Documentation> {
721        self.doc()
722    }
723}
724
725impl ScalarUDFImpl for ToTimestampNanosFunc {
726    fn name(&self) -> &str {
727        "to_timestamp_nanos"
728    }
729
730    fn signature(&self) -> &Signature {
731        &self.signature
732    }
733
734    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
735        Ok(Timestamp(Nanosecond, self.timezone.clone()))
736    }
737
738    impl_with_updated_config!();
739
740    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
741        let ScalarFunctionArgs { args, .. } = args;
742
743        if args.is_empty() {
744            return exec_err!(
745                "to_timestamp_nanos function requires 1 or more arguments, got {}",
746                args.len()
747            );
748        }
749
750        // validate that any args after the first one are Utf8
751        if args.len() > 1 {
752            validate_data_types(&args, "to_timestamp")?;
753        }
754
755        match args[0].data_type() {
756            Null
757            | Int8
758            | Int16
759            | Int32
760            | Int64
761            | UInt8
762            | UInt16
763            | UInt32
764            | UInt64
765            | Timestamp(_, _)
766            | Decimal32(_, _)
767            | Decimal64(_, _)
768            | Decimal128(_, _)
769            | Decimal256(_, _) => {
770                args[0].cast_to(&Timestamp(Nanosecond, self.timezone.clone()), None)
771            }
772            Float16 | Float32 | Float64 => args[0]
773                .cast_to(&Int64, None)?
774                .cast_to(&Timestamp(Nanosecond, self.timezone.clone()), None),
775            Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampNanosecondType>(
776                &args,
777                "to_timestamp_nanos",
778                &self.timezone,
779            ),
780            other => {
781                exec_err!(
782                    "Unsupported data type {} for function to_timestamp_nanos",
783                    other
784                )
785            }
786        }
787    }
788
789    fn documentation(&self) -> Option<&Documentation> {
790        self.doc()
791    }
792}
793
794fn to_timestamp_impl<T: ArrowTimestampType + ScalarType<i64>>(
795    args: &[ColumnarValue],
796    name: &str,
797    timezone: &Option<Arc<str>>,
798) -> Result<ColumnarValue> {
799    let factor = match T::UNIT {
800        Second => 1_000_000_000,
801        Millisecond => 1_000_000,
802        Microsecond => 1_000,
803        Nanosecond => 1,
804    };
805
806    let tz = match timezone.clone() {
807        Some(tz) => Some(tz.parse::<Tz>()?),
808        None => None,
809    };
810
811    match args.len() {
812        1 => handle::<T, _>(
813            args,
814            move |s| string_to_timestamp_nanos_with_timezone(&tz, s).map(|n| n / factor),
815            name,
816            &Timestamp(T::UNIT, timezone.clone()),
817        ),
818        n if n >= 2 => handle_multiple::<T, _, _>(
819            args,
820            move |s, format| {
821                string_to_timestamp_nanos_formatted_with_timezone(&tz, s, format)
822            },
823            |n| n / factor,
824            name,
825            &Timestamp(T::UNIT, timezone.clone()),
826        ),
827        _ => exec_err!("Unsupported 0 argument count for function {name}"),
828    }
829}
830
831#[cfg(test)]
832mod tests {
833
834    use arrow::array::types::Int64Type;
835    use arrow::array::{
836        Array, PrimitiveArray, TimestampMicrosecondArray, TimestampMillisecondArray,
837        TimestampNanosecondArray, TimestampSecondArray,
838    };
839    use arrow::array::{ArrayRef, Int64Array, StringBuilder};
840    use arrow::datatypes::{Field, TimeUnit};
841    use chrono::{DateTime, FixedOffset, Utc};
842    use datafusion_common::{DataFusionError, assert_contains};
843    use datafusion_expr::ScalarFunctionImplementation;
844
845    use super::*;
846
847    fn to_timestamp(args: &[ColumnarValue]) -> Result<ColumnarValue> {
848        let timezone: Option<Arc<str>> = Some("UTC".into());
849        to_timestamp_impl::<TimestampNanosecondType>(args, "to_timestamp", &timezone)
850    }
851
852    /// to_timestamp_millis SQL function
853    fn to_timestamp_millis(args: &[ColumnarValue]) -> Result<ColumnarValue> {
854        let timezone: Option<Arc<str>> = Some("UTC".into());
855        to_timestamp_impl::<TimestampMillisecondType>(
856            args,
857            "to_timestamp_millis",
858            &timezone,
859        )
860    }
861
862    /// to_timestamp_micros SQL function
863    fn to_timestamp_micros(args: &[ColumnarValue]) -> Result<ColumnarValue> {
864        let timezone: Option<Arc<str>> = Some("UTC".into());
865        to_timestamp_impl::<TimestampMicrosecondType>(
866            args,
867            "to_timestamp_micros",
868            &timezone,
869        )
870    }
871
872    /// to_timestamp_nanos SQL function
873    fn to_timestamp_nanos(args: &[ColumnarValue]) -> Result<ColumnarValue> {
874        let timezone: Option<Arc<str>> = Some("UTC".into());
875        to_timestamp_impl::<TimestampNanosecondType>(
876            args,
877            "to_timestamp_nanos",
878            &timezone,
879        )
880    }
881
882    /// to_timestamp_seconds SQL function
883    fn to_timestamp_seconds(args: &[ColumnarValue]) -> Result<ColumnarValue> {
884        let timezone: Option<Arc<str>> = Some("UTC".into());
885        to_timestamp_impl::<TimestampSecondType>(args, "to_timestamp_seconds", &timezone)
886    }
887
888    fn udfs_and_timeunit() -> Vec<(Box<dyn ScalarUDFImpl>, TimeUnit)> {
889        let udfs: Vec<(Box<dyn ScalarUDFImpl>, TimeUnit)> = vec![
890            (
891                Box::new(ToTimestampFunc::new_with_config(&ConfigOptions::default())),
892                Nanosecond,
893            ),
894            (
895                Box::new(ToTimestampSecondsFunc::new_with_config(
896                    &ConfigOptions::default(),
897                )),
898                Second,
899            ),
900            (
901                Box::new(ToTimestampMillisFunc::new_with_config(
902                    &ConfigOptions::default(),
903                )),
904                Millisecond,
905            ),
906            (
907                Box::new(ToTimestampMicrosFunc::new_with_config(
908                    &ConfigOptions::default(),
909                )),
910                Microsecond,
911            ),
912            (
913                Box::new(ToTimestampNanosFunc::new_with_config(
914                    &ConfigOptions::default(),
915                )),
916                Nanosecond,
917            ),
918        ];
919        udfs
920    }
921
922    fn validate_expected_error(
923        options: &mut ConfigOptions,
924        args: ScalarFunctionArgs,
925        expected_err: &str,
926    ) {
927        let udfs = udfs_and_timeunit();
928
929        for (udf, _) in udfs {
930            match udf
931                .with_updated_config(options)
932                .unwrap()
933                .invoke_with_args(args.clone())
934            {
935                Ok(_) => panic!("Expected error but got success"),
936                Err(e) => {
937                    assert!(
938                        e.to_string().contains(expected_err),
939                        "Can not find expected error '{expected_err}'. Actual error '{e}'"
940                    );
941                }
942            }
943        }
944    }
945
946    #[test]
947    fn to_timestamp_arrays_and_nulls() -> Result<()> {
948        // ensure that arrow array implementation is wired up and handles nulls correctly
949
950        let mut string_builder = StringBuilder::with_capacity(2, 1024);
951        let mut ts_builder = TimestampNanosecondArray::builder(2);
952
953        string_builder.append_value("2020-09-08T13:42:29.190855");
954        ts_builder.append_value(1599572549190855000);
955
956        string_builder.append_null();
957        ts_builder.append_null();
958        let expected_timestamps = &ts_builder.finish() as &dyn Array;
959
960        let string_array =
961            ColumnarValue::Array(Arc::new(string_builder.finish()) as ArrayRef);
962        let parsed_timestamps = to_timestamp(&[string_array])
963            .expect("that to_timestamp parsed values without error");
964        if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
965            assert_eq!(parsed_array.len(), 2);
966            assert_eq!(expected_timestamps, parsed_array.as_ref());
967        } else {
968            panic!("Expected a columnar array")
969        }
970        Ok(())
971    }
972
973    #[test]
974    fn to_timestamp_decimal128_overflow_returns_error() {
975        let value = "99999999999999999999999999999999999999"
976            .parse::<i128>()
977            .unwrap();
978        let err = decimal128_to_timestamp_nanos(
979            &ColumnarValue::Scalar(ScalarValue::Decimal128(Some(value), 38, 0)),
980            None,
981        )
982        .unwrap_err()
983        .to_string();
984
985        assert_contains!(err, "overflows timestamp nanoseconds");
986    }
987
988    #[test]
989    fn to_timestamp_decimal128_array_overflow_returns_error() {
990        let value = "99999999999999999999999999999999999999"
991            .parse::<i128>()
992            .unwrap();
993        let array = Decimal128Array::from(vec![Some(value)])
994            .with_precision_and_scale(38, 0)
995            .unwrap();
996        let err =
997            decimal128_to_timestamp_nanos(&ColumnarValue::Array(Arc::new(array)), None)
998                .unwrap_err()
999                .to_string();
1000
1001        assert_contains!(err, "overflows timestamp nanoseconds");
1002    }
1003
1004    #[test]
1005    fn to_timestamp_with_formats_arrays_and_nulls() -> Result<()> {
1006        // ensure that arrow array implementation is wired up and handles nulls correctly
1007
1008        let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1009        let mut format1_builder = StringBuilder::with_capacity(2, 1024);
1010        let mut format2_builder = StringBuilder::with_capacity(2, 1024);
1011        let mut format3_builder = StringBuilder::with_capacity(2, 1024);
1012        let mut ts_builder = TimestampNanosecondArray::builder(2);
1013
1014        date_string_builder.append_null();
1015        format1_builder.append_null();
1016        format2_builder.append_null();
1017        format3_builder.append_null();
1018        ts_builder.append_null();
1019
1020        date_string_builder.append_value("2020-09-08T13:42:29.19085Z");
1021        format1_builder.append_value("%s");
1022        format2_builder.append_value("%c");
1023        format3_builder.append_value("%+");
1024        ts_builder.append_value(1599572549190850000);
1025
1026        let expected_timestamps = &ts_builder.finish() as &dyn Array;
1027
1028        let string_array = [
1029            ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef),
1030            ColumnarValue::Array(Arc::new(format1_builder.finish()) as ArrayRef),
1031            ColumnarValue::Array(Arc::new(format2_builder.finish()) as ArrayRef),
1032            ColumnarValue::Array(Arc::new(format3_builder.finish()) as ArrayRef),
1033        ];
1034        let parsed_timestamps = to_timestamp(&string_array)
1035            .expect("that to_timestamp with format args parsed values without error");
1036        if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
1037            assert_eq!(parsed_array.len(), 2);
1038            assert_eq!(expected_timestamps, parsed_array.as_ref());
1039        } else {
1040            panic!("Expected a columnar array")
1041        }
1042        Ok(())
1043    }
1044
1045    #[test]
1046    fn to_timestamp_respects_execution_timezone() -> Result<()> {
1047        let udfs = udfs_and_timeunit();
1048
1049        let mut options = ConfigOptions::default();
1050        options.execution.time_zone = Some("-05:00".to_string());
1051
1052        let time_zone: Option<Arc<str>> = options
1053            .execution
1054            .time_zone
1055            .as_ref()
1056            .map(|tz| Arc::from(tz.as_str()));
1057
1058        for (udf, time_unit) in udfs {
1059            let field = Field::new("arg", Utf8, true).into();
1060
1061            let args = ScalarFunctionArgs {
1062                args: vec![ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1063                    "2020-09-08T13:42:29".to_string(),
1064                )))],
1065                arg_fields: vec![field],
1066                number_rows: 1,
1067                return_field: Field::new(
1068                    "f",
1069                    Timestamp(time_unit, Some("-05:00".into())),
1070                    true,
1071                )
1072                .into(),
1073                config_options: Arc::new(options.clone()),
1074            };
1075
1076            let result = udf
1077                .with_updated_config(&options.clone())
1078                .unwrap()
1079                .invoke_with_args(args)?;
1080            let result = match time_unit {
1081                Second => {
1082                    let ColumnarValue::Scalar(ScalarValue::TimestampSecond(
1083                        Some(value),
1084                        tz,
1085                    )) = result
1086                    else {
1087                        panic!("expected scalar timestamp");
1088                    };
1089
1090                    assert_eq!(tz, time_zone);
1091
1092                    value
1093                }
1094                Millisecond => {
1095                    let ColumnarValue::Scalar(ScalarValue::TimestampMillisecond(
1096                        Some(value),
1097                        tz,
1098                    )) = result
1099                    else {
1100                        panic!("expected scalar timestamp");
1101                    };
1102
1103                    assert_eq!(tz, time_zone);
1104
1105                    value
1106                }
1107                Microsecond => {
1108                    let ColumnarValue::Scalar(ScalarValue::TimestampMicrosecond(
1109                        Some(value),
1110                        tz,
1111                    )) = result
1112                    else {
1113                        panic!("expected scalar timestamp");
1114                    };
1115
1116                    assert_eq!(tz, time_zone);
1117
1118                    value
1119                }
1120                Nanosecond => {
1121                    let ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
1122                        Some(value),
1123                        tz,
1124                    )) = result
1125                    else {
1126                        panic!("expected scalar timestamp");
1127                    };
1128
1129                    assert_eq!(tz, time_zone);
1130
1131                    value
1132                }
1133            };
1134
1135            let scale = match time_unit {
1136                Second => 1_000_000_000,
1137                Millisecond => 1_000_000,
1138                Microsecond => 1_000,
1139                Nanosecond => 1,
1140            };
1141
1142            let offset = FixedOffset::west_opt(5 * 3600).unwrap();
1143            let result = Some(
1144                DateTime::<Utc>::from_timestamp_nanos(result * scale)
1145                    .with_timezone(&offset)
1146                    .to_string(),
1147            );
1148
1149            assert_eq!(result, Some("2020-09-08 13:42:29 -05:00".to_string()));
1150        }
1151
1152        Ok(())
1153    }
1154
1155    #[test]
1156    fn to_timestamp_formats_respects_execution_timezone() -> Result<()> {
1157        let udfs = udfs_and_timeunit();
1158
1159        let mut options = ConfigOptions::default();
1160        options.execution.time_zone = Some("-05:00".to_string());
1161
1162        let time_zone: Option<Arc<str>> = options
1163            .execution
1164            .time_zone
1165            .as_ref()
1166            .map(|tz| Arc::from(tz.as_str()));
1167
1168        let expr_field = Field::new("arg", Utf8, true).into();
1169        let format_field: Arc<Field> = Field::new("fmt", Utf8, true).into();
1170
1171        for (udf, time_unit) in udfs {
1172            for (value, format, expected_str) in [
1173                (
1174                    "2020-09-08 09:42:29 -05:00",
1175                    "%Y-%m-%d %H:%M:%S %z",
1176                    Some("2020-09-08 09:42:29 -05:00"),
1177                ),
1178                (
1179                    "2020-09-08T13:42:29Z",
1180                    "%+",
1181                    Some("2020-09-08 08:42:29 -05:00"),
1182                ),
1183                (
1184                    "2020-09-08 13:42:29 UTC",
1185                    "%Y-%m-%d %H:%M:%S %Z",
1186                    Some("2020-09-08 08:42:29 -05:00"),
1187                ),
1188                (
1189                    "+0000 2024-01-01 12:00:00",
1190                    "%z %Y-%m-%d %H:%M:%S",
1191                    Some("2024-01-01 07:00:00 -05:00"),
1192                ),
1193                (
1194                    "20200908134229+0100",
1195                    "%Y%m%d%H%M%S%z",
1196                    Some("2020-09-08 07:42:29 -05:00"),
1197                ),
1198                (
1199                    "2020-09-08+0230 13:42",
1200                    "%Y-%m-%d%z %H:%M",
1201                    Some("2020-09-08 06:12:00 -05:00"),
1202                ),
1203            ] {
1204                let args = ScalarFunctionArgs {
1205                    args: vec![
1206                        ColumnarValue::Scalar(ScalarValue::Utf8(Some(value.to_string()))),
1207                        ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1208                            format.to_string(),
1209                        ))),
1210                    ],
1211                    arg_fields: vec![Arc::clone(&expr_field), Arc::clone(&format_field)],
1212                    number_rows: 1,
1213                    return_field: Field::new(
1214                        "f",
1215                        Timestamp(time_unit, Some("-05:00".into())),
1216                        true,
1217                    )
1218                    .into(),
1219                    config_options: Arc::new(options.clone()),
1220                };
1221                let result = udf
1222                    .with_updated_config(&options.clone())
1223                    .unwrap()
1224                    .invoke_with_args(args)?;
1225                let result = match time_unit {
1226                    Second => {
1227                        let ColumnarValue::Scalar(ScalarValue::TimestampSecond(
1228                            Some(value),
1229                            tz,
1230                        )) = result
1231                        else {
1232                            panic!("expected scalar timestamp");
1233                        };
1234
1235                        assert_eq!(tz, time_zone);
1236
1237                        value
1238                    }
1239                    Millisecond => {
1240                        let ColumnarValue::Scalar(ScalarValue::TimestampMillisecond(
1241                            Some(value),
1242                            tz,
1243                        )) = result
1244                        else {
1245                            panic!("expected scalar timestamp");
1246                        };
1247
1248                        assert_eq!(tz, time_zone);
1249
1250                        value
1251                    }
1252                    Microsecond => {
1253                        let ColumnarValue::Scalar(ScalarValue::TimestampMicrosecond(
1254                            Some(value),
1255                            tz,
1256                        )) = result
1257                        else {
1258                            panic!("expected scalar timestamp");
1259                        };
1260
1261                        assert_eq!(tz, time_zone);
1262
1263                        value
1264                    }
1265                    Nanosecond => {
1266                        let ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
1267                            Some(value),
1268                            tz,
1269                        )) = result
1270                        else {
1271                            panic!("expected scalar timestamp");
1272                        };
1273
1274                        assert_eq!(tz, time_zone);
1275
1276                        value
1277                    }
1278                };
1279
1280                let scale = match time_unit {
1281                    Second => 1_000_000_000,
1282                    Millisecond => 1_000_000,
1283                    Microsecond => 1_000,
1284                    Nanosecond => 1,
1285                };
1286                let offset = FixedOffset::west_opt(5 * 3600).unwrap();
1287                let result = Some(
1288                    DateTime::<Utc>::from_timestamp_nanos(result * scale)
1289                        .with_timezone(&offset)
1290                        .to_string(),
1291                );
1292
1293                assert_eq!(result, expected_str.map(|s| s.to_string()));
1294            }
1295        }
1296
1297        Ok(())
1298    }
1299
1300    #[test]
1301    fn to_timestamp_invalid_execution_timezone_behavior() -> Result<()> {
1302        let field: Arc<Field> = Field::new("arg", Utf8, true).into();
1303        let return_field: Arc<Field> =
1304            Field::new("f", Timestamp(Nanosecond, None), true).into();
1305
1306        let mut options = ConfigOptions::default();
1307        options.execution.time_zone = Some("Invalid/Timezone".to_string());
1308
1309        let args = ScalarFunctionArgs {
1310            args: vec![ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1311                "2020-09-08T13:42:29Z".to_string(),
1312            )))],
1313            arg_fields: vec![Arc::clone(&field)],
1314            number_rows: 1,
1315            return_field: Arc::clone(&return_field),
1316            config_options: Arc::new(options.clone()),
1317        };
1318
1319        let expected_err =
1320            "Invalid timezone \"Invalid/Timezone\": failed to parse timezone";
1321
1322        validate_expected_error(&mut options, args, expected_err);
1323
1324        Ok(())
1325    }
1326
1327    #[test]
1328    fn to_timestamp_formats_invalid_execution_timezone_behavior() -> Result<()> {
1329        let expr_field: Arc<Field> = Field::new("arg", Utf8, true).into();
1330        let format_field: Arc<Field> = Field::new("fmt", Utf8, true).into();
1331        let return_field: Arc<Field> =
1332            Field::new("f", Timestamp(Nanosecond, None), true).into();
1333
1334        let mut options = ConfigOptions::default();
1335        options.execution.time_zone = Some("Invalid/Timezone".to_string());
1336
1337        let expected_err =
1338            "Invalid timezone \"Invalid/Timezone\": failed to parse timezone";
1339
1340        let make_args = |value: &str, format: &str| ScalarFunctionArgs {
1341            args: vec![
1342                ColumnarValue::Scalar(ScalarValue::Utf8(Some(value.to_string()))),
1343                ColumnarValue::Scalar(ScalarValue::Utf8(Some(format.to_string()))),
1344            ],
1345            arg_fields: vec![Arc::clone(&expr_field), Arc::clone(&format_field)],
1346            number_rows: 1,
1347            return_field: Arc::clone(&return_field),
1348            config_options: Arc::new(options.clone()),
1349        };
1350
1351        for (value, format, _expected_str) in [
1352            (
1353                "2020-09-08 09:42:29 -05:00",
1354                "%Y-%m-%d %H:%M:%S %z",
1355                Some("2020-09-08 09:42:29 -05:00"),
1356            ),
1357            (
1358                "2020-09-08T13:42:29Z",
1359                "%+",
1360                Some("2020-09-08 08:42:29 -05:00"),
1361            ),
1362            (
1363                "2020-09-08 13:42:29 +0000",
1364                "%Y-%m-%d %H:%M:%S %z",
1365                Some("2020-09-08 08:42:29 -05:00"),
1366            ),
1367            (
1368                "+0000 2024-01-01 12:00:00",
1369                "%z %Y-%m-%d %H:%M:%S",
1370                Some("2024-01-01 07:00:00 -05:00"),
1371            ),
1372            (
1373                "20200908134229+0100",
1374                "%Y%m%d%H%M%S%z",
1375                Some("2020-09-08 07:42:29 -05:00"),
1376            ),
1377            (
1378                "2020-09-08+0230 13:42",
1379                "%Y-%m-%d%z %H:%M",
1380                Some("2020-09-08 06:12:00 -05:00"),
1381            ),
1382        ] {
1383            let args = make_args(value, format);
1384            validate_expected_error(&mut options.clone(), args, expected_err);
1385        }
1386
1387        let args = ScalarFunctionArgs {
1388            args: vec![
1389                ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1390                    "2020-09-08T13:42:29".to_string(),
1391                ))),
1392                ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1393                    "%Y-%m-%dT%H:%M:%S".to_string(),
1394                ))),
1395            ],
1396            arg_fields: vec![Arc::clone(&expr_field), Arc::clone(&format_field)],
1397            number_rows: 1,
1398            return_field: Arc::clone(&return_field),
1399            config_options: Arc::new(options.clone()),
1400        };
1401
1402        validate_expected_error(&mut options.clone(), args, expected_err);
1403
1404        Ok(())
1405    }
1406
1407    #[test]
1408    fn to_timestamp_invalid_input_type() -> Result<()> {
1409        // pass the wrong type of input array to to_timestamp and test
1410        // that we get an error.
1411
1412        let mut builder = Int64Array::builder(1);
1413        builder.append_value(1);
1414        let int64array = ColumnarValue::Array(Arc::new(builder.finish()));
1415
1416        let expected_err =
1417            "Execution error: Unsupported data type Int64 for function to_timestamp";
1418        match to_timestamp(&[int64array]) {
1419            Ok(_) => panic!("Expected error but got success"),
1420            Err(e) => {
1421                assert!(
1422                    e.to_string().contains(expected_err),
1423                    "Can not find expected error '{expected_err}'. Actual error '{e}'"
1424                );
1425            }
1426        }
1427        Ok(())
1428    }
1429
1430    #[test]
1431    fn to_timestamp_with_formats_invalid_input_type() -> Result<()> {
1432        // pass the wrong type of input array to to_timestamp and test
1433        // that we get an error.
1434
1435        let mut builder = Int64Array::builder(1);
1436        builder.append_value(1);
1437        let int64array = [
1438            ColumnarValue::Array(Arc::new(builder.finish())),
1439            ColumnarValue::Array(Arc::new(builder.finish())),
1440        ];
1441
1442        let expected_err =
1443            "Execution error: Unsupported data type Int64 for function to_timestamp";
1444        match to_timestamp(&int64array) {
1445            Ok(_) => panic!("Expected error but got success"),
1446            Err(e) => {
1447                assert!(
1448                    e.to_string().contains(expected_err),
1449                    "Can not find expected error '{expected_err}'. Actual error '{e}'"
1450                );
1451            }
1452        }
1453        Ok(())
1454    }
1455
1456    #[test]
1457    fn to_timestamp_with_unparsable_data() -> Result<()> {
1458        let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1459
1460        date_string_builder.append_null();
1461
1462        date_string_builder.append_value("2020-09-08 - 13:42:29.19085Z");
1463
1464        let string_array =
1465            ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef);
1466
1467        let expected_err = "Arrow error: Parser error: Error parsing timestamp from '2020-09-08 - 13:42:29.19085Z': error parsing time";
1468        match to_timestamp(&[string_array]) {
1469            Ok(_) => panic!("Expected error but got success"),
1470            Err(e) => {
1471                assert!(
1472                    e.to_string().contains(expected_err),
1473                    "Can not find expected error '{expected_err}'. Actual error '{e}'"
1474                );
1475            }
1476        }
1477        Ok(())
1478    }
1479
1480    #[test]
1481    fn to_timestamp_with_invalid_tz() -> Result<()> {
1482        let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1483
1484        date_string_builder.append_null();
1485
1486        date_string_builder.append_value("2020-09-08T13:42:29ZZ");
1487
1488        let string_array =
1489            ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef);
1490
1491        let expected_err = "Arrow error: Parser error: Invalid timezone \"ZZ\": failed to parse timezone";
1492        match to_timestamp(&[string_array]) {
1493            Ok(_) => panic!("Expected error but got success"),
1494            Err(e) => {
1495                assert!(
1496                    e.to_string().contains(expected_err),
1497                    "Can not find expected error '{expected_err}'. Actual error '{e}'"
1498                );
1499            }
1500        }
1501        Ok(())
1502    }
1503
1504    #[test]
1505    fn to_timestamp_with_no_matching_formats() -> Result<()> {
1506        let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1507        let mut format1_builder = StringBuilder::with_capacity(2, 1024);
1508        let mut format2_builder = StringBuilder::with_capacity(2, 1024);
1509        let mut format3_builder = StringBuilder::with_capacity(2, 1024);
1510
1511        date_string_builder.append_null();
1512        format1_builder.append_null();
1513        format2_builder.append_null();
1514        format3_builder.append_null();
1515
1516        date_string_builder.append_value("2020-09-08T13:42:29.19085Z");
1517        format1_builder.append_value("%s");
1518        format2_builder.append_value("%c");
1519        format3_builder.append_value("%H:%M:%S");
1520
1521        let string_array = [
1522            ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef),
1523            ColumnarValue::Array(Arc::new(format1_builder.finish()) as ArrayRef),
1524            ColumnarValue::Array(Arc::new(format2_builder.finish()) as ArrayRef),
1525            ColumnarValue::Array(Arc::new(format3_builder.finish()) as ArrayRef),
1526        ];
1527
1528        let expected_err = "Execution error: Error parsing timestamp from '2020-09-08T13:42:29.19085Z' using format '%H:%M:%S': input contains invalid characters";
1529        match to_timestamp(&string_array) {
1530            Ok(_) => panic!("Expected error but got success"),
1531            Err(e) => {
1532                assert!(
1533                    e.to_string().contains(expected_err),
1534                    "Can not find expected error '{expected_err}'. Actual error '{e}'"
1535                );
1536            }
1537        }
1538        Ok(())
1539    }
1540
1541    #[test]
1542    fn string_to_timestamp_formatted() {
1543        // Explicit timezone
1544        assert_eq!(
1545            1599572549190855000,
1546            parse_timestamp_formatted("2020-09-08T13:42:29.190855+00:00", "%+").unwrap()
1547        );
1548        assert_eq!(
1549            1599572549190855000,
1550            parse_timestamp_formatted("2020-09-08T13:42:29.190855Z", "%+").unwrap()
1551        );
1552        assert_eq!(
1553            1599572549000000000,
1554            parse_timestamp_formatted("2020-09-08T13:42:29Z", "%+").unwrap()
1555        ); // no fractional part
1556        assert_eq!(
1557            1599590549190855000,
1558            parse_timestamp_formatted("2020-09-08T13:42:29.190855-05:00", "%+").unwrap()
1559        );
1560        assert_eq!(
1561            1599590549000000000,
1562            parse_timestamp_formatted("1599590549", "%s").unwrap()
1563        );
1564        assert_eq!(
1565            1599572549000000000,
1566            parse_timestamp_formatted("09-08-2020 13/42/29", "%m-%d-%Y %H/%M/%S")
1567                .unwrap()
1568        );
1569        assert_eq!(
1570            1642896000000000000,
1571            parse_timestamp_formatted("2022-01-23", "%Y-%m-%d").unwrap()
1572        );
1573    }
1574
1575    fn parse_timestamp_formatted(s: &str, format: &str) -> Result<i64, DataFusionError> {
1576        let result = string_to_timestamp_nanos_formatted_with_timezone(
1577            &Some("UTC".parse()?),
1578            s,
1579            format,
1580        );
1581        if let Err(e) = &result {
1582            eprintln!("Error parsing timestamp '{s}' using format '{format}': {e:?}");
1583        }
1584        result
1585    }
1586
1587    #[test]
1588    fn string_to_timestamp_formatted_invalid() {
1589        // Test parsing invalid formats
1590        let cases = [
1591            ("", "%Y%m%d %H%M%S", "premature end of input"),
1592            ("SS", "%c", "premature end of input"),
1593            ("Wed, 18 Feb 2015 23:16:09 GMT", "", "trailing input"),
1594            (
1595                "Wed, 18 Feb 2015 23:16:09 GMT",
1596                "%XX",
1597                "input contains invalid characters",
1598            ),
1599            (
1600                "Wed, 18 Feb 2015 23:16:09 GMT",
1601                "%Y%m%d %H%M%S",
1602                "input contains invalid characters",
1603            ),
1604        ];
1605
1606        for (s, f, ctx) in cases {
1607            let expected = format!(
1608                "Execution error: Error parsing timestamp from '{s}' using format '{f}': {ctx}"
1609            );
1610            let actual = string_to_datetime_formatted(&Utc, s, f)
1611                .unwrap_err()
1612                .strip_backtrace();
1613            assert_eq!(actual, expected)
1614        }
1615    }
1616
1617    #[test]
1618    fn string_to_timestamp_invalid_arguments() {
1619        // Test parsing invalid formats
1620        let cases = [
1621            ("", "%Y%m%d %H%M%S", "premature end of input"),
1622            ("SS", "%c", "premature end of input"),
1623            ("Wed, 18 Feb 2015 23:16:09 GMT", "", "trailing input"),
1624            (
1625                "Wed, 18 Feb 2015 23:16:09 GMT",
1626                "%XX",
1627                "input contains invalid characters",
1628            ),
1629            (
1630                "Wed, 18 Feb 2015 23:16:09 GMT",
1631                "%Y%m%d %H%M%S",
1632                "input contains invalid characters",
1633            ),
1634        ];
1635
1636        for (s, f, ctx) in cases {
1637            let expected = format!(
1638                "Execution error: Error parsing timestamp from '{s}' using format '{f}': {ctx}"
1639            );
1640            let actual = string_to_datetime_formatted(&Utc, s, f)
1641                .unwrap_err()
1642                .strip_backtrace();
1643            assert_eq!(actual, expected)
1644        }
1645    }
1646
1647    #[test]
1648    fn test_no_tz() {
1649        let udfs: Vec<Box<dyn ScalarUDFImpl>> = vec![
1650            Box::new(ToTimestampFunc::new_with_config(&ConfigOptions::default())),
1651            Box::new(ToTimestampSecondsFunc::new_with_config(
1652                &ConfigOptions::default(),
1653            )),
1654            Box::new(ToTimestampMillisFunc::new_with_config(
1655                &ConfigOptions::default(),
1656            )),
1657            Box::new(ToTimestampNanosFunc::new_with_config(
1658                &ConfigOptions::default(),
1659            )),
1660            Box::new(ToTimestampSecondsFunc::new_with_config(
1661                &ConfigOptions::default(),
1662            )),
1663        ];
1664
1665        let mut nanos_builder = TimestampNanosecondArray::builder(2);
1666        let mut millis_builder = TimestampMillisecondArray::builder(2);
1667        let mut micros_builder = TimestampMicrosecondArray::builder(2);
1668        let mut sec_builder = TimestampSecondArray::builder(2);
1669
1670        nanos_builder.append_value(1599572549190850000);
1671        millis_builder.append_value(1599572549190);
1672        micros_builder.append_value(1599572549190850);
1673        sec_builder.append_value(1599572549);
1674
1675        let nanos_timestamps =
1676            Arc::new(nanos_builder.finish().with_timezone("UTC")) as ArrayRef;
1677        let millis_timestamps =
1678            Arc::new(millis_builder.finish().with_timezone("UTC")) as ArrayRef;
1679        let micros_timestamps =
1680            Arc::new(micros_builder.finish().with_timezone("UTC")) as ArrayRef;
1681        let sec_timestamps =
1682            Arc::new(sec_builder.finish().with_timezone("UTC")) as ArrayRef;
1683
1684        let arrays = &[
1685            ColumnarValue::Array(Arc::clone(&nanos_timestamps)),
1686            ColumnarValue::Array(Arc::clone(&millis_timestamps)),
1687            ColumnarValue::Array(Arc::clone(&micros_timestamps)),
1688            ColumnarValue::Array(Arc::clone(&sec_timestamps)),
1689        ];
1690
1691        for udf in &udfs {
1692            for array in arrays {
1693                let rt = udf.return_type(&[array.data_type()]).unwrap();
1694                let arg_field = Field::new("arg", array.data_type().clone(), true).into();
1695                assert!(matches!(rt, Timestamp(_, None)));
1696                let args = ScalarFunctionArgs {
1697                    args: vec![array.clone()],
1698                    arg_fields: vec![arg_field],
1699                    number_rows: 4,
1700                    return_field: Field::new("f", rt, true).into(),
1701                    config_options: Arc::new(ConfigOptions::default()),
1702                };
1703                let res = udf
1704                    .invoke_with_args(args)
1705                    .expect("that to_timestamp parsed values without error");
1706                let array = match res {
1707                    ColumnarValue::Array(res) => res,
1708                    _ => panic!("Expected a columnar array"),
1709                };
1710                let ty = array.data_type();
1711                assert!(matches!(ty, Timestamp(_, None)));
1712            }
1713        }
1714
1715        let mut nanos_builder = TimestampNanosecondArray::builder(2);
1716        let mut millis_builder = TimestampMillisecondArray::builder(2);
1717        let mut micros_builder = TimestampMicrosecondArray::builder(2);
1718        let mut sec_builder = TimestampSecondArray::builder(2);
1719        let mut i64_builder = Int64Array::builder(2);
1720
1721        nanos_builder.append_value(1599572549190850000);
1722        millis_builder.append_value(1599572549190);
1723        micros_builder.append_value(1599572549190850);
1724        sec_builder.append_value(1599572549);
1725        i64_builder.append_value(1599572549);
1726
1727        let nanos_timestamps = Arc::new(nanos_builder.finish()) as ArrayRef;
1728        let millis_timestamps = Arc::new(millis_builder.finish()) as ArrayRef;
1729        let micros_timestamps = Arc::new(micros_builder.finish()) as ArrayRef;
1730        let sec_timestamps = Arc::new(sec_builder.finish()) as ArrayRef;
1731        let i64_timestamps = Arc::new(i64_builder.finish()) as ArrayRef;
1732
1733        let arrays = &[
1734            ColumnarValue::Array(Arc::clone(&nanos_timestamps)),
1735            ColumnarValue::Array(Arc::clone(&millis_timestamps)),
1736            ColumnarValue::Array(Arc::clone(&micros_timestamps)),
1737            ColumnarValue::Array(Arc::clone(&sec_timestamps)),
1738            ColumnarValue::Array(Arc::clone(&i64_timestamps)),
1739        ];
1740
1741        for udf in &udfs {
1742            for array in arrays {
1743                let rt = udf.return_type(&[array.data_type()]).unwrap();
1744                assert!(matches!(rt, Timestamp(_, None)));
1745                let arg_field = Field::new("arg", array.data_type().clone(), true).into();
1746                let args = ScalarFunctionArgs {
1747                    args: vec![array.clone()],
1748                    arg_fields: vec![arg_field],
1749                    number_rows: 5,
1750                    return_field: Field::new("f", rt, true).into(),
1751                    config_options: Arc::new(ConfigOptions::default()),
1752                };
1753                let res = udf
1754                    .invoke_with_args(args)
1755                    .expect("that to_timestamp parsed values without error");
1756                let array = match res {
1757                    ColumnarValue::Array(res) => res,
1758                    _ => panic!("Expected a columnar array"),
1759                };
1760                let ty = array.data_type();
1761                assert!(matches!(ty, Timestamp(_, None)));
1762            }
1763        }
1764    }
1765
1766    #[test]
1767    fn test_to_timestamp_arg_validation() {
1768        let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1769        date_string_builder.append_value("2020-09-08T13:42:29.19085Z");
1770
1771        let data = date_string_builder.finish();
1772
1773        let funcs: Vec<(ScalarFunctionImplementation, TimeUnit)> = vec![
1774            (Arc::new(to_timestamp), Nanosecond),
1775            (Arc::new(to_timestamp_micros), Microsecond),
1776            (Arc::new(to_timestamp_millis), Millisecond),
1777            (Arc::new(to_timestamp_nanos), Nanosecond),
1778            (Arc::new(to_timestamp_seconds), Second),
1779        ];
1780
1781        let mut nanos_builder = TimestampNanosecondArray::builder(2);
1782        let mut millis_builder = TimestampMillisecondArray::builder(2);
1783        let mut micros_builder = TimestampMicrosecondArray::builder(2);
1784        let mut sec_builder = TimestampSecondArray::builder(2);
1785
1786        nanos_builder.append_value(1599572549190850000);
1787        millis_builder.append_value(1599572549190);
1788        micros_builder.append_value(1599572549190850);
1789        sec_builder.append_value(1599572549);
1790
1791        let nanos_expected_timestamps = &nanos_builder.finish() as &dyn Array;
1792        let millis_expected_timestamps = &millis_builder.finish() as &dyn Array;
1793        let micros_expected_timestamps = &micros_builder.finish() as &dyn Array;
1794        let sec_expected_timestamps = &sec_builder.finish() as &dyn Array;
1795
1796        for (func, time_unit) in funcs {
1797            // test UTF8
1798            let string_array = [
1799                ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1800                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%s".to_string()))),
1801                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%c".to_string()))),
1802                ColumnarValue::Scalar(ScalarValue::Utf8(Some("%+".to_string()))),
1803            ];
1804            let parsed_timestamps = func(&string_array)
1805                .expect("that to_timestamp with format args parsed values without error");
1806            if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
1807                assert_eq!(parsed_array.len(), 1);
1808                match time_unit {
1809                    Nanosecond => {
1810                        assert_eq!(nanos_expected_timestamps, parsed_array.as_ref())
1811                    }
1812                    Millisecond => {
1813                        assert_eq!(millis_expected_timestamps, parsed_array.as_ref())
1814                    }
1815                    Microsecond => {
1816                        assert_eq!(micros_expected_timestamps, parsed_array.as_ref())
1817                    }
1818                    Second => {
1819                        assert_eq!(sec_expected_timestamps, parsed_array.as_ref())
1820                    }
1821                };
1822            } else {
1823                panic!("Expected a columnar array")
1824            }
1825
1826            // test LargeUTF8
1827            let string_array = [
1828                ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1829                ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("%s".to_string()))),
1830                ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("%c".to_string()))),
1831                ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("%+".to_string()))),
1832            ];
1833            let parsed_timestamps = func(&string_array)
1834                .expect("that to_timestamp with format args parsed values without error");
1835            if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
1836                assert_eq!(parsed_array.len(), 1);
1837                assert!(matches!(parsed_array.data_type(), Timestamp(_, None)));
1838
1839                match time_unit {
1840                    Nanosecond => {
1841                        assert_eq!(nanos_expected_timestamps, parsed_array.as_ref())
1842                    }
1843                    Millisecond => {
1844                        assert_eq!(millis_expected_timestamps, parsed_array.as_ref())
1845                    }
1846                    Microsecond => {
1847                        assert_eq!(micros_expected_timestamps, parsed_array.as_ref())
1848                    }
1849                    Second => {
1850                        assert_eq!(sec_expected_timestamps, parsed_array.as_ref())
1851                    }
1852                };
1853            } else {
1854                panic!("Expected a columnar array")
1855            }
1856
1857            // test other types
1858            let string_array = [
1859                ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1860                ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
1861                ColumnarValue::Scalar(ScalarValue::Int32(Some(2))),
1862                ColumnarValue::Scalar(ScalarValue::Int32(Some(3))),
1863            ];
1864
1865            let expected = "Unsupported data type Int32 for function".to_string();
1866            let actual = func(&string_array).unwrap_err().to_string();
1867            assert_contains!(actual, expected);
1868
1869            // test other types
1870            let string_array = [
1871                ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1872                ColumnarValue::Array(Arc::new(PrimitiveArray::<Int64Type>::new(
1873                    vec![1i64].into(),
1874                    None,
1875                )) as ArrayRef),
1876            ];
1877
1878            let expected = "Unsupported data type".to_string();
1879            let actual = func(&string_array).unwrap_err().to_string();
1880            assert_contains!(actual, expected);
1881        }
1882    }
1883
1884    #[test]
1885    fn test_decimal_to_nanoseconds_negative_scale() {
1886        // scale -2: internal value 5 represents 5 * 10^2 = 500 seconds
1887        let nanos = decimal_to_nanoseconds(5, -2).unwrap();
1888        assert_eq!(nanos, 500_000_000_000); // 500 seconds in nanoseconds
1889
1890        // scale -1: internal value 10 represents 10 * 10^1 = 100 seconds
1891        let nanos = decimal_to_nanoseconds(10, -1).unwrap();
1892        assert_eq!(nanos, 100_000_000_000);
1893
1894        // scale 0: internal value 5 represents 5 seconds
1895        let nanos = decimal_to_nanoseconds(5, 0).unwrap();
1896        assert_eq!(nanos, 5_000_000_000);
1897
1898        // scale 3: internal value 1500 represents 1.5 seconds
1899        let nanos = decimal_to_nanoseconds(1500, 3).unwrap();
1900        assert_eq!(nanos, 1_500_000_000);
1901    }
1902}