datafusion_functions/datetime/
to_char.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::any::Any;
19use std::sync::Arc;
20
21use arrow::array::cast::AsArray;
22use arrow::array::{new_null_array, Array, ArrayRef, StringArray};
23use arrow::datatypes::DataType;
24use arrow::datatypes::DataType::{
25    Date32, Date64, Duration, Time32, Time64, Timestamp, Utf8,
26};
27use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
28use arrow::error::ArrowError;
29use arrow::util::display::{ArrayFormatter, DurationFormat, FormatOptions};
30
31use datafusion_common::{exec_err, utils::take_function_args, Result, ScalarValue};
32use datafusion_expr::TypeSignature::Exact;
33use datafusion_expr::{
34    ColumnarValue, Documentation, ScalarUDFImpl, Signature, Volatility, TIMEZONE_WILDCARD,
35};
36use datafusion_macros::user_doc;
37
38#[user_doc(
39    doc_section(label = "Time and Date Functions"),
40    description = "Returns a string representation of a date, time, timestamp or duration based on a [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html). Unlike the PostgreSQL equivalent of this function numerical formatting is not supported.",
41    syntax_example = "to_char(expression, format)",
42    sql_example = r#"```sql
43> select to_char('2023-03-01'::date, '%d-%m-%Y');
44+----------------------------------------------+
45| to_char(Utf8("2023-03-01"),Utf8("%d-%m-%Y")) |
46+----------------------------------------------+
47| 01-03-2023                                   |
48+----------------------------------------------+
49```
50
51Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/to_char.rs)
52"#,
53    argument(
54        name = "expression",
55        description = "Expression to operate on. Can be a constant, column, or function that results in a date, time, timestamp or duration."
56    ),
57    argument(
58        name = "format",
59        description = "A [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) string to use to convert the expression."
60    ),
61    argument(
62        name = "day",
63        description = "Day to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
64    )
65)]
66#[derive(Debug)]
67pub struct ToCharFunc {
68    signature: Signature,
69    aliases: Vec<String>,
70}
71
72impl Default for ToCharFunc {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl ToCharFunc {
79    pub fn new() -> Self {
80        Self {
81            signature: Signature::one_of(
82                vec![
83                    Exact(vec![Date32, Utf8]),
84                    Exact(vec![Date64, Utf8]),
85                    Exact(vec![Time64(Nanosecond), Utf8]),
86                    Exact(vec![Time64(Microsecond), Utf8]),
87                    Exact(vec![Time32(Millisecond), Utf8]),
88                    Exact(vec![Time32(Second), Utf8]),
89                    Exact(vec![
90                        Timestamp(Nanosecond, Some(TIMEZONE_WILDCARD.into())),
91                        Utf8,
92                    ]),
93                    Exact(vec![Timestamp(Nanosecond, None), Utf8]),
94                    Exact(vec![
95                        Timestamp(Microsecond, Some(TIMEZONE_WILDCARD.into())),
96                        Utf8,
97                    ]),
98                    Exact(vec![Timestamp(Microsecond, None), Utf8]),
99                    Exact(vec![
100                        Timestamp(Millisecond, Some(TIMEZONE_WILDCARD.into())),
101                        Utf8,
102                    ]),
103                    Exact(vec![Timestamp(Millisecond, None), Utf8]),
104                    Exact(vec![
105                        Timestamp(Second, Some(TIMEZONE_WILDCARD.into())),
106                        Utf8,
107                    ]),
108                    Exact(vec![Timestamp(Second, None), Utf8]),
109                    Exact(vec![Duration(Nanosecond), Utf8]),
110                    Exact(vec![Duration(Microsecond), Utf8]),
111                    Exact(vec![Duration(Millisecond), Utf8]),
112                    Exact(vec![Duration(Second), Utf8]),
113                ],
114                Volatility::Immutable,
115            ),
116            aliases: vec![String::from("date_format")],
117        }
118    }
119}
120
121impl ScalarUDFImpl for ToCharFunc {
122    fn as_any(&self) -> &dyn Any {
123        self
124    }
125
126    fn name(&self) -> &str {
127        "to_char"
128    }
129
130    fn signature(&self) -> &Signature {
131        &self.signature
132    }
133
134    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
135        Ok(Utf8)
136    }
137
138    fn invoke_with_args(
139        &self,
140        args: datafusion_expr::ScalarFunctionArgs,
141    ) -> Result<ColumnarValue> {
142        let args = args.args;
143        let [date_time, format] = take_function_args(self.name(), &args)?;
144
145        match format {
146            ColumnarValue::Scalar(ScalarValue::Utf8(None))
147            | ColumnarValue::Scalar(ScalarValue::Null) => {
148                _to_char_scalar(date_time.clone(), None)
149            }
150            // constant format
151            ColumnarValue::Scalar(ScalarValue::Utf8(Some(format))) => {
152                // invoke to_char_scalar with the known string, without converting to array
153                _to_char_scalar(date_time.clone(), Some(format))
154            }
155            ColumnarValue::Array(_) => _to_char_array(&args),
156            _ => {
157                exec_err!(
158                    "Format for `to_char` must be non-null Utf8, received {:?}",
159                    format.data_type()
160                )
161            }
162        }
163    }
164
165    fn aliases(&self) -> &[String] {
166        &self.aliases
167    }
168    fn documentation(&self) -> Option<&Documentation> {
169        self.doc()
170    }
171}
172
173fn _build_format_options<'a>(
174    data_type: &DataType,
175    format: Option<&'a str>,
176) -> Result<FormatOptions<'a>, Result<ColumnarValue>> {
177    let Some(format) = format else {
178        return Ok(FormatOptions::new());
179    };
180    let format_options = match data_type {
181        Date32 => FormatOptions::new().with_date_format(Some(format)),
182        Date64 => FormatOptions::new().with_datetime_format(Some(format)),
183        Time32(_) => FormatOptions::new().with_time_format(Some(format)),
184        Time64(_) => FormatOptions::new().with_time_format(Some(format)),
185        Timestamp(_, _) => FormatOptions::new()
186            .with_timestamp_format(Some(format))
187            .with_timestamp_tz_format(Some(format)),
188        Duration(_) => FormatOptions::new().with_duration_format(
189            if "ISO8601".eq_ignore_ascii_case(format) {
190                DurationFormat::ISO8601
191            } else {
192                DurationFormat::Pretty
193            },
194        ),
195        other => {
196            return Err(exec_err!(
197                "to_char only supports date, time, timestamp and duration data types, received {other:?}"
198            ));
199        }
200    };
201    Ok(format_options)
202}
203
204/// Special version when arg\[1] is a scalar
205fn _to_char_scalar(
206    expression: ColumnarValue,
207    format: Option<&str>,
208) -> Result<ColumnarValue> {
209    // it's possible that the expression is a scalar however because
210    // of the implementation in arrow-rs we need to convert it to an array
211    let data_type = &expression.data_type();
212    let is_scalar_expression = matches!(&expression, ColumnarValue::Scalar(_));
213    let array = expression.into_array(1)?;
214
215    if format.is_none() {
216        if is_scalar_expression {
217            return Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)));
218        } else {
219            return Ok(ColumnarValue::Array(new_null_array(&Utf8, array.len())));
220        }
221    }
222
223    let format_options = match _build_format_options(data_type, format) {
224        Ok(value) => value,
225        Err(value) => return value,
226    };
227
228    let formatter = ArrayFormatter::try_new(array.as_ref(), &format_options)?;
229    let formatted: Result<Vec<Option<String>>, ArrowError> = (0..array.len())
230        .map(|i| {
231            if array.is_null(i) {
232                Ok(None)
233            } else {
234                formatter.value(i).try_to_string().map(Some)
235            }
236        })
237        .collect();
238
239    if let Ok(formatted) = formatted {
240        if is_scalar_expression {
241            Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
242                formatted.first().unwrap().clone(),
243            )))
244        } else {
245            Ok(ColumnarValue::Array(
246                Arc::new(StringArray::from(formatted)) as ArrayRef
247            ))
248        }
249    } else {
250        exec_err!("{}", formatted.unwrap_err())
251    }
252}
253
254fn _to_char_array(args: &[ColumnarValue]) -> Result<ColumnarValue> {
255    let arrays = ColumnarValue::values_to_arrays(args)?;
256    let mut results: Vec<Option<String>> = vec![];
257    let format_array = arrays[1].as_string::<i32>();
258    let data_type = arrays[0].data_type();
259
260    for idx in 0..arrays[0].len() {
261        let format = if format_array.is_null(idx) {
262            None
263        } else {
264            Some(format_array.value(idx))
265        };
266        if format.is_none() {
267            results.push(None);
268            continue;
269        }
270        let format_options = match _build_format_options(data_type, format) {
271            Ok(value) => value,
272            Err(value) => return value,
273        };
274        // this isn't ideal but this can't use ValueFormatter as it isn't independent
275        // from ArrayFormatter
276        let formatter = ArrayFormatter::try_new(arrays[0].as_ref(), &format_options)?;
277        let result = formatter.value(idx).try_to_string();
278        match result {
279            Ok(value) => results.push(Some(value)),
280            Err(e) => return exec_err!("{}", e),
281        }
282    }
283
284    match args[0] {
285        ColumnarValue::Array(_) => Ok(ColumnarValue::Array(Arc::new(StringArray::from(
286            results,
287        )) as ArrayRef)),
288        ColumnarValue::Scalar(_) => match results.first().unwrap() {
289            Some(value) => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
290                value.to_string(),
291            )))),
292            None => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))),
293        },
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use crate::datetime::to_char::ToCharFunc;
300    use arrow::array::{
301        Array, ArrayRef, Date32Array, Date64Array, StringArray, Time32MillisecondArray,
302        Time32SecondArray, Time64MicrosecondArray, Time64NanosecondArray,
303        TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray,
304        TimestampSecondArray,
305    };
306    use arrow::datatypes::{DataType, Field, TimeUnit};
307    use chrono::{NaiveDateTime, Timelike};
308    use datafusion_common::ScalarValue;
309    use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
310    use std::sync::Arc;
311
312    #[test]
313    fn test_to_char() {
314        let date = "2020-01-02T03:04:05"
315            .parse::<NaiveDateTime>()
316            .unwrap()
317            .with_nanosecond(12345)
318            .unwrap();
319        let date2 = "2026-07-08T09:10:11"
320            .parse::<NaiveDateTime>()
321            .unwrap()
322            .with_nanosecond(56789)
323            .unwrap();
324
325        let scalar_data = vec![
326            (
327                ScalarValue::Date32(Some(18506)),
328                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
329                "2020::09::01".to_string(),
330            ),
331            (
332                ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
333                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
334                "2020::01::02".to_string(),
335            ),
336            (
337                ScalarValue::Time32Second(Some(31851)),
338                ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
339                "08-50-51".to_string(),
340            ),
341            (
342                ScalarValue::Time32Millisecond(Some(18506000)),
343                ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
344                "05-08-26".to_string(),
345            ),
346            (
347                ScalarValue::Time64Microsecond(Some(12344567000)),
348                ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
349                "03-25-44 567000000".to_string(),
350            ),
351            (
352                ScalarValue::Time64Nanosecond(Some(12344567890000)),
353                ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
354                "03-25-44 567890000".to_string(),
355            ),
356            (
357                ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
358                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
359                "2020::01::02 05::04::03".to_string(),
360            ),
361            (
362                ScalarValue::TimestampMillisecond(
363                    Some(date.and_utc().timestamp_millis()),
364                    None,
365                ),
366                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
367                "2020::01::02 05::04::03".to_string(),
368            ),
369            (
370                ScalarValue::TimestampMicrosecond(
371                    Some(date.and_utc().timestamp_micros()),
372                    None,
373                ),
374                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
375                "2020::01::02 05::04::03 000012000".to_string(),
376            ),
377            (
378                ScalarValue::TimestampNanosecond(
379                    Some(date.and_utc().timestamp_nanos_opt().unwrap()),
380                    None,
381                ),
382                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
383                "2020::01::02 05::04::03 000012345".to_string(),
384            ),
385        ];
386
387        for (value, format, expected) in scalar_data {
388            let arg_fields = vec![
389                Field::new("a", value.data_type(), false).into(),
390                Field::new("a", format.data_type(), false).into(),
391            ];
392            let args = datafusion_expr::ScalarFunctionArgs {
393                args: vec![ColumnarValue::Scalar(value), ColumnarValue::Scalar(format)],
394                arg_fields,
395                number_rows: 1,
396                return_field: Field::new("f", DataType::Utf8, true).into(),
397            };
398            let result = ToCharFunc::new()
399                .invoke_with_args(args)
400                .expect("that to_char parsed values without error");
401
402            if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
403                assert_eq!(expected, date.unwrap());
404            } else {
405                panic!("Expected a scalar value")
406            }
407        }
408
409        let scalar_array_data = vec![
410            (
411                ScalarValue::Date32(Some(18506)),
412                StringArray::from(vec!["%Y::%m::%d".to_string()]),
413                "2020::09::01".to_string(),
414            ),
415            (
416                ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
417                StringArray::from(vec!["%Y::%m::%d".to_string()]),
418                "2020::01::02".to_string(),
419            ),
420            (
421                ScalarValue::Time32Second(Some(31851)),
422                StringArray::from(vec!["%H-%M-%S".to_string()]),
423                "08-50-51".to_string(),
424            ),
425            (
426                ScalarValue::Time32Millisecond(Some(18506000)),
427                StringArray::from(vec!["%H-%M-%S".to_string()]),
428                "05-08-26".to_string(),
429            ),
430            (
431                ScalarValue::Time64Microsecond(Some(12344567000)),
432                StringArray::from(vec!["%H-%M-%S %f".to_string()]),
433                "03-25-44 567000000".to_string(),
434            ),
435            (
436                ScalarValue::Time64Nanosecond(Some(12344567890000)),
437                StringArray::from(vec!["%H-%M-%S %f".to_string()]),
438                "03-25-44 567890000".to_string(),
439            ),
440            (
441                ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
442                StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
443                "2020::01::02 05::04::03".to_string(),
444            ),
445            (
446                ScalarValue::TimestampMillisecond(
447                    Some(date.and_utc().timestamp_millis()),
448                    None,
449                ),
450                StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
451                "2020::01::02 05::04::03".to_string(),
452            ),
453            (
454                ScalarValue::TimestampMicrosecond(
455                    Some(date.and_utc().timestamp_micros()),
456                    None,
457                ),
458                StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
459                "2020::01::02 05::04::03 000012000".to_string(),
460            ),
461            (
462                ScalarValue::TimestampNanosecond(
463                    Some(date.and_utc().timestamp_nanos_opt().unwrap()),
464                    None,
465                ),
466                StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
467                "2020::01::02 05::04::03 000012345".to_string(),
468            ),
469        ];
470
471        for (value, format, expected) in scalar_array_data {
472            let batch_len = format.len();
473            let arg_fields = vec![
474                Field::new("a", value.data_type(), false).into(),
475                Field::new("a", format.data_type().to_owned(), false).into(),
476            ];
477            let args = datafusion_expr::ScalarFunctionArgs {
478                args: vec![
479                    ColumnarValue::Scalar(value),
480                    ColumnarValue::Array(Arc::new(format) as ArrayRef),
481                ],
482                arg_fields,
483                number_rows: batch_len,
484                return_field: Field::new("f", DataType::Utf8, true).into(),
485            };
486            let result = ToCharFunc::new()
487                .invoke_with_args(args)
488                .expect("that to_char parsed values without error");
489
490            if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
491                assert_eq!(expected, date.unwrap());
492            } else {
493                panic!("Expected a scalar value")
494            }
495        }
496
497        let array_scalar_data = vec![
498            (
499                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
500                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
501                StringArray::from(vec!["2020::09::01", "2020::09::02"]),
502            ),
503            (
504                Arc::new(Date64Array::from(vec![
505                    date.and_utc().timestamp_millis(),
506                    date2.and_utc().timestamp_millis(),
507                ])) as ArrayRef,
508                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
509                StringArray::from(vec!["2020::01::02", "2026::07::08"]),
510            ),
511        ];
512
513        let array_array_data = vec![
514            (
515                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
516                StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
517                StringArray::from(vec!["2020::09::01", "02::09::2020"]),
518            ),
519            (
520                Arc::new(Date64Array::from(vec![
521                    date.and_utc().timestamp_millis(),
522                    date2.and_utc().timestamp_millis(),
523                ])) as ArrayRef,
524                StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
525                StringArray::from(vec!["2020::01::02", "08::07::2026"]),
526            ),
527            (
528                Arc::new(Time32MillisecondArray::from(vec![1850600, 1860700]))
529                    as ArrayRef,
530                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
531                StringArray::from(vec!["00:30:50", "00::31::00"]),
532            ),
533            (
534                Arc::new(Time32SecondArray::from(vec![18506, 18507])) as ArrayRef,
535                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
536                StringArray::from(vec!["05:08:26", "05::08::27"]),
537            ),
538            (
539                Arc::new(Time64MicrosecondArray::from(vec![12344567000, 22244567000]))
540                    as ArrayRef,
541                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
542                StringArray::from(vec!["03:25:44", "06::10::44"]),
543            ),
544            (
545                Arc::new(Time64NanosecondArray::from(vec![
546                    1234456789000,
547                    2224456789000,
548                ])) as ArrayRef,
549                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
550                StringArray::from(vec!["00:20:34", "00::37::04"]),
551            ),
552            (
553                Arc::new(TimestampSecondArray::from(vec![
554                    date.and_utc().timestamp(),
555                    date2.and_utc().timestamp(),
556                ])) as ArrayRef,
557                StringArray::from(vec!["%Y::%m::%d %S::%M::%H", "%d::%m::%Y %S-%M-%H"]),
558                StringArray::from(vec![
559                    "2020::01::02 05::04::03",
560                    "08::07::2026 11-10-09",
561                ]),
562            ),
563            (
564                Arc::new(TimestampMillisecondArray::from(vec![
565                    date.and_utc().timestamp_millis(),
566                    date2.and_utc().timestamp_millis(),
567                ])) as ArrayRef,
568                StringArray::from(vec![
569                    "%Y::%m::%d %S::%M::%H %f",
570                    "%d::%m::%Y %S-%M-%H %f",
571                ]),
572                StringArray::from(vec![
573                    "2020::01::02 05::04::03 000000000",
574                    "08::07::2026 11-10-09 000000000",
575                ]),
576            ),
577            (
578                Arc::new(TimestampMicrosecondArray::from(vec![
579                    date.and_utc().timestamp_micros(),
580                    date2.and_utc().timestamp_micros(),
581                ])) as ArrayRef,
582                StringArray::from(vec![
583                    "%Y::%m::%d %S::%M::%H %f",
584                    "%d::%m::%Y %S-%M-%H %f",
585                ]),
586                StringArray::from(vec![
587                    "2020::01::02 05::04::03 000012000",
588                    "08::07::2026 11-10-09 000056000",
589                ]),
590            ),
591            (
592                Arc::new(TimestampNanosecondArray::from(vec![
593                    date.and_utc().timestamp_nanos_opt().unwrap(),
594                    date2.and_utc().timestamp_nanos_opt().unwrap(),
595                ])) as ArrayRef,
596                StringArray::from(vec![
597                    "%Y::%m::%d %S::%M::%H %f",
598                    "%d::%m::%Y %S-%M-%H %f",
599                ]),
600                StringArray::from(vec![
601                    "2020::01::02 05::04::03 000012345",
602                    "08::07::2026 11-10-09 000056789",
603                ]),
604            ),
605        ];
606
607        for (value, format, expected) in array_scalar_data {
608            let batch_len = value.len();
609            let arg_fields = vec![
610                Field::new("a", value.data_type().clone(), false).into(),
611                Field::new("a", format.data_type(), false).into(),
612            ];
613            let args = datafusion_expr::ScalarFunctionArgs {
614                args: vec![
615                    ColumnarValue::Array(value as ArrayRef),
616                    ColumnarValue::Scalar(format),
617                ],
618                arg_fields,
619                number_rows: batch_len,
620                return_field: Field::new("f", DataType::Utf8, true).into(),
621            };
622            let result = ToCharFunc::new()
623                .invoke_with_args(args)
624                .expect("that to_char parsed values without error");
625
626            if let ColumnarValue::Array(result) = result {
627                assert_eq!(result.len(), 2);
628                assert_eq!(&expected as &dyn Array, result.as_ref());
629            } else {
630                panic!("Expected an array value")
631            }
632        }
633
634        for (value, format, expected) in array_array_data {
635            let batch_len = value.len();
636            let arg_fields = vec![
637                Field::new("a", value.data_type().clone(), false).into(),
638                Field::new("a", format.data_type().clone(), false).into(),
639            ];
640            let args = datafusion_expr::ScalarFunctionArgs {
641                args: vec![
642                    ColumnarValue::Array(value),
643                    ColumnarValue::Array(Arc::new(format) as ArrayRef),
644                ],
645                arg_fields,
646                number_rows: batch_len,
647                return_field: Field::new("f", DataType::Utf8, true).into(),
648            };
649            let result = ToCharFunc::new()
650                .invoke_with_args(args)
651                .expect("that to_char parsed values without error");
652
653            if let ColumnarValue::Array(result) = result {
654                assert_eq!(result.len(), 2);
655                assert_eq!(&expected as &dyn Array, result.as_ref());
656            } else {
657                panic!("Expected an array value")
658            }
659        }
660
661        //
662        // Fallible test cases
663        //
664
665        // invalid number of arguments
666        let arg_field = Field::new("a", DataType::Int32, true).into();
667        let args = datafusion_expr::ScalarFunctionArgs {
668            args: vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
669            arg_fields: vec![arg_field],
670            number_rows: 1,
671            return_field: Field::new("f", DataType::Utf8, true).into(),
672        };
673        let result = ToCharFunc::new().invoke_with_args(args);
674        assert_eq!(
675            result.err().unwrap().strip_backtrace(),
676            "Execution error: to_char function requires 2 arguments, got 1"
677        );
678
679        // invalid type
680        let arg_fields = vec![
681            Field::new("a", DataType::Utf8, true).into(),
682            Field::new("a", DataType::Timestamp(TimeUnit::Nanosecond, None), true).into(),
683        ];
684        let args = datafusion_expr::ScalarFunctionArgs {
685            args: vec![
686                ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
687                ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
688            ],
689            arg_fields,
690            number_rows: 1,
691            return_field: Field::new("f", DataType::Utf8, true).into(),
692        };
693        let result = ToCharFunc::new().invoke_with_args(args);
694        assert_eq!(
695            result.err().unwrap().strip_backtrace(),
696            "Execution error: Format for `to_char` must be non-null Utf8, received Timestamp(Nanosecond, None)"
697        );
698    }
699}