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