Skip to content

Commit 9845fab

Browse files
Revert "Respect execution timezone in to_timestamp and related functions (apache#19078)"
This reverts commit ada0923.
1 parent a03e445 commit 9845fab

12 files changed

Lines changed: 240 additions & 1159 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

datafusion/functions/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ workspace = true
4040
[features]
4141
crypto_expressions = ["md-5", "sha2", "blake2", "blake3"]
4242
# enable datetime functions
43-
datetime_expressions = ["chrono-tz"]
43+
datetime_expressions = []
4444
# Enable encoding by default so the doctests work. In general don't automatically enable all packages.
4545
default = [
4646
"datetime_expressions",
@@ -71,7 +71,6 @@ base64 = { version = "0.22", optional = true }
7171
blake2 = { version = "^0.10.2", optional = true }
7272
blake3 = { version = "1.8", optional = true }
7373
chrono = { workspace = true }
74-
chrono-tz = { version = "0.10.4", optional = true }
7574
datafusion-common = { workspace = true }
7675
datafusion-doc = { workspace = true }
7776
datafusion-execution = { workspace = true }

datafusion/functions/benches/to_timestamp.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,16 @@ fn criterion_benchmark(c: &mut Criterion) {
114114
Field::new("f", DataType::Timestamp(TimeUnit::Nanosecond, None), true).into();
115115
let arg_field = Field::new("a", DataType::Utf8, false).into();
116116
let arg_fields = vec![arg_field];
117-
let mut options = ConfigOptions::default();
118-
options.execution.time_zone = Some("UTC".into());
119-
let config_options = Arc::new(options);
120-
121-
let to_timestamp_udf = to_timestamp(config_options.as_ref());
117+
let config_options = Arc::new(ConfigOptions::default());
122118

123119
c.bench_function("to_timestamp_no_formats_utf8", |b| {
124-
let to_timestamp_udf = Arc::clone(&to_timestamp_udf);
125120
let arr_data = data();
126121
let batch_len = arr_data.len();
127122
let string_array = ColumnarValue::Array(Arc::new(arr_data) as ArrayRef);
128123

129124
b.iter(|| {
130125
black_box(
131-
to_timestamp_udf
126+
to_timestamp()
132127
.invoke_with_args(ScalarFunctionArgs {
133128
args: vec![string_array.clone()],
134129
arg_fields: arg_fields.clone(),
@@ -142,14 +137,13 @@ fn criterion_benchmark(c: &mut Criterion) {
142137
});
143138

144139
c.bench_function("to_timestamp_no_formats_largeutf8", |b| {
145-
let to_timestamp_udf = Arc::clone(&to_timestamp_udf);
146140
let data = cast(&data(), &DataType::LargeUtf8).unwrap();
147141
let batch_len = data.len();
148142
let string_array = ColumnarValue::Array(Arc::new(data) as ArrayRef);
149143

150144
b.iter(|| {
151145
black_box(
152-
to_timestamp_udf
146+
to_timestamp()
153147
.invoke_with_args(ScalarFunctionArgs {
154148
args: vec![string_array.clone()],
155149
arg_fields: arg_fields.clone(),
@@ -163,14 +157,13 @@ fn criterion_benchmark(c: &mut Criterion) {
163157
});
164158

165159
c.bench_function("to_timestamp_no_formats_utf8view", |b| {
166-
let to_timestamp_udf = Arc::clone(&to_timestamp_udf);
167160
let data = cast(&data(), &DataType::Utf8View).unwrap();
168161
let batch_len = data.len();
169162
let string_array = ColumnarValue::Array(Arc::new(data) as ArrayRef);
170163

171164
b.iter(|| {
172165
black_box(
173-
to_timestamp_udf
166+
to_timestamp()
174167
.invoke_with_args(ScalarFunctionArgs {
175168
args: vec![string_array.clone()],
176169
arg_fields: arg_fields.clone(),
@@ -184,7 +177,6 @@ fn criterion_benchmark(c: &mut Criterion) {
184177
});
185178

186179
c.bench_function("to_timestamp_with_formats_utf8", |b| {
187-
let to_timestamp_udf = Arc::clone(&to_timestamp_udf);
188180
let (inputs, format1, format2, format3) = data_with_formats();
189181
let batch_len = inputs.len();
190182

@@ -204,7 +196,7 @@ fn criterion_benchmark(c: &mut Criterion) {
204196

205197
b.iter(|| {
206198
black_box(
207-
to_timestamp_udf
199+
to_timestamp()
208200
.invoke_with_args(ScalarFunctionArgs {
209201
args: args.clone(),
210202
arg_fields: arg_fields.clone(),
@@ -218,7 +210,6 @@ fn criterion_benchmark(c: &mut Criterion) {
218210
});
219211

220212
c.bench_function("to_timestamp_with_formats_largeutf8", |b| {
221-
let to_timestamp_udf = Arc::clone(&to_timestamp_udf);
222213
let (inputs, format1, format2, format3) = data_with_formats();
223214
let batch_len = inputs.len();
224215

@@ -246,7 +237,7 @@ fn criterion_benchmark(c: &mut Criterion) {
246237

247238
b.iter(|| {
248239
black_box(
249-
to_timestamp_udf
240+
to_timestamp()
250241
.invoke_with_args(ScalarFunctionArgs {
251242
args: args.clone(),
252243
arg_fields: arg_fields.clone(),
@@ -260,7 +251,6 @@ fn criterion_benchmark(c: &mut Criterion) {
260251
});
261252

262253
c.bench_function("to_timestamp_with_formats_utf8view", |b| {
263-
let to_timestamp_udf = Arc::clone(&to_timestamp_udf);
264254
let (inputs, format1, format2, format3) = data_with_formats();
265255

266256
let batch_len = inputs.len();
@@ -289,7 +279,7 @@ fn criterion_benchmark(c: &mut Criterion) {
289279

290280
b.iter(|| {
291281
black_box(
292-
to_timestamp_udf
282+
to_timestamp()
293283
.invoke_with_args(ScalarFunctionArgs {
294284
args: args.clone(),
295285
arg_fields: arg_fields.clone(),

datafusion/functions/src/datetime/common.rs

Lines changed: 32 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -15,57 +15,31 @@
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;
2120
use 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;
2926
use chrono::LocalResult::Single;
3027
use chrono::format::{Parsed, StrftimeItems, parse};
3128
use chrono::{DateTime, TimeZone, Utc};
29+
3230
use datafusion_common::cast::as_generic_string_array;
3331
use 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
};
3735
use datafusion_expr::ColumnarValue;
3836

3937
/// Error message if nanosecond conversion request beyond supported interval
4038
const 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
10681
pub(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>
256184
where
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>
307230
where
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

Comments
 (0)