Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #9673: ICU DST Truncation #9705

Merged
merged 1 commit into from Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 40 additions & 7 deletions extension/icu/icu-datetrunc.cpp
Expand Up @@ -12,26 +12,59 @@
namespace duckdb {

struct ICUDateTrunc : public ICUDateFunc {
static void PreserveOffsets(icu::Calendar *calendar) {
// We have to extract _everything_ before setting anything
// Otherwise ICU will clear the fStamp fields
// This also means we must call this method first.

// Force reuse of offsets when reassembling truncated sub-hour times.
const auto zone_offset = ExtractField(calendar, UCAL_ZONE_OFFSET);
const auto dst_offset = ExtractField(calendar, UCAL_DST_OFFSET);

calendar->set(UCAL_ZONE_OFFSET, zone_offset);
calendar->set(UCAL_DST_OFFSET, dst_offset);
}

static void TruncMicrosecondInternal(icu::Calendar *calendar, uint64_t &micros) {
}

static void TruncMicrosecond(icu::Calendar *calendar, uint64_t &micros) {
PreserveOffsets(calendar);
TruncMicrosecondInternal(calendar, micros);
}

static void TruncMillisecond(icu::Calendar *calendar, uint64_t &micros) {
TruncMicrosecond(calendar, micros);
static void TruncMillisecondInternal(icu::Calendar *calendar, uint64_t &micros) {
TruncMicrosecondInternal(calendar, micros);
micros = 0;
}

static void TruncSecond(icu::Calendar *calendar, uint64_t &micros) {
TruncMillisecond(calendar, micros);
static void TruncMillisecond(icu::Calendar *calendar, uint64_t &micros) {
PreserveOffsets(calendar);
TruncMillisecondInternal(calendar, micros);
}

static void TruncSecondInternal(icu::Calendar *calendar, uint64_t &micros) {
TruncMillisecondInternal(calendar, micros);
calendar->set(UCAL_MILLISECOND, 0);
}

static void TruncMinute(icu::Calendar *calendar, uint64_t &micros) {
TruncSecond(calendar, micros);
static void TruncSecond(icu::Calendar *calendar, uint64_t &micros) {
PreserveOffsets(calendar);
TruncSecondInternal(calendar, micros);
}

static void TruncMinuteInternal(icu::Calendar *calendar, uint64_t &micros) {
TruncSecondInternal(calendar, micros);
calendar->set(UCAL_SECOND, 0);
}

static void TruncMinute(icu::Calendar *calendar, uint64_t &micros) {
PreserveOffsets(calendar);
TruncMinuteInternal(calendar, micros);
}

static void TruncHour(icu::Calendar *calendar, uint64_t &micros) {
TruncMinute(calendar, micros);
TruncMinuteInternal(calendar, micros);
calendar->set(UCAL_MINUTE, 0);
}

Expand Down
20 changes: 20 additions & 0 deletions test/sql/function/timestamp/test_icu_datediff.test
Expand Up @@ -117,3 +117,23 @@ query I
SELECT date_diff('week', '2015-10-06 04:22:11'::timestamptz, '2016-11-25 23:19:37'::timestamptz);
----
59

# DST diffs should not be negative
statement ok
set timezone='CET';

statement ok
CREATE TABLE issue9673(starttime TIMESTAMPTZ, recordtime TIMESTAMPTZ);

statement ok
INSERT INTO issue9673 VALUES ('2022-10-30 02:17:00+02', '2022-10-30 02:00:21+01');

statement ok
INSERT INTO issue9673 VALUES ('2021-10-31 02:39:00+02', '2021-10-31 02:38:20+01');

query III
SELECT starttime, recordtime, date_diff('minute', starttime, recordtime)
FROM issue9673;
----
2022-10-30 02:17:00+02 2022-10-30 02:00:21+01 43
2021-10-31 02:39:00+02 2021-10-31 02:38:20+01 59
10 changes: 10 additions & 0 deletions test/sql/function/timestamp/test_icu_datetrunc.test
Expand Up @@ -227,3 +227,13 @@ FROM generate_series('2000-01-01 00:00:00-08'::TIMESTAMP, '2023-01-01 00:00:00-0
# Unknown specifier should fail
statement error
SELECT date_trunc('duck', TIMESTAMPTZ '2019-01-06 04:03:02-08') FROM timestamps LIMIT 1;

# Minute truncation within a DST fall back ambiguity
statement ok
set timezone='CET';

query I
select date_trunc('minute', '2022-10-30 02:17:00+02'::TIMESTAMPTZ);
----
2022-10-30 02:17:00+02