Skip to content

Commit 58a1bfc

Browse files
committed
Updates from main, fixed docs on adjust_to_local_time and reverted change to calculate offset_seconds.
1 parent 4f7a5c3 commit 58a1bfc

File tree

3 files changed

+62
-19
lines changed

3 files changed

+62
-19
lines changed

datafusion/functions/src/datetime/common.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use arrow::datatypes::{ArrowTimestampType, DataType, TimeUnit};
2828
use arrow_buffer::ArrowNativeType;
2929
use chrono::LocalResult::Single;
3030
use chrono::format::{Parsed, StrftimeItems, parse};
31-
use chrono::{DateTime, MappedLocalTime, TimeDelta, TimeZone, Utc};
31+
use chrono::{DateTime, MappedLocalTime, Offset, TimeDelta, TimeZone, Utc};
3232
use datafusion_common::cast::as_generic_string_array;
3333
use datafusion_common::{
3434
DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err,
@@ -40,6 +40,59 @@ use std::ops::Add;
4040
/// Error message if nanosecond conversion request beyond supported interval
4141
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";
4242

43+
/// This function converts a timestamp with a timezone to a timestamp without a timezone.
44+
/// The display value of the adjusted timestamp remain the same, but the underlying timestamp
45+
/// representation is adjusted according to the relative timezone offset to UTC.
46+
///
47+
/// This function uses chrono to handle daylight saving time changes.
48+
///
49+
/// For example,
50+
///
51+
/// ```text
52+
/// '2019-03-31T01:00:00Z'::timestamp at time zone 'Europe/Brussels'
53+
/// ```
54+
///
55+
/// is displayed as follows in datafusion-cli:
56+
///
57+
/// ```text
58+
/// 2019-03-31T01:00:00+01:00
59+
/// ```
60+
///
61+
/// and is represented in DataFusion as:
62+
///
63+
/// ```text
64+
/// TimestampNanosecond(Some(1_553_990_400_000_000_000), Some("Europe/Brussels"))
65+
/// ```
66+
///
67+
/// To strip off the timezone while keeping the display value the same, we need to
68+
/// adjust the underlying timestamp with the timezone offset value using `adjust_to_local_time()`
69+
///
70+
/// ```text
71+
/// adjust_to_local_time(1_553_990_400_000_000_000, "Europe/Brussels") --> 1_553_994_000_000_000_000
72+
/// ```
73+
///
74+
/// The difference between `1_553_990_400_000_000_000` and `1_553_994_000_000_000_000` is
75+
/// `3600_000_000_000` ns, which corresponds to 1 hour. This matches with the timezone
76+
/// offset for "Europe/Brussels" for this date.
77+
///
78+
/// Note that the offset varies with daylight savings time (DST), which makes this tricky! For
79+
/// example, timezone "Europe/Brussels" has a 2-hour offset during DST and a 1-hour offset
80+
/// when DST ends.
81+
///
82+
/// Consequently, DataFusion can represent the timestamp in local time (with no offset or
83+
/// timezone information) as
84+
///
85+
/// ```text
86+
/// TimestampNanosecond(Some(1_553_994_000_000_000_000), None)
87+
/// ```
88+
///
89+
/// which is displayed as follows in datafusion-cli:
90+
///
91+
/// ```text
92+
/// 2019-03-31T01:00:00
93+
/// ```
94+
///
95+
/// See `test_adjust_to_local_time()` for example
4396
pub fn adjust_to_local_time<T: ArrowTimestampType>(ts: i64, tz: Tz) -> Result<i64> {
4497
fn convert_timestamp<F>(ts: i64, converter: F) -> Result<DateTime<Utc>>
4598
where
@@ -67,21 +120,10 @@ pub fn adjust_to_local_time<T: ArrowTimestampType>(ts: i64, tz: Tz) -> Result<i6
67120
TimeUnit::Second => convert_timestamp(ts, |ts| Utc.timestamp_opt(ts, 0))?,
68121
};
69122

70-
// Get the timezone offset for this datetime
71-
let tz_offset = tz.offset_from_utc_datetime(&date_time.naive_utc());
72-
// Convert offset to seconds - offset is formatted like "+01:00" or "-05:00"
73-
let offset_str = format!("{tz_offset}");
74-
let offset_seconds: i64 = if let Some(stripped) = offset_str.strip_prefix('-') {
75-
let parts: Vec<&str> = stripped.split(':').collect();
76-
let hours: i64 = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
77-
let mins: i64 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
78-
-((hours * 3600) + (mins * 60))
79-
} else {
80-
let parts: Vec<&str> = offset_str.split(':').collect();
81-
let hours: i64 = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
82-
let mins: i64 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
83-
(hours * 3600) + (mins * 60)
84-
};
123+
let offset_seconds: i64 = tz
124+
.offset_from_utc_datetime(&date_time.naive_utc())
125+
.fix()
126+
.local_minus_utc() as i64;
85127

86128
let adjusted_date_time = date_time.add(
87129
TimeDelta::try_seconds(offset_seconds)

datafusion/functions/src/datetime/date_part.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ use arrow::datatypes::DataType::{
2828
};
2929
use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
3030
use arrow::datatypes::{
31-
ArrowTimestampType, DataType, Field, FieldRef, IntervalUnit as ArrowIntervalUnit, TimeUnit, TimestampMicrosecondType,
32-
TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType,
31+
ArrowTimestampType, DataType, Field, FieldRef, IntervalUnit as ArrowIntervalUnit,
32+
TimeUnit, TimestampMicrosecondType, TimestampMillisecondType,
33+
TimestampNanosecondType, TimestampSecondType,
3334
};
3435

3536
use datafusion_common::cast::as_primitive_array;

datafusion/spark/src/function/datetime/from_utc_timestamp.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use datafusion_expr::{
3232
Coercion, ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl,
3333
Signature, TypeSignatureClass, Volatility,
3434
};
35-
use datafusion_functions::datetime::to_local_time::adjust_to_local_time;
35+
use datafusion_functions::datetime::common::adjust_to_local_time;
3636
use datafusion_functions::utils::make_scalar_function;
3737

3838
/// Apache Spark `from_utc_timestamp` function.

0 commit comments

Comments
 (0)