datafusion_functions/datetime/
to_time.rs1use crate::datetime::common::*;
19use arrow::array::cast::AsArray;
20use arrow::array::temporal_conversions::time_to_time64ns;
21use arrow::array::types::Time64NanosecondType;
22use arrow::array::{Array, PrimitiveArray, StringArrayType};
23use arrow::datatypes::DataType;
24use arrow::datatypes::DataType::*;
25use chrono::format::{Item, Parsed, StrftimeItems, parse};
26use datafusion_common::{Result, ScalarValue, exec_err};
27use datafusion_expr::{
28 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
29 Volatility,
30};
31use datafusion_macros::user_doc;
32use std::sync::Arc;
33
34const DEFAULT_TIME_FORMATS: &[&str] = &[
36 "%H:%M:%S%.f", "%H:%M:%S", "%H:%M", ];
40
41#[user_doc(
42 doc_section(label = "Time and Date Functions"),
43 description = r"Converts a value to a time (`HH:MM:SS.nnnnnnnnn`).
44Supports strings and timestamps as input.
45Strings are parsed as `HH:MM:SS`, `HH:MM:SS.nnnnnnnnn`, or `HH:MM` if no [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html)s are provided.
46Timestamps will have the time portion extracted.
47Returns the corresponding time.
48
49Note: `to_time` returns Time64(Nanosecond), which represents the time of day in nanoseconds since midnight.",
50 syntax_example = "to_time(expression[, format1, ..., format_n])",
51 sql_example = r#"```sql
52> select to_time('12:30:45');
53+---------------------------+
54| to_time(Utf8("12:30:45")) |
55+---------------------------+
56| 12:30:45 |
57+---------------------------+
58> select to_time('12-30-45', '%H-%M-%S');
59+--------------------------------------------+
60| to_time(Utf8("12-30-45"),Utf8("%H-%M-%S")) |
61+--------------------------------------------+
62| 12:30:45 |
63+--------------------------------------------+
64> select to_time('2024-01-15 14:30:45'::timestamp);
65+--------------------------------------------------+
66| to_time(Utf8("2024-01-15 14:30:45")) |
67+--------------------------------------------------+
68| 14:30:45 |
69+--------------------------------------------------+
70```
71
72Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
73"#,
74 standard_argument(name = "expression", prefix = "String or Timestamp"),
75 argument(
76 name = "format_n",
77 description = r"Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression. Formats will be tried in the order
78 they appear with the first successful one being returned. If none of the formats successfully parse the expression
79 an error will be returned."
80 )
81)]
82#[derive(Debug, PartialEq, Eq, Hash)]
83pub struct ToTimeFunc {
84 signature: Signature,
85}
86
87impl Default for ToTimeFunc {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl ToTimeFunc {
94 pub fn new() -> Self {
95 Self {
96 signature: Signature::variadic_any(Volatility::Immutable),
97 }
98 }
99}
100
101impl ScalarUDFImpl for ToTimeFunc {
102 fn name(&self) -> &str {
103 "to_time"
104 }
105
106 fn signature(&self) -> &Signature {
107 &self.signature
108 }
109
110 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
111 Ok(Time64(arrow::datatypes::TimeUnit::Nanosecond))
112 }
113
114 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
115 let args = args.args;
116 if args.is_empty() {
117 return exec_err!("to_time function requires 1 or more arguments, got 0");
118 }
119
120 if args.len() > 1 {
122 validate_data_types(&args, "to_time")?;
123 }
124
125 match args[0].data_type() {
126 Utf8View | LargeUtf8 | Utf8 => string_to_time(&args),
127 Null => Ok(ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(None))),
128 Timestamp(_, _) => timestamp_to_time(&args[0]),
130 other => {
131 exec_err!("Unsupported data type {} for function to_time", other)
132 }
133 }
134 }
135
136 fn documentation(&self) -> Option<&Documentation> {
137 self.doc()
138 }
139}
140
141fn string_to_time(args: &[ColumnarValue]) -> Result<ColumnarValue> {
143 let formats = collect_formats(args)?;
144 let formats = compile_formats(&formats);
145
146 match &args[0] {
147 ColumnarValue::Scalar(ScalarValue::Utf8(s))
148 | ColumnarValue::Scalar(ScalarValue::LargeUtf8(s))
149 | ColumnarValue::Scalar(ScalarValue::Utf8View(s)) => {
150 let result = s
151 .as_ref()
152 .map(|s| parse_time_with_formats(s, &formats))
153 .transpose()?;
154 Ok(ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(result)))
155 }
156 ColumnarValue::Array(array) => {
157 let result = match array.data_type() {
158 Utf8 => parse_time_array(&array.as_string::<i32>(), &formats)?,
159 LargeUtf8 => parse_time_array(&array.as_string::<i64>(), &formats)?,
160 Utf8View => parse_time_array(&array.as_string_view(), &formats)?,
161 other => return exec_err!("Unsupported type for to_time: {other}"),
162 };
163 Ok(ColumnarValue::Array(Arc::new(result)))
164 }
165 other => exec_err!("Unsupported argument for to_time: {other:?}"),
166 }
167}
168
169fn collect_formats(args: &[ColumnarValue]) -> Result<Vec<&str>> {
171 if args.len() <= 1 {
172 return Ok(DEFAULT_TIME_FORMATS.to_vec());
173 }
174
175 let mut formats = Vec::with_capacity(args.len() - 1);
176 for (i, arg) in args[1..].iter().enumerate() {
177 match arg {
178 ColumnarValue::Scalar(ScalarValue::Utf8(Some(s)))
179 | ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(s)))
180 | ColumnarValue::Scalar(ScalarValue::Utf8View(Some(s))) => {
181 formats.push(s.as_str());
182 }
183 ColumnarValue::Scalar(ScalarValue::Utf8(None))
184 | ColumnarValue::Scalar(ScalarValue::LargeUtf8(None))
185 | ColumnarValue::Scalar(ScalarValue::Utf8View(None)) => {
186 }
188 ColumnarValue::Array(_) => {
189 return exec_err!(
190 "to_time format argument {} must be a scalar, not an array",
191 i + 2 );
193 }
194 other => {
195 return exec_err!(
196 "to_time format argument {} has unsupported type: {:?}",
197 i + 2,
198 other.data_type()
199 );
200 }
201 }
202 }
203 Ok(formats)
204}
205
206fn timestamp_to_time(arg: &ColumnarValue) -> Result<ColumnarValue> {
208 arg.cast_to(&Time64(arrow::datatypes::TimeUnit::Nanosecond), None)
209}
210
211struct CompiledTimeFormat<'a> {
212 source: &'a str,
213 items: Vec<Item<'a>>,
214}
215
216fn compile_formats<'a>(formats: &[&'a str]) -> Vec<CompiledTimeFormat<'a>> {
217 formats
218 .iter()
219 .map(|source| CompiledTimeFormat {
220 source,
221 items: StrftimeItems::new(source).collect(),
222 })
223 .collect()
224}
225
226fn parse_time_array<'a, A: StringArrayType<'a>>(
228 array: &A,
229 formats: &[CompiledTimeFormat<'_>],
230) -> Result<PrimitiveArray<Time64NanosecondType>> {
231 let mut values = Vec::with_capacity(array.len());
232 for i in 0..array.len() {
233 if array.is_null(i) {
234 values.push(0);
235 } else {
236 values.push(parse_time_with_formats(array.value(i), formats)?);
237 }
238 }
239 Ok(PrimitiveArray::new(values.into(), array.nulls().cloned()))
240}
241
242fn parse_time_with_formats(s: &str, formats: &[CompiledTimeFormat<'_>]) -> Result<i64> {
244 for format in formats {
245 let mut parsed = Parsed::new();
246 if parse(&mut parsed, s, format.items.iter()).is_ok()
247 && let Ok(time) = parsed.to_naive_time()
248 {
249 return Ok(time_to_time64ns(time));
250 }
251 }
252 exec_err!(
253 "Error parsing '{}' as time. Tried formats: {:?}",
254 s,
255 formats
256 .iter()
257 .map(|format| format.source)
258 .collect::<Vec<_>>()
259 )
260}