1515// specific language governing permissions and limitations
1616// under the License.
1717
18- use std:: sync:: { Arc , LazyLock } ;
18+ use std:: sync:: Arc ;
1919
20- use arrow:: array:: timezone:: Tz ;
2120use arrow:: array:: {
2221 Array , ArrowPrimitiveType , AsArray , GenericStringArray , PrimitiveArray ,
2322 StringArrayType , StringViewArray ,
2423} ;
25- use arrow:: compute:: DecimalCast ;
26- use arrow:: compute:: kernels:: cast_utils:: string_to_datetime;
27- use arrow:: datatypes:: { DataType , TimeUnit } ;
28- use arrow_buffer:: ArrowNativeType ;
24+ use arrow:: compute:: kernels:: cast_utils:: string_to_timestamp_nanos;
25+ use arrow:: datatypes:: DataType ;
2926use chrono:: LocalResult :: Single ;
3027use chrono:: format:: { Parsed , StrftimeItems , parse} ;
3128use chrono:: { DateTime , TimeZone , Utc } ;
29+
3230use datafusion_common:: cast:: as_generic_string_array;
3331use datafusion_common:: {
34- DataFusionError , Result , ScalarValue , exec_datafusion_err, exec_err,
35- internal_datafusion_err , unwrap_or_internal_err,
32+ DataFusionError , Result , ScalarType , ScalarValue , exec_datafusion_err, exec_err,
33+ unwrap_or_internal_err,
3634} ;
3735use datafusion_expr:: ColumnarValue ;
3836
3937/// Error message if nanosecond conversion request beyond supported interval
4038const ERR_NANOSECONDS_NOT_SUPPORTED : & str = "The dates that can be represented as nanoseconds have to be between 1677-09-21T00:12:44.0 and 2262-04-11T23:47:16.854775804" ;
4139
42- static UTC : LazyLock < Tz > = LazyLock :: new ( || "UTC" . parse ( ) . expect ( "UTC is always valid" ) ) ;
43-
44- /// Converts a string representation of a date‑time into a timestamp expressed in
45- /// nanoseconds since the Unix epoch.
46- ///
47- /// This helper is a thin wrapper around the more general `string_to_datetime`
48- /// function. It accepts an optional `timezone` which, if `None`, defaults to
49- /// Coordinated Universal Time (UTC). The string `s` must contain a valid
50- /// date‑time format that can be parsed by the underlying chrono parser.
51- ///
52- /// # Return Value
53- ///
54- /// * `Ok(i64)` – The number of nanoseconds since `1970‑01‑01T00:00:00Z`.
55- /// * `Err(DataFusionError)` – If the string cannot be parsed, the parsed
56- /// value is out of range (between 1677-09-21T00:12:44.0 and 2262-04-11T23:47:16.854775804)
57- /// or the parsed value does not correspond to an unambiguous time.
58- pub ( crate ) fn string_to_timestamp_nanos_with_timezone (
59- timezone : & Option < Tz > ,
60- s : & str ,
61- ) -> Result < i64 > {
62- let tz = timezone. as_ref ( ) . unwrap_or ( & UTC ) ;
63- let dt = string_to_datetime ( tz, s) ?;
64- let parsed = dt
65- . timestamp_nanos_opt ( )
66- . ok_or_else ( || exec_datafusion_err ! ( "{ERR_NANOSECONDS_NOT_SUPPORTED}" ) ) ?;
67-
68- Ok ( parsed)
40+ /// Calls string_to_timestamp_nanos and converts the error type
41+ pub ( crate ) fn string_to_timestamp_nanos_shim ( s : & str ) -> Result < i64 > {
42+ string_to_timestamp_nanos ( s) . map_err ( |e| e. into ( ) )
6943}
7044
7145/// Checks that all the arguments from the second are of type [Utf8], [LargeUtf8] or [Utf8View]
@@ -95,12 +69,13 @@ pub(crate) fn validate_data_types(args: &[ColumnarValue], name: &str) -> Result<
9569/// Accepts a string and parses it using the [`chrono::format::strftime`] specifiers
9670/// relative to the provided `timezone`
9771///
72+ /// [IANA timezones] are only supported if the `arrow-array/chrono-tz` feature is enabled
73+ ///
74+ /// * `2023-01-01 040506 America/Los_Angeles`
75+ ///
9876/// If a timestamp is ambiguous, for example as a result of daylight-savings time, an error
9977/// will be returned
10078///
101- /// Note that parsing [IANA timezones] is not supported yet in chrono - <https://github.com/chronotope/chrono/issues/38>
102- /// and this implementation only supports named timezones at the end of the string preceded by a space.
103- ///
10479/// [`chrono::format::strftime`]: https://docs.rs/chrono/latest/chrono/format/strftime/index.html
10580/// [IANA timezones]: https://www.iana.org/time-zones
10681pub ( crate ) fn string_to_datetime_formatted < T : TimeZone > (
@@ -114,55 +89,11 @@ pub(crate) fn string_to_datetime_formatted<T: TimeZone>(
11489 )
11590 } ;
11691
117- let mut datetime_str = s;
118- let mut format = format;
119-
120- // Manually handle the most common case of a named timezone at the end of the timestamp.
121- // Note that %+ handles 'Z' at the end of the string without a space. This code doesn't
122- // handle named timezones with no preceding space since that would require writing a
123- // custom parser (or switching to Jiff)
124- let tz: Option < chrono_tz:: Tz > = if format. trim_end ( ) . ends_with ( " %Z" ) {
125- // grab the string after the last space as the named timezone
126- if let Some ( ( dt_str, timezone_name) ) = datetime_str. trim_end ( ) . rsplit_once ( ' ' ) {
127- datetime_str = dt_str;
128-
129- // attempt to parse the timezone name
130- let result: Result < chrono_tz:: Tz , chrono_tz:: ParseError > =
131- timezone_name. parse ( ) ;
132- let Ok ( tz) = result else {
133- return Err ( err ( & result. unwrap_err ( ) . to_string ( ) ) ) ;
134- } ;
135-
136- // successfully parsed the timezone name, remove the ' %Z' from the format
137- format = & format[ ..format. len ( ) - 3 ] ;
138-
139- Some ( tz)
140- } else {
141- None
142- }
143- } else if format. contains ( "%Z" ) {
144- return Err ( err (
145- "'%Z' is only supported at the end of the format string preceded by a space" ,
146- ) ) ;
147- } else {
148- None
149- } ;
150-
15192 let mut parsed = Parsed :: new ( ) ;
152- parse ( & mut parsed, datetime_str, StrftimeItems :: new ( format) )
153- . map_err ( |e| err ( & e. to_string ( ) ) ) ?;
93+ parse ( & mut parsed, s, StrftimeItems :: new ( format) ) . map_err ( |e| err ( & e. to_string ( ) ) ) ?;
15494
155- let dt = match tz {
156- Some ( tz) => {
157- // A timezone was manually parsed out, convert it to a fixed offset
158- match parsed. to_datetime_with_timezone ( & tz) {
159- Ok ( dt) => Ok ( dt. fixed_offset ( ) ) ,
160- Err ( e) => Err ( e) ,
161- }
162- }
163- // default to parse the string assuming it has a timezone
164- None => parsed. to_datetime ( ) ,
165- } ;
95+ // attempt to parse the string assuming it has a timezone
96+ let dt = parsed. to_datetime ( ) ;
16697
16798 if let Err ( e) = & dt {
16899 // no timezone or other failure, try without a timezone
@@ -184,7 +115,7 @@ pub(crate) fn string_to_datetime_formatted<T: TimeZone>(
184115}
185116
186117/// Accepts a string with a `chrono` format and converts it to a
187- /// nanosecond precision timestamp relative to the provided `timezone` .
118+ /// nanosecond precision timestamp.
188119///
189120/// See [`chrono::format::strftime`] for the full set of supported formats.
190121///
@@ -210,21 +141,19 @@ pub(crate) fn string_to_datetime_formatted<T: TimeZone>(
210141///
211142/// [`chrono::format::strftime`]: https://docs.rs/chrono/latest/chrono/format/strftime/index.html
212143#[ inline]
213- pub ( crate ) fn string_to_timestamp_nanos_formatted_with_timezone (
214- timezone : & Option < Tz > ,
144+ pub ( crate ) fn string_to_timestamp_nanos_formatted (
215145 s : & str ,
216146 format : & str ,
217147) -> Result < i64 , DataFusionError > {
218- let dt = string_to_datetime_formatted ( timezone. as_ref ( ) . unwrap_or ( & UTC ) , s, format) ?;
219- let parsed = dt
148+ string_to_datetime_formatted ( & Utc , s, format) ?
149+ . naive_utc ( )
150+ . and_utc ( )
220151 . timestamp_nanos_opt ( )
221- . ok_or_else ( || exec_datafusion_err ! ( "{ERR_NANOSECONDS_NOT_SUPPORTED}" ) ) ?;
222-
223- Ok ( parsed)
152+ . ok_or_else ( || exec_datafusion_err ! ( "{ERR_NANOSECONDS_NOT_SUPPORTED}" ) )
224153}
225154
226155/// Accepts a string with a `chrono` format and converts it to a
227- /// millisecond precision timestamp relative to the provided `timezone` .
156+ /// millisecond precision timestamp.
228157///
229158/// See [`chrono::format::strftime`] for the full set of supported formats.
230159///
@@ -247,14 +176,14 @@ pub(crate) fn string_to_timestamp_millis_formatted(s: &str, format: &str) -> Res
247176 . timestamp_millis ( ) )
248177}
249178
250- pub ( crate ) fn handle < O , F > (
179+ pub ( crate ) fn handle < O , F , S > (
251180 args : & [ ColumnarValue ] ,
252181 op : F ,
253182 name : & str ,
254- dt : & DataType ,
255183) -> Result < ColumnarValue >
256184where
257185 O : ArrowPrimitiveType ,
186+ S : ScalarType < O :: Native > ,
258187 F : Fn ( & str ) -> Result < O :: Native > ,
259188{
260189 match & args[ 0 ] {
@@ -281,13 +210,8 @@ where
281210 } ,
282211 ColumnarValue :: Scalar ( scalar) => match scalar. try_as_str ( ) {
283212 Some ( a) => {
284- let result = a
285- . as_ref ( )
286- . map ( |x| op ( x) )
287- . transpose ( ) ?
288- . and_then ( |v| v. to_i64 ( ) ) ;
289- let s = scalar_value ( dt, result) ?;
290- Ok ( ColumnarValue :: Scalar ( s) )
213+ let result = a. as_ref ( ) . map ( |x| op ( x) ) . transpose ( ) ?;
214+ Ok ( ColumnarValue :: Scalar ( S :: scalar ( result) ) )
291215 }
292216 _ => exec_err ! ( "Unsupported data type {scalar:?} for function {name}" ) ,
293217 } ,
@@ -297,15 +221,15 @@ where
297221// Given a function that maps a `&str`, `&str` to an arrow native type,
298222// returns a `ColumnarValue` where the function is applied to either a `ArrayRef` or `ScalarValue`
299223// depending on the `args`'s variant.
300- pub ( crate ) fn handle_multiple < O , F , M > (
224+ pub ( crate ) fn handle_multiple < O , F , S , M > (
301225 args : & [ ColumnarValue ] ,
302226 op : F ,
303227 op2 : M ,
304228 name : & str ,
305- dt : & DataType ,
306229) -> Result < ColumnarValue >
307230where
308231 O : ArrowPrimitiveType ,
232+ S : ScalarType < O :: Native > ,
309233 F : Fn ( & str , & str ) -> Result < O :: Native > ,
310234 M : Fn ( O :: Native ) -> O :: Native ,
311235{
@@ -374,9 +298,9 @@ where
374298 if let Some ( s) = x {
375299 match op ( a, s. as_str ( ) ) {
376300 Ok ( r) => {
377- let result = op2 ( r ) . to_i64 ( ) ;
378- let s = scalar_value ( dt , result ) ? ;
379- ret = Some ( Ok ( ColumnarValue :: Scalar ( s ) ) ) ;
301+ ret = Some ( Ok ( ColumnarValue :: Scalar ( S :: scalar ( Some (
302+ op2 ( r ) ,
303+ ) ) ) ) ) ;
380304 break ;
381305 }
382306 Err ( e) => ret = Some ( Err ( e) ) ,
@@ -530,16 +454,3 @@ where
530454 // first map is the iterator, second is for the `Option<_>`
531455 array. iter ( ) . map ( |x| x. map ( & op) . transpose ( ) ) . collect ( )
532456}
533-
534- fn scalar_value ( dt : & DataType , r : Option < i64 > ) -> Result < ScalarValue > {
535- match dt {
536- DataType :: Date32 => Ok ( ScalarValue :: Date32 ( r. and_then ( |v| v. to_i32 ( ) ) ) ) ,
537- DataType :: Timestamp ( u, tz) => match u {
538- TimeUnit :: Second => Ok ( ScalarValue :: TimestampSecond ( r, tz. clone ( ) ) ) ,
539- TimeUnit :: Millisecond => Ok ( ScalarValue :: TimestampMillisecond ( r, tz. clone ( ) ) ) ,
540- TimeUnit :: Microsecond => Ok ( ScalarValue :: TimestampMicrosecond ( r, tz. clone ( ) ) ) ,
541- TimeUnit :: Nanosecond => Ok ( ScalarValue :: TimestampNanosecond ( r, tz. clone ( ) ) ) ,
542- } ,
543- t => Err ( internal_datafusion_err ! ( "Unsupported data type: {t:?}" ) ) ,
544- }
545- }
0 commit comments