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