Skip to main content

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::{Result, ScalarValue, internal_err};
25use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyContext};
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: &SimplifyContext,
100    ) -> Result<ExprSimplifyResult> {
101        let Some(now_ts) = info.query_execution_start_time() else {
102            return Ok(ExprSimplifyResult::Original(args));
103        };
104
105        // Try to get timezone from config and convert to local time
106        let nano = info
107            .config_options()
108            .execution
109            .time_zone
110            .as_ref()
111            .and_then(|tz| tz.parse::<Tz>().ok())
112            .map_or_else(
113                || datetime_to_time_nanos(&now_ts),
114                |tz| {
115                    let local_now = tz.from_utc_datetime(&now_ts.naive_utc());
116                    datetime_to_time_nanos(&local_now)
117                },
118            );
119
120        Ok(ExprSimplifyResult::Simplified(Expr::Literal(
121            ScalarValue::Time64Nanosecond(nano),
122            None,
123        )))
124    }
125
126    fn documentation(&self) -> Option<&Documentation> {
127        self.doc()
128    }
129}
130
131// Helper function for conversion of datetime to a timestamp.
132fn datetime_to_time_nanos<Tz: TimeZone>(dt: &chrono::DateTime<Tz>) -> Option<i64> {
133    let hour = dt.hour() as i64;
134    let minute = dt.minute() as i64;
135    let second = dt.second() as i64;
136    let nanosecond = dt.nanosecond() as i64;
137    Some((hour * 3600 + minute * 60 + second) * 1_000_000_000 + nanosecond)
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use chrono::{DateTime, Utc};
144    use datafusion_common::config::ConfigOptions;
145    use datafusion_common::{DFSchema, ScalarValue};
146    use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyContext};
147    use std::sync::Arc;
148
149    fn set_session_timezone_env(tz: &str, start_time: DateTime<Utc>) -> SimplifyContext {
150        let mut config = ConfigOptions::default();
151        config.execution.time_zone = if tz.is_empty() {
152            None
153        } else {
154            Some(tz.to_string())
155        };
156        let schema = Arc::new(DFSchema::empty());
157        SimplifyContext::default()
158            .with_schema(schema)
159            .with_config_options(Arc::new(config))
160            .with_query_execution_start_time(Some(start_time))
161    }
162
163    #[test]
164    fn test_current_time_timezone_offset() {
165        // Use a fixed start time for consistent testing
166        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 12, 0, 0).unwrap();
167
168        // Test with UTC+05:00
169        let info_plus_5 = set_session_timezone_env("+05:00", start_time);
170        let result_plus_5 = CurrentTimeFunc::new()
171            .simplify(vec![], &info_plus_5)
172            .unwrap();
173
174        // Test with UTC-05:00
175        let info_minus_5 = set_session_timezone_env("-05:00", start_time);
176        let result_minus_5 = CurrentTimeFunc::new()
177            .simplify(vec![], &info_minus_5)
178            .unwrap();
179
180        // Extract nanoseconds from results
181        let nanos_plus_5 = match result_plus_5 {
182            ExprSimplifyResult::Simplified(Expr::Literal(
183                ScalarValue::Time64Nanosecond(Some(n)),
184                _,
185            )) => n,
186            _ => panic!("Expected Time64Nanosecond literal"),
187        };
188
189        let nanos_minus_5 = match result_minus_5 {
190            ExprSimplifyResult::Simplified(Expr::Literal(
191                ScalarValue::Time64Nanosecond(Some(n)),
192                _,
193            )) => n,
194            _ => panic!("Expected Time64Nanosecond literal"),
195        };
196
197        // Calculate the difference: UTC+05:00 should be 10 hours ahead of UTC-05:00
198        let difference = nanos_plus_5 - nanos_minus_5;
199
200        // 10 hours in nanoseconds
201        let expected_offset = 10i64 * 3600 * 1_000_000_000;
202
203        assert_eq!(
204            difference, expected_offset,
205            "Expected 10-hour offset difference in nanoseconds between UTC+05:00 and UTC-05:00"
206        );
207    }
208}