1use std::sync::Arc;
19
20use crate::datetime::common::*;
21use arrow::array::timezone::Tz;
22use arrow::array::{
23 Array, Decimal128Array, Float16Array, Float32Array, Float64Array,
24 TimestampNanosecondArray,
25};
26use arrow::datatypes::DataType::*;
27use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
28use arrow::datatypes::{
29 ArrowTimestampType, DECIMAL128_MAX_PRECISION, DataType, TimestampMicrosecondType,
30 TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType,
31};
32use datafusion_common::config::ConfigOptions;
33use datafusion_common::{Result, ScalarType, ScalarValue, exec_datafusion_err, exec_err};
34use datafusion_expr::{
35 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl,
36 Signature, Volatility,
37};
38use datafusion_macros::user_doc;
39
40#[user_doc(
41 doc_section(label = "Time and Date Functions"),
42 description = r#"
43Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000000<TZ>`) in the session time zone. Supports strings,
44integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
45if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided.
46Strings that parse without a time zone are treated as if they are in the
47session time zone, or UTC if no session time zone is set.
48Integers, unsigned integers, and doubles are interpreted as seconds since the unix epoch (`1970-01-01T00:00:00Z`).
49
50Note: `to_timestamp` returns `Timestamp(ns, TimeZone)` where the time zone is the session time zone. The supported range
51for integer input is between`-9223372037` and `9223372036`. Supported range for string input is between
52`1677-09-21T00:12:44.0` and `2262-04-11T23:47:16.0`. Please use `to_timestamp_seconds`
53for the input outside of supported bounds.
54
55The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
56The time zone can be a value like +00:00, 'Europe/London' etc.
57"#,
58 syntax_example = "to_timestamp(expression[, ..., format_n])",
59 sql_example = r#"```sql
60> select to_timestamp('2023-01-31T09:26:56.123456789-05:00');
61+-----------------------------------------------------------+
62| to_timestamp(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
63+-----------------------------------------------------------+
64| 2023-01-31T14:26:56.123456789 |
65+-----------------------------------------------------------+
66> select to_timestamp('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
67+--------------------------------------------------------------------------------------------------------+
68| to_timestamp(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
69+--------------------------------------------------------------------------------------------------------+
70| 2023-05-17T03:59:00.123456789 |
71+--------------------------------------------------------------------------------------------------------+
72```
73Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
74"#,
75 argument(
76 name = "expression",
77 description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
78 ),
79 argument(
80 name = "format_n",
81 description = r#"
82Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
83Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
84parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
85Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
86only supported at the end of the string preceded by a space.
87"#
88 )
89)]
90#[derive(Debug, PartialEq, Eq, Hash)]
91pub struct ToTimestampFunc {
92 signature: Signature,
93 timezone: Option<Arc<str>>,
94}
95
96#[user_doc(
97 doc_section(label = "Time and Date Functions"),
98 description = r#"
99Converts a value to a timestamp (`YYYY-MM-DDT00:00:00<TZ>`) in the session time zone. Supports strings,
100integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
101if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided.
102Strings that parse without a time zone are treated as if they are in the
103session time zone, or UTC if no session time zone is set.
104Integers, unsigned integers, and doubles are interpreted as seconds since the unix epoch (`1970-01-01T00:00:00Z`).
105
106The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
107The time zone can be a value like +00:00, 'Europe/London' etc.
108"#,
109 syntax_example = "to_timestamp_seconds(expression[, ..., format_n])",
110 sql_example = r#"```sql
111> select to_timestamp_seconds('2023-01-31T09:26:56.123456789-05:00');
112+-------------------------------------------------------------------+
113| to_timestamp_seconds(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
114+-------------------------------------------------------------------+
115| 2023-01-31T14:26:56 |
116+-------------------------------------------------------------------+
117> select to_timestamp_seconds('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
118+----------------------------------------------------------------------------------------------------------------+
119| to_timestamp_seconds(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
120+----------------------------------------------------------------------------------------------------------------+
121| 2023-05-17T03:59:00 |
122+----------------------------------------------------------------------------------------------------------------+
123```
124Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
125"#,
126 argument(
127 name = "expression",
128 description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
129 ),
130 argument(
131 name = "format_n",
132 description = r#"
133Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
134Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
135parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
136Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
137only supported at the end of the string preceded by a space.
138"#
139 )
140)]
141#[derive(Debug, PartialEq, Eq, Hash)]
142pub struct ToTimestampSecondsFunc {
143 signature: Signature,
144 timezone: Option<Arc<str>>,
145}
146
147#[user_doc(
148 doc_section(label = "Time and Date Functions"),
149 description = r#"
150Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000<TZ>`) in the session time zone. Supports strings,
151integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
152if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided.
153Strings that parse without a time zone are treated as if they are in the
154session time zone, or UTC if no session time zone is set.
155Integers, unsigned integers, and doubles are interpreted as milliseconds since the unix epoch (`1970-01-01T00:00:00Z`).
156
157The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
158The time zone can be a value like +00:00, 'Europe/London' etc.
159"#,
160 syntax_example = "to_timestamp_millis(expression[, ..., format_n])",
161 sql_example = r#"```sql
162> select to_timestamp_millis('2023-01-31T09:26:56.123456789-05:00');
163+------------------------------------------------------------------+
164| to_timestamp_millis(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
165+------------------------------------------------------------------+
166| 2023-01-31T14:26:56.123 |
167+------------------------------------------------------------------+
168> select to_timestamp_millis('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
169+---------------------------------------------------------------------------------------------------------------+
170| to_timestamp_millis(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
171+---------------------------------------------------------------------------------------------------------------+
172| 2023-05-17T03:59:00.123 |
173+---------------------------------------------------------------------------------------------------------------+
174```
175Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
176"#,
177 argument(
178 name = "expression",
179 description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
180 ),
181 argument(
182 name = "format_n",
183 description = r#"
184Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
185Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
186parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
187Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
188only supported at the end of the string preceded by a space.
189"#
190 )
191)]
192#[derive(Debug, PartialEq, Eq, Hash)]
193pub struct ToTimestampMillisFunc {
194 signature: Signature,
195 timezone: Option<Arc<str>>,
196}
197
198#[user_doc(
199 doc_section(label = "Time and Date Functions"),
200 description = r#"
201Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000000<TZ>`) in the session time zone. Supports strings,
202integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
203if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided.
204Strings that parse without a time zone are treated as if they are in the
205session time zone, or UTC if no session time zone is set.
206Integers, unsigned integers, and doubles are interpreted as microseconds since the unix epoch (`1970-01-01T00:00:00Z`).
207
208The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
209The time zone can be a value like +00:00, 'Europe/London' etc.
210"#,
211 syntax_example = "to_timestamp_micros(expression[, ..., format_n])",
212 sql_example = r#"```sql
213> select to_timestamp_micros('2023-01-31T09:26:56.123456789-05:00');
214+------------------------------------------------------------------+
215| to_timestamp_micros(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
216+------------------------------------------------------------------+
217| 2023-01-31T14:26:56.123456 |
218+------------------------------------------------------------------+
219> select to_timestamp_micros('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
220+---------------------------------------------------------------------------------------------------------------+
221| to_timestamp_micros(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
222+---------------------------------------------------------------------------------------------------------------+
223| 2023-05-17T03:59:00.123456 |
224+---------------------------------------------------------------------------------------------------------------+
225```
226Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
227"#,
228 argument(
229 name = "expression",
230 description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
231 ),
232 argument(
233 name = "format_n",
234 description = r#"
235Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
236Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
237parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
238Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
239only supported at the end of the string preceded by a space.
240"#
241 )
242)]
243#[derive(Debug, PartialEq, Eq, Hash)]
244pub struct ToTimestampMicrosFunc {
245 signature: Signature,
246 timezone: Option<Arc<str>>,
247}
248
249#[user_doc(
250 doc_section(label = "Time and Date Functions"),
251 description = r#"
252Converts a value to a timestamp (`YYYY-MM-DDT00:00:00.000000000<TZ>`) in the session time zone. Supports strings,
253integer, unsigned integer, and double types as input. Strings are parsed as RFC3339 (e.g. '2023-07-20T05:44:00')
254if no [Chrono formats](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) are provided.
255Strings that parse without a time zone are treated as if they are in the
256session time zone. Integers, unsigned integers, and doubles are interpreted as nanoseconds since the unix epoch (`1970-01-01T00:00:00Z`).
257
258The session time zone can be set using the statement `SET TIMEZONE = 'desired time zone'`.
259The time zone can be a value like +00:00, 'Europe/London' etc.
260"#,
261 syntax_example = "to_timestamp_nanos(expression[, ..., format_n])",
262 sql_example = r#"```sql
263> select to_timestamp_nanos('2023-01-31T09:26:56.123456789-05:00');
264+-----------------------------------------------------------------+
265| to_timestamp_nanos(Utf8("2023-01-31T09:26:56.123456789-05:00")) |
266+-----------------------------------------------------------------+
267| 2023-01-31T14:26:56.123456789 |
268+-----------------------------------------------------------------+
269> select to_timestamp_nanos('03:59:00.123456789 05-17-2023', '%c', '%+', '%H:%M:%S%.f %m-%d-%Y');
270+--------------------------------------------------------------------------------------------------------------+
271| to_timestamp_nanos(Utf8("03:59:00.123456789 05-17-2023"),Utf8("%c"),Utf8("%+"),Utf8("%H:%M:%S%.f %m-%d-%Y")) |
272+--------------------------------------------------------------------------------------------------------------+
273| 2023-05-17T03:59:00.123456789 |
274+---------------------------------------------------------------------------------------------------------------+
275```
276Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/date_time.rs)
277"#,
278 argument(
279 name = "expression",
280 description = "Expression to operate on. Can be a constant, column, or function, and any combination of arithmetic operators."
281 ),
282 argument(
283 name = "format_n",
284 description = r#"
285Optional [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) strings to use to parse the expression.
286Formats will be tried in the order they appear with the first successful one being returned. If none of the formats successfully
287parse the expression an error will be returned. NULL formats are skipped. If every format is NULL the result is NULL.
288Note: parsing of named timezones (e.g. 'America/New_York') using %Z is
289only supported at the end of the string preceded by a space.
290"#
291 )
292)]
293#[derive(Debug, PartialEq, Eq, Hash)]
294pub struct ToTimestampNanosFunc {
295 signature: Signature,
296 timezone: Option<Arc<str>>,
297}
298
299macro_rules! impl_to_timestamp_constructors {
302 ($func:ty) => {
303 impl Default for $func {
304 fn default() -> Self {
305 Self::new_with_config(&ConfigOptions::default())
306 }
307 }
308
309 impl $func {
310 #[deprecated(since = "52.0.0", note = "use `new_with_config` instead")]
311 pub fn new() -> Self {
317 Self::new_with_config(&ConfigOptions::default())
318 }
319
320 pub fn new_with_config(config: &ConfigOptions) -> Self {
321 Self {
322 signature: Signature::variadic_any(Volatility::Immutable),
323 timezone: config
324 .execution
325 .time_zone
326 .as_ref()
327 .map(|tz| Arc::from(tz.as_str())),
328 }
329 }
330 }
331 };
332}
333
334impl_to_timestamp_constructors!(ToTimestampFunc);
335impl_to_timestamp_constructors!(ToTimestampSecondsFunc);
336impl_to_timestamp_constructors!(ToTimestampMillisFunc);
337impl_to_timestamp_constructors!(ToTimestampMicrosFunc);
338impl_to_timestamp_constructors!(ToTimestampNanosFunc);
339
340fn decimal_to_nanoseconds(value: i128, scale: i8) -> Result<i64> {
341 let nanos_exponent = 9_i16 - scale as i16;
342 let power = 10_i128
343 .checked_pow(nanos_exponent.unsigned_abs() as u32)
344 .ok_or_else(|| {
345 exec_datafusion_err!(
346 "Decimal value {value} with scale {scale} overflows timestamp nanoseconds"
347 )
348 })?;
349
350 let timestamp_nanos = if nanos_exponent >= 0 {
351 value.checked_mul(power).ok_or_else(|| {
352 exec_datafusion_err!(
353 "Decimal value {value} with scale {scale} overflows timestamp nanoseconds"
354 )
355 })?
356 } else {
357 value / power
358 };
359
360 i64::try_from(timestamp_nanos).map_err(|_| {
361 exec_datafusion_err!(
362 "Decimal value {value} with scale {scale} overflows timestamp nanoseconds"
363 )
364 })
365}
366
367fn decimal128_to_timestamp_nanos(
368 arg: &ColumnarValue,
369 tz: Option<Arc<str>>,
370) -> Result<ColumnarValue> {
371 match arg {
372 ColumnarValue::Scalar(ScalarValue::Decimal128(Some(value), _, scale)) => {
373 let timestamp_nanos = decimal_to_nanoseconds(*value, *scale)?;
374 Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
375 Some(timestamp_nanos),
376 tz,
377 )))
378 }
379 ColumnarValue::Scalar(ScalarValue::Decimal128(None, _, _)) => Ok(
380 ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(None, tz)),
381 ),
382 ColumnarValue::Array(arr) => {
383 let decimal_arr = downcast_arg!(arr, Decimal128Array);
384 let scale = decimal_arr.scale();
385 let result: TimestampNanosecondArray = decimal_arr
386 .iter()
387 .map(|v| v.map(|val| decimal_to_nanoseconds(val, scale)).transpose())
388 .collect::<Result<_>>()?;
389 let result = result.with_timezone_opt(tz);
390 Ok(ColumnarValue::Array(Arc::new(result)))
391 }
392 _ => exec_err!("Invalid Decimal128 value for to_timestamp"),
393 }
394}
395
396macro_rules! impl_with_updated_config {
404 () => {
405 fn with_updated_config(&self, config: &ConfigOptions) -> Option<ScalarUDF> {
406 Some(Self::new_with_config(config).into())
407 }
408 };
409}
410
411impl ScalarUDFImpl for ToTimestampFunc {
412 fn name(&self) -> &str {
413 "to_timestamp"
414 }
415
416 fn signature(&self) -> &Signature {
417 &self.signature
418 }
419
420 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
421 Ok(Timestamp(Nanosecond, self.timezone.clone()))
422 }
423
424 impl_with_updated_config!();
425
426 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
427 let ScalarFunctionArgs { args, .. } = args;
428
429 if args.is_empty() {
430 return exec_err!(
431 "to_timestamp function requires 1 or more arguments, got {}",
432 args.len()
433 );
434 }
435
436 if args.len() > 1 {
438 validate_data_types(&args, "to_timestamp")?;
439 }
440
441 let tz = self.timezone.clone();
442
443 match args[0].data_type() {
444 Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64 => args[0]
445 .cast_to(&Timestamp(Second, None), None)?
446 .cast_to(&Timestamp(Nanosecond, tz), None),
447 Null | Timestamp(_, _) => args[0].cast_to(&Timestamp(Nanosecond, tz), None),
448 Float16 => match &args[0] {
449 ColumnarValue::Scalar(ScalarValue::Float16(value)) => {
450 let timestamp_nanos =
451 value.map(|v| (v.to_f64() * 1_000_000_000.0) as i64);
452 Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
453 timestamp_nanos,
454 tz,
455 )))
456 }
457 ColumnarValue::Array(arr) => {
458 let f16_arr = downcast_arg!(arr, Float16Array);
459 let result: TimestampNanosecondArray =
460 f16_arr.unary(|x| (x.to_f64() * 1_000_000_000.0) as i64);
461 Ok(ColumnarValue::Array(Arc::new(result.with_timezone_opt(tz))))
462 }
463 _ => exec_err!("Invalid Float16 value for to_timestamp"),
464 },
465 Float32 => match &args[0] {
466 ColumnarValue::Scalar(ScalarValue::Float32(value)) => {
467 let timestamp_nanos =
468 value.map(|v| (v as f64 * 1_000_000_000.0) as i64);
469 Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
470 timestamp_nanos,
471 tz,
472 )))
473 }
474 ColumnarValue::Array(arr) => {
475 let f32_arr = downcast_arg!(arr, Float32Array);
476 let result: TimestampNanosecondArray =
477 f32_arr.unary(|x| (x as f64 * 1_000_000_000.0) as i64);
478 Ok(ColumnarValue::Array(Arc::new(result.with_timezone_opt(tz))))
479 }
480 _ => exec_err!("Invalid Float32 value for to_timestamp"),
481 },
482 Float64 => match &args[0] {
483 ColumnarValue::Scalar(ScalarValue::Float64(value)) => {
484 let timestamp_nanos = value.map(|v| (v * 1_000_000_000.0) as i64);
485 Ok(ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
486 timestamp_nanos,
487 tz,
488 )))
489 }
490 ColumnarValue::Array(arr) => {
491 let f64_arr = downcast_arg!(arr, Float64Array);
492 let result: TimestampNanosecondArray =
493 f64_arr.unary(|x| (x * 1_000_000_000.0) as i64);
494 Ok(ColumnarValue::Array(Arc::new(result.with_timezone_opt(tz))))
495 }
496 _ => exec_err!("Invalid Float64 value for to_timestamp"),
497 },
498 Decimal32(_, _) | Decimal64(_, _) | Decimal256(_, _) => {
499 let arg =
500 args[0].cast_to(&Decimal128(DECIMAL128_MAX_PRECISION, 9), None)?;
501 decimal128_to_timestamp_nanos(&arg, tz)
502 }
503 Decimal128(_, _) => decimal128_to_timestamp_nanos(&args[0], tz),
504 Utf8View | LargeUtf8 | Utf8 => {
505 to_timestamp_impl::<TimestampNanosecondType>(&args, "to_timestamp", &tz)
506 }
507 other => {
508 exec_err!("Unsupported data type {other} for function to_timestamp")
509 }
510 }
511 }
512
513 fn documentation(&self) -> Option<&Documentation> {
514 self.doc()
515 }
516}
517
518impl ScalarUDFImpl for ToTimestampSecondsFunc {
519 fn name(&self) -> &str {
520 "to_timestamp_seconds"
521 }
522
523 fn signature(&self) -> &Signature {
524 &self.signature
525 }
526
527 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
528 Ok(Timestamp(Second, self.timezone.clone()))
529 }
530
531 impl_with_updated_config!();
532
533 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
534 let ScalarFunctionArgs { args, .. } = args;
535
536 if args.is_empty() {
537 return exec_err!(
538 "to_timestamp_seconds function requires 1 or more arguments, got {}",
539 args.len()
540 );
541 }
542
543 if args.len() > 1 {
545 validate_data_types(&args, "to_timestamp")?;
546 }
547
548 let tz = self.timezone.clone();
549
550 match args[0].data_type() {
551 Null
552 | Int8
553 | Int16
554 | Int32
555 | Int64
556 | UInt8
557 | UInt16
558 | UInt32
559 | UInt64
560 | Timestamp(_, _)
561 | Decimal32(_, _)
562 | Decimal64(_, _)
563 | Decimal128(_, _)
564 | Decimal256(_, _) => args[0].cast_to(&Timestamp(Second, tz), None),
565 Float16 | Float32 | Float64 => args[0]
566 .cast_to(&Int64, None)?
567 .cast_to(&Timestamp(Second, tz), None),
568 Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampSecondType>(
569 &args,
570 "to_timestamp_seconds",
571 &self.timezone,
572 ),
573 other => {
574 exec_err!(
575 "Unsupported data type {} for function to_timestamp_seconds",
576 other
577 )
578 }
579 }
580 }
581
582 fn documentation(&self) -> Option<&Documentation> {
583 self.doc()
584 }
585}
586
587impl ScalarUDFImpl for ToTimestampMillisFunc {
588 fn name(&self) -> &str {
589 "to_timestamp_millis"
590 }
591
592 fn signature(&self) -> &Signature {
593 &self.signature
594 }
595
596 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
597 Ok(Timestamp(Millisecond, self.timezone.clone()))
598 }
599
600 impl_with_updated_config!();
601
602 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
603 let ScalarFunctionArgs { args, .. } = args;
604
605 if args.is_empty() {
606 return exec_err!(
607 "to_timestamp_millis function requires 1 or more arguments, got {}",
608 args.len()
609 );
610 }
611
612 if args.len() > 1 {
614 validate_data_types(&args, "to_timestamp")?;
615 }
616
617 match args[0].data_type() {
618 Null
619 | Int8
620 | Int16
621 | Int32
622 | Int64
623 | UInt8
624 | UInt16
625 | UInt32
626 | UInt64
627 | Timestamp(_, _)
628 | Decimal32(_, _)
629 | Decimal64(_, _)
630 | Decimal128(_, _)
631 | Decimal256(_, _) => {
632 args[0].cast_to(&Timestamp(Millisecond, self.timezone.clone()), None)
633 }
634 Float16 | Float32 | Float64 => args[0]
635 .cast_to(&Int64, None)?
636 .cast_to(&Timestamp(Millisecond, self.timezone.clone()), None),
637 Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampMillisecondType>(
638 &args,
639 "to_timestamp_millis",
640 &self.timezone,
641 ),
642 other => {
643 exec_err!(
644 "Unsupported data type {} for function to_timestamp_millis",
645 other
646 )
647 }
648 }
649 }
650
651 fn documentation(&self) -> Option<&Documentation> {
652 self.doc()
653 }
654}
655
656impl ScalarUDFImpl for ToTimestampMicrosFunc {
657 fn name(&self) -> &str {
658 "to_timestamp_micros"
659 }
660
661 fn signature(&self) -> &Signature {
662 &self.signature
663 }
664
665 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
666 Ok(Timestamp(Microsecond, self.timezone.clone()))
667 }
668
669 impl_with_updated_config!();
670
671 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
672 let ScalarFunctionArgs { args, .. } = args;
673
674 if args.is_empty() {
675 return exec_err!(
676 "to_timestamp_micros function requires 1 or more arguments, got {}",
677 args.len()
678 );
679 }
680
681 if args.len() > 1 {
683 validate_data_types(&args, "to_timestamp")?;
684 }
685
686 match args[0].data_type() {
687 Null
688 | Int8
689 | Int16
690 | Int32
691 | Int64
692 | UInt8
693 | UInt16
694 | UInt32
695 | UInt64
696 | Timestamp(_, _)
697 | Decimal32(_, _)
698 | Decimal64(_, _)
699 | Decimal128(_, _)
700 | Decimal256(_, _) => {
701 args[0].cast_to(&Timestamp(Microsecond, self.timezone.clone()), None)
702 }
703 Float16 | Float32 | Float64 => args[0]
704 .cast_to(&Int64, None)?
705 .cast_to(&Timestamp(Microsecond, self.timezone.clone()), None),
706 Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampMicrosecondType>(
707 &args,
708 "to_timestamp_micros",
709 &self.timezone,
710 ),
711 other => {
712 exec_err!(
713 "Unsupported data type {} for function to_timestamp_micros",
714 other
715 )
716 }
717 }
718 }
719
720 fn documentation(&self) -> Option<&Documentation> {
721 self.doc()
722 }
723}
724
725impl ScalarUDFImpl for ToTimestampNanosFunc {
726 fn name(&self) -> &str {
727 "to_timestamp_nanos"
728 }
729
730 fn signature(&self) -> &Signature {
731 &self.signature
732 }
733
734 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
735 Ok(Timestamp(Nanosecond, self.timezone.clone()))
736 }
737
738 impl_with_updated_config!();
739
740 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
741 let ScalarFunctionArgs { args, .. } = args;
742
743 if args.is_empty() {
744 return exec_err!(
745 "to_timestamp_nanos function requires 1 or more arguments, got {}",
746 args.len()
747 );
748 }
749
750 if args.len() > 1 {
752 validate_data_types(&args, "to_timestamp")?;
753 }
754
755 match args[0].data_type() {
756 Null
757 | Int8
758 | Int16
759 | Int32
760 | Int64
761 | UInt8
762 | UInt16
763 | UInt32
764 | UInt64
765 | Timestamp(_, _)
766 | Decimal32(_, _)
767 | Decimal64(_, _)
768 | Decimal128(_, _)
769 | Decimal256(_, _) => {
770 args[0].cast_to(&Timestamp(Nanosecond, self.timezone.clone()), None)
771 }
772 Float16 | Float32 | Float64 => args[0]
773 .cast_to(&Int64, None)?
774 .cast_to(&Timestamp(Nanosecond, self.timezone.clone()), None),
775 Utf8View | LargeUtf8 | Utf8 => to_timestamp_impl::<TimestampNanosecondType>(
776 &args,
777 "to_timestamp_nanos",
778 &self.timezone,
779 ),
780 other => {
781 exec_err!(
782 "Unsupported data type {} for function to_timestamp_nanos",
783 other
784 )
785 }
786 }
787 }
788
789 fn documentation(&self) -> Option<&Documentation> {
790 self.doc()
791 }
792}
793
794fn to_timestamp_impl<T: ArrowTimestampType + ScalarType<i64>>(
795 args: &[ColumnarValue],
796 name: &str,
797 timezone: &Option<Arc<str>>,
798) -> Result<ColumnarValue> {
799 let factor = match T::UNIT {
800 Second => 1_000_000_000,
801 Millisecond => 1_000_000,
802 Microsecond => 1_000,
803 Nanosecond => 1,
804 };
805
806 let tz = match timezone.clone() {
807 Some(tz) => Some(tz.parse::<Tz>()?),
808 None => None,
809 };
810
811 match args.len() {
812 1 => handle::<T, _>(
813 args,
814 move |s| string_to_timestamp_nanos_with_timezone(&tz, s).map(|n| n / factor),
815 name,
816 &Timestamp(T::UNIT, timezone.clone()),
817 ),
818 n if n >= 2 => handle_multiple::<T, _, _>(
819 args,
820 move |s, format| {
821 string_to_timestamp_nanos_formatted_with_timezone(&tz, s, format)
822 },
823 |n| n / factor,
824 name,
825 &Timestamp(T::UNIT, timezone.clone()),
826 ),
827 _ => exec_err!("Unsupported 0 argument count for function {name}"),
828 }
829}
830
831#[cfg(test)]
832mod tests {
833
834 use arrow::array::types::Int64Type;
835 use arrow::array::{
836 Array, PrimitiveArray, TimestampMicrosecondArray, TimestampMillisecondArray,
837 TimestampNanosecondArray, TimestampSecondArray,
838 };
839 use arrow::array::{ArrayRef, Int64Array, StringBuilder};
840 use arrow::datatypes::{Field, TimeUnit};
841 use chrono::{DateTime, FixedOffset, Utc};
842 use datafusion_common::{DataFusionError, assert_contains};
843 use datafusion_expr::ScalarFunctionImplementation;
844
845 use super::*;
846
847 fn to_timestamp(args: &[ColumnarValue]) -> Result<ColumnarValue> {
848 let timezone: Option<Arc<str>> = Some("UTC".into());
849 to_timestamp_impl::<TimestampNanosecondType>(args, "to_timestamp", &timezone)
850 }
851
852 fn to_timestamp_millis(args: &[ColumnarValue]) -> Result<ColumnarValue> {
854 let timezone: Option<Arc<str>> = Some("UTC".into());
855 to_timestamp_impl::<TimestampMillisecondType>(
856 args,
857 "to_timestamp_millis",
858 &timezone,
859 )
860 }
861
862 fn to_timestamp_micros(args: &[ColumnarValue]) -> Result<ColumnarValue> {
864 let timezone: Option<Arc<str>> = Some("UTC".into());
865 to_timestamp_impl::<TimestampMicrosecondType>(
866 args,
867 "to_timestamp_micros",
868 &timezone,
869 )
870 }
871
872 fn to_timestamp_nanos(args: &[ColumnarValue]) -> Result<ColumnarValue> {
874 let timezone: Option<Arc<str>> = Some("UTC".into());
875 to_timestamp_impl::<TimestampNanosecondType>(
876 args,
877 "to_timestamp_nanos",
878 &timezone,
879 )
880 }
881
882 fn to_timestamp_seconds(args: &[ColumnarValue]) -> Result<ColumnarValue> {
884 let timezone: Option<Arc<str>> = Some("UTC".into());
885 to_timestamp_impl::<TimestampSecondType>(args, "to_timestamp_seconds", &timezone)
886 }
887
888 fn udfs_and_timeunit() -> Vec<(Box<dyn ScalarUDFImpl>, TimeUnit)> {
889 let udfs: Vec<(Box<dyn ScalarUDFImpl>, TimeUnit)> = vec![
890 (
891 Box::new(ToTimestampFunc::new_with_config(&ConfigOptions::default())),
892 Nanosecond,
893 ),
894 (
895 Box::new(ToTimestampSecondsFunc::new_with_config(
896 &ConfigOptions::default(),
897 )),
898 Second,
899 ),
900 (
901 Box::new(ToTimestampMillisFunc::new_with_config(
902 &ConfigOptions::default(),
903 )),
904 Millisecond,
905 ),
906 (
907 Box::new(ToTimestampMicrosFunc::new_with_config(
908 &ConfigOptions::default(),
909 )),
910 Microsecond,
911 ),
912 (
913 Box::new(ToTimestampNanosFunc::new_with_config(
914 &ConfigOptions::default(),
915 )),
916 Nanosecond,
917 ),
918 ];
919 udfs
920 }
921
922 fn validate_expected_error(
923 options: &mut ConfigOptions,
924 args: ScalarFunctionArgs,
925 expected_err: &str,
926 ) {
927 let udfs = udfs_and_timeunit();
928
929 for (udf, _) in udfs {
930 match udf
931 .with_updated_config(options)
932 .unwrap()
933 .invoke_with_args(args.clone())
934 {
935 Ok(_) => panic!("Expected error but got success"),
936 Err(e) => {
937 assert!(
938 e.to_string().contains(expected_err),
939 "Can not find expected error '{expected_err}'. Actual error '{e}'"
940 );
941 }
942 }
943 }
944 }
945
946 #[test]
947 fn to_timestamp_arrays_and_nulls() -> Result<()> {
948 let mut string_builder = StringBuilder::with_capacity(2, 1024);
951 let mut ts_builder = TimestampNanosecondArray::builder(2);
952
953 string_builder.append_value("2020-09-08T13:42:29.190855");
954 ts_builder.append_value(1599572549190855000);
955
956 string_builder.append_null();
957 ts_builder.append_null();
958 let expected_timestamps = &ts_builder.finish() as &dyn Array;
959
960 let string_array =
961 ColumnarValue::Array(Arc::new(string_builder.finish()) as ArrayRef);
962 let parsed_timestamps = to_timestamp(&[string_array])
963 .expect("that to_timestamp parsed values without error");
964 if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
965 assert_eq!(parsed_array.len(), 2);
966 assert_eq!(expected_timestamps, parsed_array.as_ref());
967 } else {
968 panic!("Expected a columnar array")
969 }
970 Ok(())
971 }
972
973 #[test]
974 fn to_timestamp_decimal128_overflow_returns_error() {
975 let value = "99999999999999999999999999999999999999"
976 .parse::<i128>()
977 .unwrap();
978 let err = decimal128_to_timestamp_nanos(
979 &ColumnarValue::Scalar(ScalarValue::Decimal128(Some(value), 38, 0)),
980 None,
981 )
982 .unwrap_err()
983 .to_string();
984
985 assert_contains!(err, "overflows timestamp nanoseconds");
986 }
987
988 #[test]
989 fn to_timestamp_decimal128_array_overflow_returns_error() {
990 let value = "99999999999999999999999999999999999999"
991 .parse::<i128>()
992 .unwrap();
993 let array = Decimal128Array::from(vec![Some(value)])
994 .with_precision_and_scale(38, 0)
995 .unwrap();
996 let err =
997 decimal128_to_timestamp_nanos(&ColumnarValue::Array(Arc::new(array)), None)
998 .unwrap_err()
999 .to_string();
1000
1001 assert_contains!(err, "overflows timestamp nanoseconds");
1002 }
1003
1004 #[test]
1005 fn to_timestamp_with_formats_arrays_and_nulls() -> Result<()> {
1006 let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1009 let mut format1_builder = StringBuilder::with_capacity(2, 1024);
1010 let mut format2_builder = StringBuilder::with_capacity(2, 1024);
1011 let mut format3_builder = StringBuilder::with_capacity(2, 1024);
1012 let mut ts_builder = TimestampNanosecondArray::builder(2);
1013
1014 date_string_builder.append_null();
1015 format1_builder.append_null();
1016 format2_builder.append_null();
1017 format3_builder.append_null();
1018 ts_builder.append_null();
1019
1020 date_string_builder.append_value("2020-09-08T13:42:29.19085Z");
1021 format1_builder.append_value("%s");
1022 format2_builder.append_value("%c");
1023 format3_builder.append_value("%+");
1024 ts_builder.append_value(1599572549190850000);
1025
1026 let expected_timestamps = &ts_builder.finish() as &dyn Array;
1027
1028 let string_array = [
1029 ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef),
1030 ColumnarValue::Array(Arc::new(format1_builder.finish()) as ArrayRef),
1031 ColumnarValue::Array(Arc::new(format2_builder.finish()) as ArrayRef),
1032 ColumnarValue::Array(Arc::new(format3_builder.finish()) as ArrayRef),
1033 ];
1034 let parsed_timestamps = to_timestamp(&string_array)
1035 .expect("that to_timestamp with format args parsed values without error");
1036 if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
1037 assert_eq!(parsed_array.len(), 2);
1038 assert_eq!(expected_timestamps, parsed_array.as_ref());
1039 } else {
1040 panic!("Expected a columnar array")
1041 }
1042 Ok(())
1043 }
1044
1045 #[test]
1046 fn to_timestamp_respects_execution_timezone() -> Result<()> {
1047 let udfs = udfs_and_timeunit();
1048
1049 let mut options = ConfigOptions::default();
1050 options.execution.time_zone = Some("-05:00".to_string());
1051
1052 let time_zone: Option<Arc<str>> = options
1053 .execution
1054 .time_zone
1055 .as_ref()
1056 .map(|tz| Arc::from(tz.as_str()));
1057
1058 for (udf, time_unit) in udfs {
1059 let field = Field::new("arg", Utf8, true).into();
1060
1061 let args = ScalarFunctionArgs {
1062 args: vec![ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1063 "2020-09-08T13:42:29".to_string(),
1064 )))],
1065 arg_fields: vec![field],
1066 number_rows: 1,
1067 return_field: Field::new(
1068 "f",
1069 Timestamp(time_unit, Some("-05:00".into())),
1070 true,
1071 )
1072 .into(),
1073 config_options: Arc::new(options.clone()),
1074 };
1075
1076 let result = udf
1077 .with_updated_config(&options.clone())
1078 .unwrap()
1079 .invoke_with_args(args)?;
1080 let result = match time_unit {
1081 Second => {
1082 let ColumnarValue::Scalar(ScalarValue::TimestampSecond(
1083 Some(value),
1084 tz,
1085 )) = result
1086 else {
1087 panic!("expected scalar timestamp");
1088 };
1089
1090 assert_eq!(tz, time_zone);
1091
1092 value
1093 }
1094 Millisecond => {
1095 let ColumnarValue::Scalar(ScalarValue::TimestampMillisecond(
1096 Some(value),
1097 tz,
1098 )) = result
1099 else {
1100 panic!("expected scalar timestamp");
1101 };
1102
1103 assert_eq!(tz, time_zone);
1104
1105 value
1106 }
1107 Microsecond => {
1108 let ColumnarValue::Scalar(ScalarValue::TimestampMicrosecond(
1109 Some(value),
1110 tz,
1111 )) = result
1112 else {
1113 panic!("expected scalar timestamp");
1114 };
1115
1116 assert_eq!(tz, time_zone);
1117
1118 value
1119 }
1120 Nanosecond => {
1121 let ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
1122 Some(value),
1123 tz,
1124 )) = result
1125 else {
1126 panic!("expected scalar timestamp");
1127 };
1128
1129 assert_eq!(tz, time_zone);
1130
1131 value
1132 }
1133 };
1134
1135 let scale = match time_unit {
1136 Second => 1_000_000_000,
1137 Millisecond => 1_000_000,
1138 Microsecond => 1_000,
1139 Nanosecond => 1,
1140 };
1141
1142 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
1143 let result = Some(
1144 DateTime::<Utc>::from_timestamp_nanos(result * scale)
1145 .with_timezone(&offset)
1146 .to_string(),
1147 );
1148
1149 assert_eq!(result, Some("2020-09-08 13:42:29 -05:00".to_string()));
1150 }
1151
1152 Ok(())
1153 }
1154
1155 #[test]
1156 fn to_timestamp_formats_respects_execution_timezone() -> Result<()> {
1157 let udfs = udfs_and_timeunit();
1158
1159 let mut options = ConfigOptions::default();
1160 options.execution.time_zone = Some("-05:00".to_string());
1161
1162 let time_zone: Option<Arc<str>> = options
1163 .execution
1164 .time_zone
1165 .as_ref()
1166 .map(|tz| Arc::from(tz.as_str()));
1167
1168 let expr_field = Field::new("arg", Utf8, true).into();
1169 let format_field: Arc<Field> = Field::new("fmt", Utf8, true).into();
1170
1171 for (udf, time_unit) in udfs {
1172 for (value, format, expected_str) in [
1173 (
1174 "2020-09-08 09:42:29 -05:00",
1175 "%Y-%m-%d %H:%M:%S %z",
1176 Some("2020-09-08 09:42:29 -05:00"),
1177 ),
1178 (
1179 "2020-09-08T13:42:29Z",
1180 "%+",
1181 Some("2020-09-08 08:42:29 -05:00"),
1182 ),
1183 (
1184 "2020-09-08 13:42:29 UTC",
1185 "%Y-%m-%d %H:%M:%S %Z",
1186 Some("2020-09-08 08:42:29 -05:00"),
1187 ),
1188 (
1189 "+0000 2024-01-01 12:00:00",
1190 "%z %Y-%m-%d %H:%M:%S",
1191 Some("2024-01-01 07:00:00 -05:00"),
1192 ),
1193 (
1194 "20200908134229+0100",
1195 "%Y%m%d%H%M%S%z",
1196 Some("2020-09-08 07:42:29 -05:00"),
1197 ),
1198 (
1199 "2020-09-08+0230 13:42",
1200 "%Y-%m-%d%z %H:%M",
1201 Some("2020-09-08 06:12:00 -05:00"),
1202 ),
1203 ] {
1204 let args = ScalarFunctionArgs {
1205 args: vec![
1206 ColumnarValue::Scalar(ScalarValue::Utf8(Some(value.to_string()))),
1207 ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1208 format.to_string(),
1209 ))),
1210 ],
1211 arg_fields: vec![Arc::clone(&expr_field), Arc::clone(&format_field)],
1212 number_rows: 1,
1213 return_field: Field::new(
1214 "f",
1215 Timestamp(time_unit, Some("-05:00".into())),
1216 true,
1217 )
1218 .into(),
1219 config_options: Arc::new(options.clone()),
1220 };
1221 let result = udf
1222 .with_updated_config(&options.clone())
1223 .unwrap()
1224 .invoke_with_args(args)?;
1225 let result = match time_unit {
1226 Second => {
1227 let ColumnarValue::Scalar(ScalarValue::TimestampSecond(
1228 Some(value),
1229 tz,
1230 )) = result
1231 else {
1232 panic!("expected scalar timestamp");
1233 };
1234
1235 assert_eq!(tz, time_zone);
1236
1237 value
1238 }
1239 Millisecond => {
1240 let ColumnarValue::Scalar(ScalarValue::TimestampMillisecond(
1241 Some(value),
1242 tz,
1243 )) = result
1244 else {
1245 panic!("expected scalar timestamp");
1246 };
1247
1248 assert_eq!(tz, time_zone);
1249
1250 value
1251 }
1252 Microsecond => {
1253 let ColumnarValue::Scalar(ScalarValue::TimestampMicrosecond(
1254 Some(value),
1255 tz,
1256 )) = result
1257 else {
1258 panic!("expected scalar timestamp");
1259 };
1260
1261 assert_eq!(tz, time_zone);
1262
1263 value
1264 }
1265 Nanosecond => {
1266 let ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(
1267 Some(value),
1268 tz,
1269 )) = result
1270 else {
1271 panic!("expected scalar timestamp");
1272 };
1273
1274 assert_eq!(tz, time_zone);
1275
1276 value
1277 }
1278 };
1279
1280 let scale = match time_unit {
1281 Second => 1_000_000_000,
1282 Millisecond => 1_000_000,
1283 Microsecond => 1_000,
1284 Nanosecond => 1,
1285 };
1286 let offset = FixedOffset::west_opt(5 * 3600).unwrap();
1287 let result = Some(
1288 DateTime::<Utc>::from_timestamp_nanos(result * scale)
1289 .with_timezone(&offset)
1290 .to_string(),
1291 );
1292
1293 assert_eq!(result, expected_str.map(|s| s.to_string()));
1294 }
1295 }
1296
1297 Ok(())
1298 }
1299
1300 #[test]
1301 fn to_timestamp_invalid_execution_timezone_behavior() -> Result<()> {
1302 let field: Arc<Field> = Field::new("arg", Utf8, true).into();
1303 let return_field: Arc<Field> =
1304 Field::new("f", Timestamp(Nanosecond, None), true).into();
1305
1306 let mut options = ConfigOptions::default();
1307 options.execution.time_zone = Some("Invalid/Timezone".to_string());
1308
1309 let args = ScalarFunctionArgs {
1310 args: vec![ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1311 "2020-09-08T13:42:29Z".to_string(),
1312 )))],
1313 arg_fields: vec![Arc::clone(&field)],
1314 number_rows: 1,
1315 return_field: Arc::clone(&return_field),
1316 config_options: Arc::new(options.clone()),
1317 };
1318
1319 let expected_err =
1320 "Invalid timezone \"Invalid/Timezone\": failed to parse timezone";
1321
1322 validate_expected_error(&mut options, args, expected_err);
1323
1324 Ok(())
1325 }
1326
1327 #[test]
1328 fn to_timestamp_formats_invalid_execution_timezone_behavior() -> Result<()> {
1329 let expr_field: Arc<Field> = Field::new("arg", Utf8, true).into();
1330 let format_field: Arc<Field> = Field::new("fmt", Utf8, true).into();
1331 let return_field: Arc<Field> =
1332 Field::new("f", Timestamp(Nanosecond, None), true).into();
1333
1334 let mut options = ConfigOptions::default();
1335 options.execution.time_zone = Some("Invalid/Timezone".to_string());
1336
1337 let expected_err =
1338 "Invalid timezone \"Invalid/Timezone\": failed to parse timezone";
1339
1340 let make_args = |value: &str, format: &str| ScalarFunctionArgs {
1341 args: vec![
1342 ColumnarValue::Scalar(ScalarValue::Utf8(Some(value.to_string()))),
1343 ColumnarValue::Scalar(ScalarValue::Utf8(Some(format.to_string()))),
1344 ],
1345 arg_fields: vec![Arc::clone(&expr_field), Arc::clone(&format_field)],
1346 number_rows: 1,
1347 return_field: Arc::clone(&return_field),
1348 config_options: Arc::new(options.clone()),
1349 };
1350
1351 for (value, format, _expected_str) in [
1352 (
1353 "2020-09-08 09:42:29 -05:00",
1354 "%Y-%m-%d %H:%M:%S %z",
1355 Some("2020-09-08 09:42:29 -05:00"),
1356 ),
1357 (
1358 "2020-09-08T13:42:29Z",
1359 "%+",
1360 Some("2020-09-08 08:42:29 -05:00"),
1361 ),
1362 (
1363 "2020-09-08 13:42:29 +0000",
1364 "%Y-%m-%d %H:%M:%S %z",
1365 Some("2020-09-08 08:42:29 -05:00"),
1366 ),
1367 (
1368 "+0000 2024-01-01 12:00:00",
1369 "%z %Y-%m-%d %H:%M:%S",
1370 Some("2024-01-01 07:00:00 -05:00"),
1371 ),
1372 (
1373 "20200908134229+0100",
1374 "%Y%m%d%H%M%S%z",
1375 Some("2020-09-08 07:42:29 -05:00"),
1376 ),
1377 (
1378 "2020-09-08+0230 13:42",
1379 "%Y-%m-%d%z %H:%M",
1380 Some("2020-09-08 06:12:00 -05:00"),
1381 ),
1382 ] {
1383 let args = make_args(value, format);
1384 validate_expected_error(&mut options.clone(), args, expected_err);
1385 }
1386
1387 let args = ScalarFunctionArgs {
1388 args: vec![
1389 ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1390 "2020-09-08T13:42:29".to_string(),
1391 ))),
1392 ColumnarValue::Scalar(ScalarValue::Utf8(Some(
1393 "%Y-%m-%dT%H:%M:%S".to_string(),
1394 ))),
1395 ],
1396 arg_fields: vec![Arc::clone(&expr_field), Arc::clone(&format_field)],
1397 number_rows: 1,
1398 return_field: Arc::clone(&return_field),
1399 config_options: Arc::new(options.clone()),
1400 };
1401
1402 validate_expected_error(&mut options.clone(), args, expected_err);
1403
1404 Ok(())
1405 }
1406
1407 #[test]
1408 fn to_timestamp_invalid_input_type() -> Result<()> {
1409 let mut builder = Int64Array::builder(1);
1413 builder.append_value(1);
1414 let int64array = ColumnarValue::Array(Arc::new(builder.finish()));
1415
1416 let expected_err =
1417 "Execution error: Unsupported data type Int64 for function to_timestamp";
1418 match to_timestamp(&[int64array]) {
1419 Ok(_) => panic!("Expected error but got success"),
1420 Err(e) => {
1421 assert!(
1422 e.to_string().contains(expected_err),
1423 "Can not find expected error '{expected_err}'. Actual error '{e}'"
1424 );
1425 }
1426 }
1427 Ok(())
1428 }
1429
1430 #[test]
1431 fn to_timestamp_with_formats_invalid_input_type() -> Result<()> {
1432 let mut builder = Int64Array::builder(1);
1436 builder.append_value(1);
1437 let int64array = [
1438 ColumnarValue::Array(Arc::new(builder.finish())),
1439 ColumnarValue::Array(Arc::new(builder.finish())),
1440 ];
1441
1442 let expected_err =
1443 "Execution error: Unsupported data type Int64 for function to_timestamp";
1444 match to_timestamp(&int64array) {
1445 Ok(_) => panic!("Expected error but got success"),
1446 Err(e) => {
1447 assert!(
1448 e.to_string().contains(expected_err),
1449 "Can not find expected error '{expected_err}'. Actual error '{e}'"
1450 );
1451 }
1452 }
1453 Ok(())
1454 }
1455
1456 #[test]
1457 fn to_timestamp_with_unparsable_data() -> Result<()> {
1458 let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1459
1460 date_string_builder.append_null();
1461
1462 date_string_builder.append_value("2020-09-08 - 13:42:29.19085Z");
1463
1464 let string_array =
1465 ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef);
1466
1467 let expected_err = "Arrow error: Parser error: Error parsing timestamp from '2020-09-08 - 13:42:29.19085Z': error parsing time";
1468 match to_timestamp(&[string_array]) {
1469 Ok(_) => panic!("Expected error but got success"),
1470 Err(e) => {
1471 assert!(
1472 e.to_string().contains(expected_err),
1473 "Can not find expected error '{expected_err}'. Actual error '{e}'"
1474 );
1475 }
1476 }
1477 Ok(())
1478 }
1479
1480 #[test]
1481 fn to_timestamp_with_invalid_tz() -> Result<()> {
1482 let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1483
1484 date_string_builder.append_null();
1485
1486 date_string_builder.append_value("2020-09-08T13:42:29ZZ");
1487
1488 let string_array =
1489 ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef);
1490
1491 let expected_err = "Arrow error: Parser error: Invalid timezone \"ZZ\": failed to parse timezone";
1492 match to_timestamp(&[string_array]) {
1493 Ok(_) => panic!("Expected error but got success"),
1494 Err(e) => {
1495 assert!(
1496 e.to_string().contains(expected_err),
1497 "Can not find expected error '{expected_err}'. Actual error '{e}'"
1498 );
1499 }
1500 }
1501 Ok(())
1502 }
1503
1504 #[test]
1505 fn to_timestamp_with_no_matching_formats() -> Result<()> {
1506 let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1507 let mut format1_builder = StringBuilder::with_capacity(2, 1024);
1508 let mut format2_builder = StringBuilder::with_capacity(2, 1024);
1509 let mut format3_builder = StringBuilder::with_capacity(2, 1024);
1510
1511 date_string_builder.append_null();
1512 format1_builder.append_null();
1513 format2_builder.append_null();
1514 format3_builder.append_null();
1515
1516 date_string_builder.append_value("2020-09-08T13:42:29.19085Z");
1517 format1_builder.append_value("%s");
1518 format2_builder.append_value("%c");
1519 format3_builder.append_value("%H:%M:%S");
1520
1521 let string_array = [
1522 ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef),
1523 ColumnarValue::Array(Arc::new(format1_builder.finish()) as ArrayRef),
1524 ColumnarValue::Array(Arc::new(format2_builder.finish()) as ArrayRef),
1525 ColumnarValue::Array(Arc::new(format3_builder.finish()) as ArrayRef),
1526 ];
1527
1528 let expected_err = "Execution error: Error parsing timestamp from '2020-09-08T13:42:29.19085Z' using format '%H:%M:%S': input contains invalid characters";
1529 match to_timestamp(&string_array) {
1530 Ok(_) => panic!("Expected error but got success"),
1531 Err(e) => {
1532 assert!(
1533 e.to_string().contains(expected_err),
1534 "Can not find expected error '{expected_err}'. Actual error '{e}'"
1535 );
1536 }
1537 }
1538 Ok(())
1539 }
1540
1541 #[test]
1542 fn string_to_timestamp_formatted() {
1543 assert_eq!(
1545 1599572549190855000,
1546 parse_timestamp_formatted("2020-09-08T13:42:29.190855+00:00", "%+").unwrap()
1547 );
1548 assert_eq!(
1549 1599572549190855000,
1550 parse_timestamp_formatted("2020-09-08T13:42:29.190855Z", "%+").unwrap()
1551 );
1552 assert_eq!(
1553 1599572549000000000,
1554 parse_timestamp_formatted("2020-09-08T13:42:29Z", "%+").unwrap()
1555 ); assert_eq!(
1557 1599590549190855000,
1558 parse_timestamp_formatted("2020-09-08T13:42:29.190855-05:00", "%+").unwrap()
1559 );
1560 assert_eq!(
1561 1599590549000000000,
1562 parse_timestamp_formatted("1599590549", "%s").unwrap()
1563 );
1564 assert_eq!(
1565 1599572549000000000,
1566 parse_timestamp_formatted("09-08-2020 13/42/29", "%m-%d-%Y %H/%M/%S")
1567 .unwrap()
1568 );
1569 assert_eq!(
1570 1642896000000000000,
1571 parse_timestamp_formatted("2022-01-23", "%Y-%m-%d").unwrap()
1572 );
1573 }
1574
1575 fn parse_timestamp_formatted(s: &str, format: &str) -> Result<i64, DataFusionError> {
1576 let result = string_to_timestamp_nanos_formatted_with_timezone(
1577 &Some("UTC".parse()?),
1578 s,
1579 format,
1580 );
1581 if let Err(e) = &result {
1582 eprintln!("Error parsing timestamp '{s}' using format '{format}': {e:?}");
1583 }
1584 result
1585 }
1586
1587 #[test]
1588 fn string_to_timestamp_formatted_invalid() {
1589 let cases = [
1591 ("", "%Y%m%d %H%M%S", "premature end of input"),
1592 ("SS", "%c", "premature end of input"),
1593 ("Wed, 18 Feb 2015 23:16:09 GMT", "", "trailing input"),
1594 (
1595 "Wed, 18 Feb 2015 23:16:09 GMT",
1596 "%XX",
1597 "input contains invalid characters",
1598 ),
1599 (
1600 "Wed, 18 Feb 2015 23:16:09 GMT",
1601 "%Y%m%d %H%M%S",
1602 "input contains invalid characters",
1603 ),
1604 ];
1605
1606 for (s, f, ctx) in cases {
1607 let expected = format!(
1608 "Execution error: Error parsing timestamp from '{s}' using format '{f}': {ctx}"
1609 );
1610 let actual = string_to_datetime_formatted(&Utc, s, f)
1611 .unwrap_err()
1612 .strip_backtrace();
1613 assert_eq!(actual, expected)
1614 }
1615 }
1616
1617 #[test]
1618 fn string_to_timestamp_invalid_arguments() {
1619 let cases = [
1621 ("", "%Y%m%d %H%M%S", "premature end of input"),
1622 ("SS", "%c", "premature end of input"),
1623 ("Wed, 18 Feb 2015 23:16:09 GMT", "", "trailing input"),
1624 (
1625 "Wed, 18 Feb 2015 23:16:09 GMT",
1626 "%XX",
1627 "input contains invalid characters",
1628 ),
1629 (
1630 "Wed, 18 Feb 2015 23:16:09 GMT",
1631 "%Y%m%d %H%M%S",
1632 "input contains invalid characters",
1633 ),
1634 ];
1635
1636 for (s, f, ctx) in cases {
1637 let expected = format!(
1638 "Execution error: Error parsing timestamp from '{s}' using format '{f}': {ctx}"
1639 );
1640 let actual = string_to_datetime_formatted(&Utc, s, f)
1641 .unwrap_err()
1642 .strip_backtrace();
1643 assert_eq!(actual, expected)
1644 }
1645 }
1646
1647 #[test]
1648 fn test_no_tz() {
1649 let udfs: Vec<Box<dyn ScalarUDFImpl>> = vec![
1650 Box::new(ToTimestampFunc::new_with_config(&ConfigOptions::default())),
1651 Box::new(ToTimestampSecondsFunc::new_with_config(
1652 &ConfigOptions::default(),
1653 )),
1654 Box::new(ToTimestampMillisFunc::new_with_config(
1655 &ConfigOptions::default(),
1656 )),
1657 Box::new(ToTimestampNanosFunc::new_with_config(
1658 &ConfigOptions::default(),
1659 )),
1660 Box::new(ToTimestampSecondsFunc::new_with_config(
1661 &ConfigOptions::default(),
1662 )),
1663 ];
1664
1665 let mut nanos_builder = TimestampNanosecondArray::builder(2);
1666 let mut millis_builder = TimestampMillisecondArray::builder(2);
1667 let mut micros_builder = TimestampMicrosecondArray::builder(2);
1668 let mut sec_builder = TimestampSecondArray::builder(2);
1669
1670 nanos_builder.append_value(1599572549190850000);
1671 millis_builder.append_value(1599572549190);
1672 micros_builder.append_value(1599572549190850);
1673 sec_builder.append_value(1599572549);
1674
1675 let nanos_timestamps =
1676 Arc::new(nanos_builder.finish().with_timezone("UTC")) as ArrayRef;
1677 let millis_timestamps =
1678 Arc::new(millis_builder.finish().with_timezone("UTC")) as ArrayRef;
1679 let micros_timestamps =
1680 Arc::new(micros_builder.finish().with_timezone("UTC")) as ArrayRef;
1681 let sec_timestamps =
1682 Arc::new(sec_builder.finish().with_timezone("UTC")) as ArrayRef;
1683
1684 let arrays = &[
1685 ColumnarValue::Array(Arc::clone(&nanos_timestamps)),
1686 ColumnarValue::Array(Arc::clone(&millis_timestamps)),
1687 ColumnarValue::Array(Arc::clone(µs_timestamps)),
1688 ColumnarValue::Array(Arc::clone(&sec_timestamps)),
1689 ];
1690
1691 for udf in &udfs {
1692 for array in arrays {
1693 let rt = udf.return_type(&[array.data_type()]).unwrap();
1694 let arg_field = Field::new("arg", array.data_type().clone(), true).into();
1695 assert!(matches!(rt, Timestamp(_, None)));
1696 let args = ScalarFunctionArgs {
1697 args: vec![array.clone()],
1698 arg_fields: vec![arg_field],
1699 number_rows: 4,
1700 return_field: Field::new("f", rt, true).into(),
1701 config_options: Arc::new(ConfigOptions::default()),
1702 };
1703 let res = udf
1704 .invoke_with_args(args)
1705 .expect("that to_timestamp parsed values without error");
1706 let array = match res {
1707 ColumnarValue::Array(res) => res,
1708 _ => panic!("Expected a columnar array"),
1709 };
1710 let ty = array.data_type();
1711 assert!(matches!(ty, Timestamp(_, None)));
1712 }
1713 }
1714
1715 let mut nanos_builder = TimestampNanosecondArray::builder(2);
1716 let mut millis_builder = TimestampMillisecondArray::builder(2);
1717 let mut micros_builder = TimestampMicrosecondArray::builder(2);
1718 let mut sec_builder = TimestampSecondArray::builder(2);
1719 let mut i64_builder = Int64Array::builder(2);
1720
1721 nanos_builder.append_value(1599572549190850000);
1722 millis_builder.append_value(1599572549190);
1723 micros_builder.append_value(1599572549190850);
1724 sec_builder.append_value(1599572549);
1725 i64_builder.append_value(1599572549);
1726
1727 let nanos_timestamps = Arc::new(nanos_builder.finish()) as ArrayRef;
1728 let millis_timestamps = Arc::new(millis_builder.finish()) as ArrayRef;
1729 let micros_timestamps = Arc::new(micros_builder.finish()) as ArrayRef;
1730 let sec_timestamps = Arc::new(sec_builder.finish()) as ArrayRef;
1731 let i64_timestamps = Arc::new(i64_builder.finish()) as ArrayRef;
1732
1733 let arrays = &[
1734 ColumnarValue::Array(Arc::clone(&nanos_timestamps)),
1735 ColumnarValue::Array(Arc::clone(&millis_timestamps)),
1736 ColumnarValue::Array(Arc::clone(µs_timestamps)),
1737 ColumnarValue::Array(Arc::clone(&sec_timestamps)),
1738 ColumnarValue::Array(Arc::clone(&i64_timestamps)),
1739 ];
1740
1741 for udf in &udfs {
1742 for array in arrays {
1743 let rt = udf.return_type(&[array.data_type()]).unwrap();
1744 assert!(matches!(rt, Timestamp(_, None)));
1745 let arg_field = Field::new("arg", array.data_type().clone(), true).into();
1746 let args = ScalarFunctionArgs {
1747 args: vec![array.clone()],
1748 arg_fields: vec![arg_field],
1749 number_rows: 5,
1750 return_field: Field::new("f", rt, true).into(),
1751 config_options: Arc::new(ConfigOptions::default()),
1752 };
1753 let res = udf
1754 .invoke_with_args(args)
1755 .expect("that to_timestamp parsed values without error");
1756 let array = match res {
1757 ColumnarValue::Array(res) => res,
1758 _ => panic!("Expected a columnar array"),
1759 };
1760 let ty = array.data_type();
1761 assert!(matches!(ty, Timestamp(_, None)));
1762 }
1763 }
1764 }
1765
1766 #[test]
1767 fn test_to_timestamp_arg_validation() {
1768 let mut date_string_builder = StringBuilder::with_capacity(2, 1024);
1769 date_string_builder.append_value("2020-09-08T13:42:29.19085Z");
1770
1771 let data = date_string_builder.finish();
1772
1773 let funcs: Vec<(ScalarFunctionImplementation, TimeUnit)> = vec![
1774 (Arc::new(to_timestamp), Nanosecond),
1775 (Arc::new(to_timestamp_micros), Microsecond),
1776 (Arc::new(to_timestamp_millis), Millisecond),
1777 (Arc::new(to_timestamp_nanos), Nanosecond),
1778 (Arc::new(to_timestamp_seconds), Second),
1779 ];
1780
1781 let mut nanos_builder = TimestampNanosecondArray::builder(2);
1782 let mut millis_builder = TimestampMillisecondArray::builder(2);
1783 let mut micros_builder = TimestampMicrosecondArray::builder(2);
1784 let mut sec_builder = TimestampSecondArray::builder(2);
1785
1786 nanos_builder.append_value(1599572549190850000);
1787 millis_builder.append_value(1599572549190);
1788 micros_builder.append_value(1599572549190850);
1789 sec_builder.append_value(1599572549);
1790
1791 let nanos_expected_timestamps = &nanos_builder.finish() as &dyn Array;
1792 let millis_expected_timestamps = &millis_builder.finish() as &dyn Array;
1793 let micros_expected_timestamps = µs_builder.finish() as &dyn Array;
1794 let sec_expected_timestamps = &sec_builder.finish() as &dyn Array;
1795
1796 for (func, time_unit) in funcs {
1797 let string_array = [
1799 ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1800 ColumnarValue::Scalar(ScalarValue::Utf8(Some("%s".to_string()))),
1801 ColumnarValue::Scalar(ScalarValue::Utf8(Some("%c".to_string()))),
1802 ColumnarValue::Scalar(ScalarValue::Utf8(Some("%+".to_string()))),
1803 ];
1804 let parsed_timestamps = func(&string_array)
1805 .expect("that to_timestamp with format args parsed values without error");
1806 if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
1807 assert_eq!(parsed_array.len(), 1);
1808 match time_unit {
1809 Nanosecond => {
1810 assert_eq!(nanos_expected_timestamps, parsed_array.as_ref())
1811 }
1812 Millisecond => {
1813 assert_eq!(millis_expected_timestamps, parsed_array.as_ref())
1814 }
1815 Microsecond => {
1816 assert_eq!(micros_expected_timestamps, parsed_array.as_ref())
1817 }
1818 Second => {
1819 assert_eq!(sec_expected_timestamps, parsed_array.as_ref())
1820 }
1821 };
1822 } else {
1823 panic!("Expected a columnar array")
1824 }
1825
1826 let string_array = [
1828 ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1829 ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("%s".to_string()))),
1830 ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("%c".to_string()))),
1831 ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("%+".to_string()))),
1832 ];
1833 let parsed_timestamps = func(&string_array)
1834 .expect("that to_timestamp with format args parsed values without error");
1835 if let ColumnarValue::Array(parsed_array) = parsed_timestamps {
1836 assert_eq!(parsed_array.len(), 1);
1837 assert!(matches!(parsed_array.data_type(), Timestamp(_, None)));
1838
1839 match time_unit {
1840 Nanosecond => {
1841 assert_eq!(nanos_expected_timestamps, parsed_array.as_ref())
1842 }
1843 Millisecond => {
1844 assert_eq!(millis_expected_timestamps, parsed_array.as_ref())
1845 }
1846 Microsecond => {
1847 assert_eq!(micros_expected_timestamps, parsed_array.as_ref())
1848 }
1849 Second => {
1850 assert_eq!(sec_expected_timestamps, parsed_array.as_ref())
1851 }
1852 };
1853 } else {
1854 panic!("Expected a columnar array")
1855 }
1856
1857 let string_array = [
1859 ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1860 ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
1861 ColumnarValue::Scalar(ScalarValue::Int32(Some(2))),
1862 ColumnarValue::Scalar(ScalarValue::Int32(Some(3))),
1863 ];
1864
1865 let expected = "Unsupported data type Int32 for function".to_string();
1866 let actual = func(&string_array).unwrap_err().to_string();
1867 assert_contains!(actual, expected);
1868
1869 let string_array = [
1871 ColumnarValue::Array(Arc::new(data.clone()) as ArrayRef),
1872 ColumnarValue::Array(Arc::new(PrimitiveArray::<Int64Type>::new(
1873 vec![1i64].into(),
1874 None,
1875 )) as ArrayRef),
1876 ];
1877
1878 let expected = "Unsupported data type".to_string();
1879 let actual = func(&string_array).unwrap_err().to_string();
1880 assert_contains!(actual, expected);
1881 }
1882 }
1883
1884 #[test]
1885 fn test_decimal_to_nanoseconds_negative_scale() {
1886 let nanos = decimal_to_nanoseconds(5, -2).unwrap();
1888 assert_eq!(nanos, 500_000_000_000); let nanos = decimal_to_nanoseconds(10, -1).unwrap();
1892 assert_eq!(nanos, 100_000_000_000);
1893
1894 let nanos = decimal_to_nanoseconds(5, 0).unwrap();
1896 assert_eq!(nanos, 5_000_000_000);
1897
1898 let nanos = decimal_to_nanoseconds(1500, 3).unwrap();
1900 assert_eq!(nanos, 1_500_000_000);
1901 }
1902}