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