## Coverage Report

#### Created: 2025-11-22 06:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /Users/andrewlamb/Software/datafusion/datafusion/functions/src/datetime/to_char.rs

    Line
    Count
    Source
    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
    18
    use std::any::Any;
    19
    use std::sync::Arc;
    20
    21
    use arrow::array::cast::AsArray;
    22
    use arrow::array::{new_null_array, Array, ArrayRef, StringArray};
    23
    use arrow::compute::cast;
    24
    use arrow::datatypes::DataType;
    25
    use arrow::datatypes::DataType::{
    26
        Date32, Date64, Duration, Time32, Time64, Timestamp, Utf8,
    27
    };
    28
    use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
    29
    use arrow::error::ArrowError;
    30
    use arrow::util::display::{ArrayFormatter, DurationFormat, FormatOptions};
    31
    use datafusion_common::{exec_err, utils::take_function_args, Result, ScalarValue};
    32
    use datafusion_expr::TypeSignature::Exact;
    33
    use datafusion_expr::{
    34
        ColumnarValue, Documentation, ScalarUDFImpl, Signature, Volatility, TIMEZONE_WILDCARD,
    35
    };
    36
    use 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
    51
    Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/date_time_functions.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, PartialEq, Eq, Hash)]
    67
    pub struct ToCharFunc {
    68
        signature: Signature,
    69
        aliases: Vec<String>,
    70
    }
    71
    72
    impl Default for ToCharFunc {
    73
    0
        fn default() -> Self {
    74
    0
            Self::new()
    75
    0
        }
    76
    }
    77
    78
    impl ToCharFunc {
    79
    0
        pub fn new() -> Self {
    80
    0
            Self {
    81
    0
                signature: Signature::one_of(
    82
    0
                    vec![
    83
    0
                        Exact(vec![Date32, Utf8]),
    84
    0
                        Exact(vec![Date64, Utf8]),
    85
    0
                        Exact(vec![Time64(Nanosecond), Utf8]),
    86
    0
                        Exact(vec![Time64(Microsecond), Utf8]),
    87
    0
                        Exact(vec![Time32(Millisecond), Utf8]),
    88
    0
                        Exact(vec![Time32(Second), Utf8]),
    89
    0
                        Exact(vec![
    90
    0
                            Timestamp(Nanosecond, Some(TIMEZONE_WILDCARD.into())),
    91
    0
                            Utf8,
    92
    0
                        ]),
    93
    0
                        Exact(vec![Timestamp(Nanosecond, None), Utf8]),
    94
    0
                        Exact(vec![
    95
    0
                            Timestamp(Microsecond, Some(TIMEZONE_WILDCARD.into())),
    96
    0
                            Utf8,
    97
    0
                        ]),
    98
    0
                        Exact(vec![Timestamp(Microsecond, None), Utf8]),
    99
    0
                        Exact(vec![
    100
    0
                            Timestamp(Millisecond, Some(TIMEZONE_WILDCARD.into())),
    101
    0
                            Utf8,
    102
    0
                        ]),
    103
    0
                        Exact(vec![Timestamp(Millisecond, None), Utf8]),
    104
    0
                        Exact(vec![
    105
    0
                            Timestamp(Second, Some(TIMEZONE_WILDCARD.into())),
    106
    0
                            Utf8,
    107
    0
                        ]),
    108
    0
                        Exact(vec![Timestamp(Second, None), Utf8]),
    109
    0
                        Exact(vec![Duration(Nanosecond), Utf8]),
    110
    0
                        Exact(vec![Duration(Microsecond), Utf8]),
    111
    0
                        Exact(vec![Duration(Millisecond), Utf8]),
    112
    0
                        Exact(vec![Duration(Second), Utf8]),
    113
    0
                    ],
    114
    0
                    Volatility::Immutable,
    115
    0
                ),
    116
    0
                aliases: vec![String::from("date_format")],
    117
    0
            }
    118
    0
        }
    119
    }
    120
    121
    impl ScalarUDFImpl for ToCharFunc {
    122
    0
        fn as_any(&self) -> &dyn Any {
    123
    0
            self
    124
    0
        }
    125
    126
    0
        fn name(&self) -> &str {
    127
    0
            "to_char"
    128
    0
        }
    129
    130
    0
        fn signature(&self) -> &Signature {
    131
    0
            &self.signature
    132
    0
        }
    133
    134
    0
        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
    135
    0
            Ok(Utf8)
    136
    0
        }
    137
    138
    0
        fn invoke_with_args(
    139
    0
            &self,
    140
    0
            args: datafusion_expr::ScalarFunctionArgs,
    141
    0
        ) -> Result<ColumnarValue> {
    142
    0
            let args = args.args;
    143
    0
            let [date_time, format] = take_function_args(self.name(), &args)?;
    144
    145
    0
            match format {
    146
                ColumnarValue::Scalar(ScalarValue::Utf8(None))
    147
                | ColumnarValue::Scalar(ScalarValue::Null) => {
    148
    0
                    to_char_scalar(date_time.clone(), None)
    149
                }
    150
                // constant format
    151
    0
                ColumnarValue::Scalar(ScalarValue::Utf8(Some(format))) => {
    152
                    // invoke to_char_scalar with the known string, without converting to array
    153
    0
                    to_char_scalar(date_time.clone(), Some(format))
    154
                }
    155
    0
                ColumnarValue::Array(_) => to_char_array(&args),
    156
                _ => {
    157
    0
                    exec_err!(
    158
    0
                        "Format for `to_char` must be non-null Utf8, received {:?}",
    159
    0
                        format.data_type()
    160
                    )
    161
                }
    162
            }
    163
    0
        }
    164
    165
    0
        fn aliases(&self) -> &[String] {
    166
    0
            &self.aliases
    167
    0
        }
    168
    169
    0
        fn documentation(&self) -> Option<&Documentation> {
    170
    0
            self.doc()
    171
    0
        }
    172
    }
    173
    174
    0
    fn build_format_options<'a>(
    175
    0
        data_type: &DataType,
    176
    0
        format: Option<&'a str>,
    177
    0
    ) -> Result<FormatOptions<'a>, Result<ColumnarValue>> {
    178
    0
        let Some(format) = format else {
    179
    0
            return Ok(FormatOptions::new());
    180
        };
    181
    0
        let format_options = match data_type {
    182
    0
            Date32 => FormatOptions::new()
    183
    0
                .with_date_format(Some(format))
    184
    0
                .with_datetime_format(Some(format)),
    185
    0
            Date64 => FormatOptions::new().with_datetime_format(Some(format)),
    186
    0
            Time32(_) => FormatOptions::new().with_time_format(Some(format)),
    187
    0
            Time64(_) => FormatOptions::new().with_time_format(Some(format)),
    188
    0
            Timestamp(_, _) => FormatOptions::new()
    189
    0
                .with_timestamp_format(Some(format))
    190
    0
                .with_timestamp_tz_format(Some(format)),
    191
    0
            Duration(_) => FormatOptions::new().with_duration_format(
    192
    0
                if "ISO8601".eq_ignore_ascii_case(format) {
    193
    0
                    DurationFormat::ISO8601
    194
                } else {
    195
    0
                    DurationFormat::Pretty
    196
                },
    197
            ),
    198
    0
            other => {
    199
    0
                return Err(exec_err!(
    200
    0
                    "to_char only supports date, time, timestamp and duration data types, received {other:?}"
    201
    0
                ));
    202
            }
    203
        };
    204
    0
        Ok(format_options)
    205
    0
    }
    206
    207
    /// Special version when arg\[1] is a scalar
    208
    0
    fn to_char_scalar(
    209
    0
        expression: ColumnarValue,
    210
    0
        format: Option<&str>,
    211
    0
    ) -> Result<ColumnarValue> {
    212
        // it's possible that the expression is a scalar however because
    213
        // of the implementation in arrow-rs we need to convert it to an array
    214
    0
        let data_type = &expression.data_type();
    215
    0
        let is_scalar_expression = matches!(&expression, ColumnarValue::Scalar(_));
    216
    0
        let array = expression.clone().into_array(1)?;
    217
    218
    0
        if format.is_none() {
    219
    0
            return if is_scalar_expression {
    220
    0
                Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
    221
            } else {
    222
    0
                Ok(ColumnarValue::Array(new_null_array(&Utf8, array.len())))
    223
            };
    224
    0
        }
    225
    226
    0
        let format_options = match build_format_options(data_type, format) {
    227
    0
            Ok(value) => value,
    228
    0
            Err(value) => return value,
    229
        };
    230
    231
    0
        let formatter = ArrayFormatter::try_new(array.as_ref(), &format_options)?;
    232
    0
        let formatted: Result<Vec<Option<String>>, ArrowError> = (0..array.len())
    233
    0
            .map(|i| {
    234
    0
                if array.is_null(i) {
    235
    0
                    Ok(None)
    236
                } else {
    237
    0
                    formatter.value(i).try_to_string().map(Some)
    238
                }
    239
    0
            })
    240
    0
            .collect();
    241
    242
    0
        if let Ok(formatted) = formatted {
    243
    0
            if is_scalar_expression {
    244
    0
                Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
    245
    0
                    formatted.first().unwrap().clone(),
    246
    0
                )))
    247
            } else {
    248
    0
                Ok(ColumnarValue::Array(
    249
    0
                    Arc::new(StringArray::from(formatted)) as ArrayRef
    250
    0
                ))
    251
            }
    252
        } else {
    253
            // if the data type was a Date32, formatting could have failed because the format string
    254
            // contained datetime specifiers, so we'll retry by casting the date array as a timestamp array
    255
    0
            if data_type == &Date32 {
    256
    0
                return to_char_scalar(expression.clone().cast_to(&Date64, None)?, format);
    257
    0
            }
    258
    259
    0
            exec_err!("{}", formatted.unwrap_err())
    260
        }
    261
    0
    }
    262
    263
    0
    fn to_char_array(args: &[ColumnarValue]) -> Result<ColumnarValue> {
    264
    0
        let arrays = ColumnarValue::values_to_arrays(args)?;
    265
    0
        let mut results: Vec<Option<String>> = vec![];
    266
    0
        let format_array = arrays[1].as_string::<i32>();
    267
    0
        let data_type = arrays[0].data_type();
    268
    269
    0
        for idx in 0..arrays[0].len() {
    270
    0
            let format = if format_array.is_null(idx) {
    271
    0
                None
    272
            } else {
    273
    0
                Some(format_array.value(idx))
    274
            };
    275
    0
            if format.is_none() {
    276
    0
                results.push(None);
    277
    0
                continue;
    278
    0
            }
    279
    0
            let format_options = match build_format_options(data_type, format) {
    280
    0
                Ok(value) => value,
    281
    0
                Err(value) => return value,
    282
            };
    283
            // this isn't ideal but this can't use ValueFormatter as it isn't independent
    284
            // from ArrayFormatter
    285
    0
            let formatter = ArrayFormatter::try_new(arrays[0].as_ref(), &format_options)?;
    286
    0
            let result = formatter.value(idx).try_to_string();
    287
    0
            match result {
    288
    0
                Ok(value) => results.push(Some(value)),
    289
    0
                Err(e) => {
    290
                    // if the data type was a Date32, formatting could have failed because the format string
    291
                    // contained datetime specifiers, so we'll treat this specific date element as a timestamp
    292
    0
                    if data_type == &Date32 {
    293
    0
                        let failed_date_value = arrays[0].slice(idx, 1);
    294
    295
    0
                        match retry_date_as_timestamp(failed_date_value, &format_options) {
    296
    0
                            Ok(value) => {
    297
    0
                                results.push(Some(value));
    298
    0
                                continue;
    299
                            }
    300
    0
                            Err(e) => {
    301
    0
                                return exec_err!("{}", e);
    302
                            }
    303
                        }
    304
    0
                    }
    305
    306
    0
                    return exec_err!("{}", e);
    307
                }
    308
            }
    309
        }
    310
    311
    0
        match args[0] {
    312
    0
            ColumnarValue::Array(_) => Ok(ColumnarValue::Array(Arc::new(StringArray::from(
    313
    0
                results,
    314
    0
            )) as ArrayRef)),
    315
    0
            ColumnarValue::Scalar(_) => match results.first().unwrap() {
    316
    0
                Some(value) => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
    317
    0
                    value.to_string(),
    318
    0
                )))),
    319
    0
                None => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))),
    320
            },
    321
        }
    322
    0
    }
    323
    324
    0
    fn retry_date_as_timestamp(
    325
    0
        array_ref: ArrayRef,
    326
    0
        format_options: &FormatOptions,
    327
    0
    ) -> Result<String> {
    328
    0
        let target_data_type = Date64;
    329
    330
    0
        let date_value = cast(&array_ref, &target_data_type)?;
    331
    0
        let formatter = ArrayFormatter::try_new(date_value.as_ref(), format_options)?;
    332
    0
        let result = formatter.value(0).try_to_string()?;
    333
    334
    0
        Ok(result)
    335
    0
    }
    336
    337
    #[cfg(test)]
    338
    mod tests {
    339
        use crate::datetime::to_char::ToCharFunc;
    340
        use arrow::array::{
    341
            Array, ArrayRef, Date32Array, Date64Array, StringArray, Time32MillisecondArray,
    342
            Time32SecondArray, Time64MicrosecondArray, Time64NanosecondArray,
    343
            TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray,
    344
            TimestampSecondArray,
    345
        };
    346
        use arrow::datatypes::{DataType, Field, TimeUnit};
    347
        use chrono::{NaiveDateTime, Timelike};
    348
        use datafusion_common::config::ConfigOptions;
    349
        use datafusion_common::ScalarValue;
    350
        use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
    351
        use std::sync::Arc;
    352
    353
        #[test]
    354
        fn test_array_array() {
    355
            let array_array_data = vec![(
    356
                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
    357
                StringArray::from(vec!["%Y::%m::%d", "%Y::%m::%d %S::%M::%H %f"]),
    358
                StringArray::from(vec!["2020::09::01", "2020::09::02 00::00::00 000000000"]),
    359
            )];
    360
    361
            for (value, format, expected) in array_array_data {
    362
                let batch_len = value.len();
    363
                let value_data_type = value.data_type().clone();
    364
                let format_data_type = format.data_type().clone();
    365
    366
                let args = datafusion_expr::ScalarFunctionArgs {
    367
                    args: vec![
    368
                        ColumnarValue::Array(value),
    369
                        ColumnarValue::Array(Arc::new(format) as ArrayRef),
    370
                    ],
    371
                    arg_fields: vec![
    372
                        Field::new("a", value_data_type, true).into(),
    373
                        Field::new("b", format_data_type, true).into(),
    374
                    ],
    375
                    number_rows: batch_len,
    376
                    return_field: Field::new("f", DataType::Utf8, true).into(),
    377
                    config_options: Arc::clone(&Arc::new(ConfigOptions::default())),
    378
                };
    379
                let result = ToCharFunc::new()
    380
                    .invoke_with_args(args)
    381
                    .expect("that to_char parsed values without error");
    382
    383
                if let ColumnarValue::Array(result) = result {
    384
                    assert_eq!(result.len(), 2);
    385
                    assert_eq!(&expected as &dyn Array, result.as_ref());
    386
                } else {
    387
                    panic!("Expected an array value")
    388
                }
    389
            }
    390
        }
    391
    392
        #[test]
    393
        fn test_to_char() {
    394
            let date = "2020-01-02T03:04:05"
    395
                .parse::<NaiveDateTime>()
    396
                .unwrap()
    397
                .with_nanosecond(12345)
    398
                .unwrap();
    399
            let date2 = "2026-07-08T09:10:11"
    400
                .parse::<NaiveDateTime>()
    401
                .unwrap()
    402
                .with_nanosecond(56789)
    403
                .unwrap();
    404
    405
            let scalar_data = vec![
    406
                (
    407
                    ScalarValue::Date32(Some(18506)),
    408
                    ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
    409
                    "2020::09::01".to_string(),
    410
                ),
    411
                (
    412
                    ScalarValue::Date32(Some(18506)),
    413
                    ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
    414
                    "2020::09::01 00::00::00 000000000".to_string(),
    415
                ),
    416
                (
    417
                    ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
    418
                    ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
    419
                    "2020::01::02".to_string(),
    420
                ),
    421
                (
    422
                    ScalarValue::Time32Second(Some(31851)),
    423
                    ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
    424
                    "08-50-51".to_string(),
    425
                ),
    426
                (
    427
                    ScalarValue::Time32Millisecond(Some(18506000)),
    428
                    ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
    429
                    "05-08-26".to_string(),
    430
                ),
    431
                (
    432
                    ScalarValue::Time64Microsecond(Some(12344567000)),
    433
                    ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
    434
                    "03-25-44 567000000".to_string(),
    435
                ),
    436
                (
    437
                    ScalarValue::Time64Nanosecond(Some(12344567890000)),
    438
                    ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
    439
                    "03-25-44 567890000".to_string(),
    440
                ),
    441
                (
    442
                    ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
    443
                    ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
    444
                    "2020::01::02 05::04::03".to_string(),
    445
                ),
    446
                (
    447
                    ScalarValue::TimestampMillisecond(
    448
                        Some(date.and_utc().timestamp_millis()),
    449
                        None,
    450
                    ),
    451
                    ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
    452
                    "2020::01::02 05::04::03".to_string(),
    453
                ),
    454
                (
    455
                    ScalarValue::TimestampMicrosecond(
    456
                        Some(date.and_utc().timestamp_micros()),
    457
                        None,
    458
                    ),
    459
                    ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
    460
                    "2020::01::02 05::04::03 000012000".to_string(),
    461
                ),
    462
                (
    463
                    ScalarValue::TimestampNanosecond(
    464
                        Some(date.and_utc().timestamp_nanos_opt().unwrap()),
    465
                        None,
    466
                    ),
    467
                    ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
    468
                    "2020::01::02 05::04::03 000012345".to_string(),
    469
                ),
    470
            ];
    471
    472
            for (value, format, expected) in scalar_data {
    473
                let arg_fields = vec![
    474
                    Field::new("a", value.data_type(), false).into(),
    475
                    Field::new("a", format.data_type(), false).into(),
    476
                ];
    477
                let args = datafusion_expr::ScalarFunctionArgs {
    478
                    args: vec![ColumnarValue::Scalar(value), ColumnarValue::Scalar(format)],
    479
                    arg_fields,
    480
                    number_rows: 1,
    481
                    return_field: Field::new("f", DataType::Utf8, true).into(),
    482
                    config_options: Arc::new(ConfigOptions::default()),
    483
                };
    484
                let result = ToCharFunc::new()
    485
                    .invoke_with_args(args)
    486
                    .expect("that to_char parsed values without error");
    487
    488
                if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
    489
                    assert_eq!(expected, date.unwrap());
    490
                } else {
    491
                    panic!("Expected a scalar value")
    492
                }
    493
            }
    494
    495
            let scalar_array_data = vec![
    496
                (
    497
                    ScalarValue::Date32(Some(18506)),
    498
                    StringArray::from(vec!["%Y::%m::%d".to_string()]),
    499
                    "2020::09::01".to_string(),
    500
                ),
    501
                (
    502
                    ScalarValue::Date32(Some(18506)),
    503
                    StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
    504
                    "2020::09::01 00::00::00 000000000".to_string(),
    505
                ),
    506
                (
    507
                    ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
    508
                    StringArray::from(vec!["%Y::%m::%d".to_string()]),
    509
                    "2020::01::02".to_string(),
    510
                ),
    511
                (
    512
                    ScalarValue::Time32Second(Some(31851)),
    513
                    StringArray::from(vec!["%H-%M-%S".to_string()]),
    514
                    "08-50-51".to_string(),
    515
                ),
    516
                (
    517
                    ScalarValue::Time32Millisecond(Some(18506000)),
    518
                    StringArray::from(vec!["%H-%M-%S".to_string()]),
    519
                    "05-08-26".to_string(),
    520
                ),
    521
                (
    522
                    ScalarValue::Time64Microsecond(Some(12344567000)),
    523
                    StringArray::from(vec!["%H-%M-%S %f".to_string()]),
    524
                    "03-25-44 567000000".to_string(),
    525
                ),
    526
                (
    527
                    ScalarValue::Time64Nanosecond(Some(12344567890000)),
    528
                    StringArray::from(vec!["%H-%M-%S %f".to_string()]),
    529
                    "03-25-44 567890000".to_string(),
    530
                ),
    531
                (
    532
                    ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
    533
                    StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
    534
                    "2020::01::02 05::04::03".to_string(),
    535
                ),
    536
                (
    537
                    ScalarValue::TimestampMillisecond(
    538
                        Some(date.and_utc().timestamp_millis()),
    539
                        None,
    540
                    ),
    541
                    StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
    542
                    "2020::01::02 05::04::03".to_string(),
    543
                ),
    544
                (
    545
                    ScalarValue::TimestampMicrosecond(
    546
                        Some(date.and_utc().timestamp_micros()),
    547
                        None,
    548
                    ),
    549
                    StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
    550
                    "2020::01::02 05::04::03 000012000".to_string(),
    551
                ),
    552
                (
    553
                    ScalarValue::TimestampNanosecond(
    554
                        Some(date.and_utc().timestamp_nanos_opt().unwrap()),
    555
                        None,
    556
                    ),
    557
                    StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
    558
                    "2020::01::02 05::04::03 000012345".to_string(),
    559
                ),
    560
            ];
    561
    562
            for (value, format, expected) in scalar_array_data {
    563
                let batch_len = format.len();
    564
                let arg_fields = vec![
    565
                    Field::new("a", value.data_type(), false).into(),
    566
                    Field::new("a", format.data_type().to_owned(), false).into(),
    567
                ];
    568
                let args = datafusion_expr::ScalarFunctionArgs {
    569
                    args: vec![
    570
                        ColumnarValue::Scalar(value),
    571
                        ColumnarValue::Array(Arc::new(format) as ArrayRef),
    572
                    ],
    573
                    arg_fields,
    574
                    number_rows: batch_len,
    575
                    return_field: Field::new("f", DataType::Utf8, true).into(),
    576
                    config_options: Arc::new(ConfigOptions::default()),
    577
                };
    578
                let result = ToCharFunc::new()
    579
                    .invoke_with_args(args)
    580
                    .expect("that to_char parsed values without error");
    581
    582
                if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
    583
                    assert_eq!(expected, date.unwrap());
    584
                } else {
    585
                    panic!("Expected a scalar value")
    586
                }
    587
            }
    588
    589
            let array_scalar_data = vec![
    590
                (
    591
                    Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
    592
                    ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
    593
                    StringArray::from(vec!["2020::09::01", "2020::09::02"]),
    594
                ),
    595
                (
    596
                    Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
    597
                    ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
    598
                    StringArray::from(vec![
    599
                        "2020::09::01 00::00::00 000000000",
    600
                        "2020::09::02 00::00::00 000000000",
    601
                    ]),
    602
                ),
    603
                (
    604
                    Arc::new(Date64Array::from(vec![
    605
                        date.and_utc().timestamp_millis(),
    606
                        date2.and_utc().timestamp_millis(),
    607
                    ])) as ArrayRef,
    608
                    ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
    609
                    StringArray::from(vec!["2020::01::02", "2026::07::08"]),
    610
                ),
    611
            ];
    612
    613
            let array_array_data = vec![
    614
                (
    615
                    Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
    616
                    StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
    617
                    StringArray::from(vec!["2020::09::01", "02::09::2020"]),
    618
                ),
    619
                (
    620
                    Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
    621
                    StringArray::from(vec![
    622
                        "%Y::%m::%d %S::%M::%H %f",
    623
                        "%Y::%m::%d %S::%M::%H %f",
    624
                    ]),
    625
                    StringArray::from(vec![
    626
                        "2020::09::01 00::00::00 000000000",
    627
                        "2020::09::02 00::00::00 000000000",
    628
                    ]),
    629
                ),
    630
                (
    631
                    Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
    632
                    StringArray::from(vec!["%Y::%m::%d", "%Y::%m::%d %S::%M::%H %f"]),
    633
                    StringArray::from(vec![
    634
                        "2020::09::01",
    635
                        "2020::09::02 00::00::00 000000000",
    636
                    ]),
    637
                ),
    638
                (
    639
                    Arc::new(Date64Array::from(vec![
    640
                        date.and_utc().timestamp_millis(),
    641
                        date2.and_utc().timestamp_millis(),
    642
                    ])) as ArrayRef,
    643
                    StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
    644
                    StringArray::from(vec!["2020::01::02", "08::07::2026"]),
    645
                ),
    646
                (
    647
                    Arc::new(Time32MillisecondArray::from(vec![1850600, 1860700]))
    648
                        as ArrayRef,
    649
                    StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
    650
                    StringArray::from(vec!["00:30:50", "00::31::00"]),
    651
                ),
    652
                (
    653
                    Arc::new(Time32SecondArray::from(vec![18506, 18507])) as ArrayRef,
    654
                    StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
    655
                    StringArray::from(vec!["05:08:26", "05::08::27"]),
    656
                ),
    657
                (
    658
                    Arc::new(Time64MicrosecondArray::from(vec![12344567000, 22244567000]))
    659
                        as ArrayRef,
    660
                    StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
    661
                    StringArray::from(vec!["03:25:44", "06::10::44"]),
    662
                ),
    663
                (
    664
                    Arc::new(Time64NanosecondArray::from(vec![
    665
                        1234456789000,
    666
                        2224456789000,
    667
                    ])) as ArrayRef,
    668
                    StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
    669
                    StringArray::from(vec!["00:20:34", "00::37::04"]),
    670
                ),
    671
                (
    672
                    Arc::new(TimestampSecondArray::from(vec![
    673
                        date.and_utc().timestamp(),
    674
                        date2.and_utc().timestamp(),
    675
                    ])) as ArrayRef,
    676
                    StringArray::from(vec!["%Y::%m::%d %S::%M::%H", "%d::%m::%Y %S-%M-%H"]),
    677
                    StringArray::from(vec![
    678
                        "2020::01::02 05::04::03",
    679
                        "08::07::2026 11-10-09",
    680
                    ]),
    681
                ),
    682
                (
    683
                    Arc::new(TimestampMillisecondArray::from(vec![
    684
                        date.and_utc().timestamp_millis(),
    685
                        date2.and_utc().timestamp_millis(),
    686
                    ])) as ArrayRef,
    687
                    StringArray::from(vec![
    688
                        "%Y::%m::%d %S::%M::%H %f",
    689
                        "%d::%m::%Y %S-%M-%H %f",
    690
                    ]),
    691
                    StringArray::from(vec![
    692
                        "2020::01::02 05::04::03 000000000",
    693
                        "08::07::2026 11-10-09 000000000",
    694
                    ]),
    695
                ),
    696
                (
    697
                    Arc::new(TimestampMicrosecondArray::from(vec![
    698
                        date.and_utc().timestamp_micros(),
    699
                        date2.and_utc().timestamp_micros(),
    700
                    ])) as ArrayRef,
    701
                    StringArray::from(vec![
    702
                        "%Y::%m::%d %S::%M::%H %f",
    703
                        "%d::%m::%Y %S-%M-%H %f",
    704
                    ]),
    705
                    StringArray::from(vec![
    706
                        "2020::01::02 05::04::03 000012000",
    707
                        "08::07::2026 11-10-09 000056000",
    708
                    ]),
    709
                ),
    710
                (
    711
                    Arc::new(TimestampNanosecondArray::from(vec![
    712
                        date.and_utc().timestamp_nanos_opt().unwrap(),
    713
                        date2.and_utc().timestamp_nanos_opt().unwrap(),
    714
                    ])) as ArrayRef,
    715
                    StringArray::from(vec![
    716
                        "%Y::%m::%d %S::%M::%H %f",
    717
                        "%d::%m::%Y %S-%M-%H %f",
    718
                    ]),
    719
                    StringArray::from(vec![
    720
                        "2020::01::02 05::04::03 000012345",
    721
                        "08::07::2026 11-10-09 000056789",
    722
                    ]),
    723
                ),
    724
            ];
    725
    726
            for (value, format, expected) in array_scalar_data {
    727
                let batch_len = value.len();
    728
                let arg_fields = vec![
    729
                    Field::new("a", value.data_type().clone(), false).into(),
    730
                    Field::new("a", format.data_type(), false).into(),
    731
                ];
    732
                let args = datafusion_expr::ScalarFunctionArgs {
    733
                    args: vec![
    734
                        ColumnarValue::Array(value as ArrayRef),
    735
                        ColumnarValue::Scalar(format),
    736
                    ],
    737
                    arg_fields,
    738
                    number_rows: batch_len,
    739
                    return_field: Field::new("f", DataType::Utf8, true).into(),
    740
                    config_options: Arc::new(ConfigOptions::default()),
    741
                };
    742
                let result = ToCharFunc::new()
    743
                    .invoke_with_args(args)
    744
                    .expect("that to_char parsed values without error");
    745
    746
                if let ColumnarValue::Array(result) = result {
    747
                    assert_eq!(result.len(), 2);
    748
                    assert_eq!(&expected as &dyn Array, result.as_ref());
    749
                } else {
    750
                    panic!("Expected an array value")
    751
                }
    752
            }
    753
    754
            for (value, format, expected) in array_array_data {
    755
                let batch_len = value.len();
    756
                let arg_fields = vec![
    757
                    Field::new("a", value.data_type().clone(), false).into(),
    758
                    Field::new("a", format.data_type().clone(), false).into(),
    759
                ];
    760
                let args = datafusion_expr::ScalarFunctionArgs {
    761
                    args: vec![
    762
                        ColumnarValue::Array(value),
    763
                        ColumnarValue::Array(Arc::new(format) as ArrayRef),
    764
                    ],
    765
                    arg_fields,
    766
                    number_rows: batch_len,
    767
                    return_field: Field::new("f", DataType::Utf8, true).into(),
    768
                    config_options: Arc::new(ConfigOptions::default()),
    769
                };
    770
                let result = ToCharFunc::new()
    771
                    .invoke_with_args(args)
    772
                    .expect("that to_char parsed values without error");
    773
    774
                if let ColumnarValue::Array(result) = result {
    775
                    assert_eq!(result.len(), 2);
    776
                    assert_eq!(&expected as &dyn Array, result.as_ref());
    777
                } else {
    778
                    panic!("Expected an array value")
    779
                }
    780
            }
    781
    782
            //
    783
            // Fallible test cases
    784
            //
    785
    786
            // invalid number of arguments
    787
            let arg_field = Field::new("a", DataType::Int32, true).into();
    788
            let args = datafusion_expr::ScalarFunctionArgs {
    789
                args: vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
    790
                arg_fields: vec![arg_field],
    791
                number_rows: 1,
    792
                return_field: Field::new("f", DataType::Utf8, true).into(),
    793
                config_options: Arc::new(ConfigOptions::default()),
    794
            };
    795
            let result = ToCharFunc::new().invoke_with_args(args);
    796
            assert_eq!(
    797
                result.err().unwrap().strip_backtrace(),
    798
                "Execution error: to_char function requires 2 arguments, got 1"
    799
            );
    800
    801
            // invalid type
    802
            let arg_fields = vec![
    803
                Field::new("a", DataType::Utf8, true).into(),
    804
                Field::new("a", DataType::Timestamp(TimeUnit::Nanosecond, None), true).into(),
    805
            ];
    806
            let args = datafusion_expr::ScalarFunctionArgs {
    807
                args: vec![
    808
                    ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
    809
                    ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
    810
                ],
    811
                arg_fields,
    812
                number_rows: 1,
    813
                return_field: Field::new("f", DataType::Utf8, true).into(),
    814
                config_options: Arc::new(ConfigOptions::default()),
    815
            };
    816
            let result = ToCharFunc::new().invoke_with_args(args);
    817
            assert_eq!(
    818
                result.err().unwrap().strip_backtrace(),
    819
                "Execution error: Format for `to_char` must be non-null Utf8, received Timestamp(Nanosecond, None)"
    820
            );
    821
        }
    822
    }