## 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/current_time.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 arrow::array::timezone::Tz;
    19
    use arrow::datatypes::DataType;
    20
    use arrow::datatypes::DataType::Time64;
    21
    use arrow::datatypes::TimeUnit::Nanosecond;
    22
    use chrono::TimeZone;
    23
    use chrono::Timelike;
    24
    use datafusion_common::{internal_err, Result, ScalarValue};
    25
    use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyInfo};
    26
    use datafusion_expr::{
    27
        ColumnarValue, Documentation, Expr, ScalarUDFImpl, Signature, Volatility,
    28
    };
    29
    use datafusion_macros::user_doc;
    30
    use std::any::Any;
    31
    32
    #[user_doc(
    33
        doc_section(label = "Time and Date Functions"),
    34
        description = r#"
    35
    Returns the current time in the session time zone.
    36
    37
    The `current_time()` return value is determined at query time and will return the same time, no matter when in the query plan the function executes.
    38
    39
    The session time zone can be set using the statement 'SET datafusion.execution.time_zone = desired time zone'. The time zone can be a value like +00:00, 'Europe/London' etc.
    40
    "#,
    41
        syntax_example = r#"current_time()
    42
        (optional) SET datafusion.execution.time_zone = '+00:00';
    43
        SELECT current_time();"#
    44
    )]
    45
    #[derive(Debug, PartialEq, Eq, Hash)]
    46
    pub struct CurrentTimeFunc {
    47
        signature: Signature,
    48
    }
    49
    50
    impl Default for CurrentTimeFunc {
    51
    0
        fn default() -> Self {
    52
    0
            Self::new()
    53
    0
        }
    54
    }
    55
    56
    impl CurrentTimeFunc {
    57
    0
        pub fn new() -> Self {
    58
    0
            Self {
    59
    0
                signature: Signature::nullary(Volatility::Stable),
    60
    0
            }
    61
    0
        }
    62
    }
    63
    64
    /// Create an implementation of `current_time()` that always returns the
    65
    /// specified current time.
    66
    ///
    67
    /// The semantics of `current_time()` require it to return the same value
    68
    /// wherever it appears within a single statement. This value is
    69
    /// chosen during planning time.
    70
    impl ScalarUDFImpl for CurrentTimeFunc {
    71
    0
        fn as_any(&self) -> &dyn Any {
    72
    0
            self
    73
    0
        }
    74
    75
    0
        fn name(&self) -> &str {
    76
    0
            "current_time"
    77
    0
        }
    78
    79
    0
        fn signature(&self) -> &Signature {
    80
    0
            &self.signature
    81
    0
        }
    82
    83
    0
        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
    84
    0
            Ok(Time64(Nanosecond))
    85
    0
        }
    86
    87
    0
        fn invoke_with_args(
    88
    0
            &self,
    89
    0
            _args: datafusion_expr::ScalarFunctionArgs,
    90
    0
        ) -> Result<ColumnarValue> {
    91
    0
            internal_err!(
    92
    0
                "invoke should not be called on a simplified current_time() function"
    93
            )
    94
    0
        }
    95
    96
    0
        fn simplify(
    97
    0
            &self,
    98
    0
            _args: Vec<Expr>,
    99
    0
            info: &dyn SimplifyInfo,
    100
    0
        ) -> Result<ExprSimplifyResult> {
    101
    0
            let now_ts = info.execution_props().query_execution_start_time;
    102
    103
            // Try to get timezone from config and convert to local time
    104
    0
            let nano = info
    105
    0
                .execution_props()
    106
    0
                .config_options()
    107
    0
                .and_then(|config| {
    108
    0
                    config
    109
    0
                        .execution
    110
    0
                        .time_zone
    111
    0
                        .as_ref()
    112
    0
                        .map(|tz| tz.parse::<Tz>().ok())
    113
    0
                })
    114
    0
                .flatten()
    115
    0
                .map_or_else(
    116
    0
                    || datetime_to_time_nanos(&now_ts),
    117
    0
                    |tz| {
    118
    0
                        let local_now = tz.from_utc_datetime(&now_ts.naive_utc());
    119
    0
                        datetime_to_time_nanos(&local_now)
    120
    0
                    },
    121
                );
    122
    123
    0
            Ok(ExprSimplifyResult::Simplified(Expr::Literal(
    124
    0
                ScalarValue::Time64Nanosecond(nano),
    125
    0
                None,
    126
    0
            )))
    127
    0
        }
    128
    129
    0
        fn documentation(&self) -> Option<&Documentation> {
    130
    0
            self.doc()
    131
    0
        }
    132
    }
    133
    134
    // Helper function for conversion of datetime to a timestamp.
    135
    0
    fn datetime_to_time_nanos<Tz: TimeZone>(dt: &chrono::DateTime<Tz>) -> Option<i64> {
    136
    0
        let hour = dt.hour() as i64;
    137
    0
        let minute = dt.minute() as i64;
    138
    0
        let second = dt.second() as i64;
    139
    0
        let nanosecond = dt.nanosecond() as i64;
    140
    0
        Some((hour * 3600 + minute * 60 + second) * 1_000_000_000 + nanosecond)
    141
    0
    }
    142
    143
    #[cfg(test)]
    144
    mod tests {
    145
        use super::*;
    146
        use arrow::datatypes::{DataType, TimeUnit::Nanosecond};
    147
        use chrono::{DateTime, Utc};
    148
        use datafusion_common::{Result, ScalarValue};
    149
        use datafusion_expr::execution_props::ExecutionProps;
    150
        use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyInfo};
    151
        use std::sync::Arc;
    152
    153
        struct MockSimplifyInfo {
    154
            execution_props: ExecutionProps,
    155
        }
    156
    157
        impl SimplifyInfo for MockSimplifyInfo {
    158
            fn is_boolean_type(&self, _expr: &Expr) -> Result<bool> {
    159
                Ok(false)
    160
            }
    161
    162
            fn nullable(&self, _expr: &Expr) -> Result<bool> {
    163
                Ok(true)
    164
            }
    165
    166
            fn execution_props(&self) -> &ExecutionProps {
    167
                &self.execution_props
    168
            }
    169
    170
            fn get_data_type(&self, _expr: &Expr) -> Result<DataType> {
    171
                Ok(Time64(Nanosecond))
    172
            }
    173
        }
    174
    175
        fn set_session_timezone_env(tz: &str, start_time: DateTime<Utc>) -> MockSimplifyInfo {
    176
            let mut config = datafusion_common::config::ConfigOptions::default();
    177
            config.execution.time_zone = if tz.is_empty() {
    178
                None
    179
            } else {
    180
                Some(tz.to_string())
    181
            };
    182
            let mut execution_props =
    183
                ExecutionProps::new().with_query_execution_start_time(start_time);
    184
            execution_props.config_options = Some(Arc::new(config));
    185
            MockSimplifyInfo { execution_props }
    186
        }
    187
    188
        #[test]
    189
        fn test_current_time_timezone_offset() {
    190
            // Use a fixed start time for consistent testing
    191
            let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 12, 0, 0).unwrap();
    192
    193
            // Test with UTC+05:00
    194
            let info_plus_5 = set_session_timezone_env("+05:00", start_time);
    195
            let result_plus_5 = CurrentTimeFunc::new()
    196
                .simplify(vec![], &info_plus_5)
    197
                .unwrap();
    198
    199
            // Test with UTC-05:00
    200
            let info_minus_5 = set_session_timezone_env("-05:00", start_time);
    201
            let result_minus_5 = CurrentTimeFunc::new()
    202
                .simplify(vec![], &info_minus_5)
    203
                .unwrap();
    204
    205
            // Extract nanoseconds from results
    206
            let nanos_plus_5 = match result_plus_5 {
    207
                ExprSimplifyResult::Simplified(Expr::Literal(
    208
                    ScalarValue::Time64Nanosecond(Some(n)),
    209
                    _,
    210
                )) => n,
    211
                _ => panic!("Expected Time64Nanosecond literal"),
    212
            };
    213
    214
            let nanos_minus_5 = match result_minus_5 {
    215
                ExprSimplifyResult::Simplified(Expr::Literal(
    216
                    ScalarValue::Time64Nanosecond(Some(n)),
    217
                    _,
    218
                )) => n,
    219
                _ => panic!("Expected Time64Nanosecond literal"),
    220
            };
    221
    222
            // Calculate the difference: UTC+05:00 should be 10 hours ahead of UTC-05:00
    223
            let difference = nanos_plus_5 - nanos_minus_5;
    224
    225
            // 10 hours in nanoseconds
    226
            let expected_offset = 10i64 * 3600 * 1_000_000_000;
    227
    228
            assert_eq!(difference, expected_offset, "Expected 10-hour offset difference in nanoseconds between UTC+05:00 and UTC-05:00");
    229
        }
    230
    }