datafusion_functions/datetime/
current_time.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 arrow::array::timezone::Tz;
19use arrow::datatypes::DataType;
20use arrow::datatypes::DataType::Time64;
21use arrow::datatypes::TimeUnit::Nanosecond;
22use chrono::TimeZone;
23use chrono::Timelike;
24use datafusion_common::{internal_err, Result, ScalarValue};
25use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyInfo};
26use datafusion_expr::{
27    ColumnarValue, Documentation, Expr, ScalarUDFImpl, Signature, Volatility,
28};
29use datafusion_macros::user_doc;
30use std::any::Any;
31
32#[user_doc(
33    doc_section(label = "Time and Date Functions"),
34    description = r#"
35Returns the current time in the session time zone.
36
37The `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
39The 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)]
46pub struct CurrentTimeFunc {
47    signature: Signature,
48}
49
50impl Default for CurrentTimeFunc {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl CurrentTimeFunc {
57    pub fn new() -> Self {
58        Self {
59            signature: Signature::nullary(Volatility::Stable),
60        }
61    }
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.
70impl ScalarUDFImpl for CurrentTimeFunc {
71    fn as_any(&self) -> &dyn Any {
72        self
73    }
74
75    fn name(&self) -> &str {
76        "current_time"
77    }
78
79    fn signature(&self) -> &Signature {
80        &self.signature
81    }
82
83    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
84        Ok(Time64(Nanosecond))
85    }
86
87    fn invoke_with_args(
88        &self,
89        _args: datafusion_expr::ScalarFunctionArgs,
90    ) -> Result<ColumnarValue> {
91        internal_err!(
92            "invoke should not be called on a simplified current_time() function"
93        )
94    }
95
96    fn simplify(
97        &self,
98        _args: Vec<Expr>,
99        info: &dyn SimplifyInfo,
100    ) -> Result<ExprSimplifyResult> {
101        let now_ts = info.execution_props().query_execution_start_time;
102
103        // Try to get timezone from config and convert to local time
104        let nano = info
105            .execution_props()
106            .config_options()
107            .and_then(|config| {
108                config
109                    .execution
110                    .time_zone
111                    .as_ref()
112                    .map(|tz| tz.parse::<Tz>().ok())
113            })
114            .flatten()
115            .map_or_else(
116                || datetime_to_time_nanos(&now_ts),
117                |tz| {
118                    let local_now = tz.from_utc_datetime(&now_ts.naive_utc());
119                    datetime_to_time_nanos(&local_now)
120                },
121            );
122
123        Ok(ExprSimplifyResult::Simplified(Expr::Literal(
124            ScalarValue::Time64Nanosecond(nano),
125            None,
126        )))
127    }
128
129    fn documentation(&self) -> Option<&Documentation> {
130        self.doc()
131    }
132}
133
134// Helper function for conversion of datetime to a timestamp.
135fn datetime_to_time_nanos<Tz: TimeZone>(dt: &chrono::DateTime<Tz>) -> Option<i64> {
136    let hour = dt.hour() as i64;
137    let minute = dt.minute() as i64;
138    let second = dt.second() as i64;
139    let nanosecond = dt.nanosecond() as i64;
140    Some((hour * 3600 + minute * 60 + second) * 1_000_000_000 + nanosecond)
141}
142
143#[cfg(test)]
144mod 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}