This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 7e721f83642 branch-4.1: [fix](nereids) Fix DST spring-forward gap
handling in timestamptz literal #62945 (#62978)
7e721f83642 is described below
commit 7e721f836423e7a43ccfa07251ace191fb30f83d
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed May 6 14:45:27 2026 +0800
branch-4.1: [fix](nereids) Fix DST spring-forward gap handling in
timestamptz literal #62945 (#62978)
Cherry-picked from #62945
Co-authored-by: zclllyybb <[email protected]>
---
.../trees/expressions/literal/DateLiteral.java | 2 +-
.../trees/expressions/literal/DateTimeLiteral.java | 66 ++++++++--------------
.../timestamptz/test_timestamptz_dst_gap.out | 11 ++++
.../timestamptz/test_timestamptz_dst_gap.groovy | 61 ++++++++++++++++++++
4 files changed, 98 insertions(+), 42 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateLiteral.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateLiteral.java
index 9e6dd2c9f39..cbec0669eb6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateLiteral.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateLiteral.java
@@ -377,7 +377,7 @@ public class DateLiteral extends Literal implements
ComparableLiteral {
}
protected static boolean checkRange(long year, long month, long day) {
- return year > MAX_DATE.getYear() || month > MAX_DATE.getMonth() || day
> MAX_DATE.getDay();
+ return year < 0 || year > MAX_DATE.getYear() || month >
MAX_DATE.getMonth() || day > MAX_DATE.getDay();
}
protected static boolean checkDate(long year, long month, long day) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
index 9fccbfb4bfa..ef5f5f37582 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
@@ -37,10 +37,8 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.math.BigInteger;
-import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
-import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
@@ -154,23 +152,14 @@ public class DateTimeLiteral extends DateLiteral {
ZoneId zoneId = temporal.query(TemporalQueries.zone());
if (zoneId != null) {
- // get correct DST of that time.
- Instant thatTime = ZonedDateTime
- .of((int) year, (int) month, (int) day, (int) hour, (int)
minute, (int) second, 0, zoneId)
- .toInstant();
-
- int offset =
DateUtils.getTimeZone().getRules().getOffset(thatTime).getTotalSeconds()
- - zoneId.getRules().getOffset(thatTime).getTotalSeconds();
- if (offset != 0) {
- DateTimeLiteral tempLiteral = new DateTimeLiteral(year, month,
day, hour, minute, second);
- DateTimeLiteral result = (DateTimeLiteral)
tempLiteral.plusSeconds(offset);
- second = result.second;
- minute = result.minute;
- hour = result.hour;
- day = result.day;
- month = result.month;
- year = result.year;
- }
+ LocalDateTime localDateTime = convertTimeZone(year, month, day,
hour, minute, second,
+ zoneId, DateUtils.getTimeZone());
+ year = localDateTime.getYear();
+ month = localDateTime.getMonthValue();
+ day = localDateTime.getDayOfMonth();
+ hour = localDateTime.getHour();
+ minute = localDateTime.getMinute();
+ second = localDateTime.getSecond();
}
long microSecond = DateUtils.getOrDefault(temporal,
ChronoField.NANO_OF_SECOND) / 100L;
@@ -237,28 +226,15 @@ public class DateTimeLiteral extends DateLiteral {
ZoneId zoneId = temporal.query(TemporalQueries.zone());
if (zoneId != null) {
- // get correct DST of that time.
- Instant thatTime = ZonedDateTime
- .of((int) year, (int) month, (int) day, (int) hour, (int)
minute, (int) second, 0, zoneId)
- .toInstant();
-
- int offset = 0;
- if (this.dataType instanceof TimeStampTzType) {
- offset =
ZoneId.of("UTC").getRules().getOffset(thatTime).getTotalSeconds()
- -
zoneId.getRules().getOffset(thatTime).getTotalSeconds();
- } else {
- offset =
DateUtils.getTimeZone().getRules().getOffset(thatTime).getTotalSeconds()
- -
zoneId.getRules().getOffset(thatTime).getTotalSeconds();
- }
- if (offset != 0) {
- DateTimeLiteral result = (DateTimeLiteral)
this.plusSeconds(offset);
- this.second = result.second;
- this.minute = result.minute;
- this.hour = result.hour;
- this.day = result.day;
- this.month = result.month;
- this.year = result.year;
- }
+ ZoneId targetZone = this.dataType instanceof TimeStampTzType ?
ZoneId.of("UTC") : DateUtils.getTimeZone();
+ LocalDateTime localDateTime = convertTimeZone(year, month, day,
hour, minute, second,
+ zoneId, targetZone);
+ this.year = localDateTime.getYear();
+ this.month = localDateTime.getMonthValue();
+ this.day = localDateTime.getDayOfMonth();
+ this.hour = localDateTime.getHour();
+ this.minute = localDateTime.getMinute();
+ this.second = localDateTime.getSecond();
}
microSecond = DateUtils.getOrDefault(temporal,
ChronoField.NANO_OF_SECOND) / 100L;
@@ -293,6 +269,14 @@ public class DateTimeLiteral extends DateLiteral {
}
}
+ private static LocalDateTime convertTimeZone(long year, long month, long
day, long hour, long minute,
+ long second, ZoneId fromZone, ZoneId toZone) {
+ return LocalDateTime.of((int) year, (int) month, (int) day, (int)
hour, (int) minute, (int) second)
+ .atZone(fromZone)
+ .withZoneSameInstant(toZone)
+ .toLocalDateTime();
+ }
+
public boolean checkRange() {
return checkRange(year, month, day) || hour > MAX_DATETIME.getHour()
|| minute > MAX_DATETIME.getMinute()
|| second > MAX_DATETIME.getSecond() || microSecond >
MAX_MICROSECOND;
diff --git
a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_gap.out
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_gap.out
new file mode 100644
index 00000000000..ae3b0c924d0
--- /dev/null
+++ b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_gap.out
@@ -0,0 +1,11 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !sql --
+1 named_gap_ny 2024-03-10 07:30:00.000000+00:00
+2 explicit_before_gap 2024-03-10 06:30:00.000000+00:00
+3 explicit_after_gap 2024-03-10 07:30:00.000000+00:00
+4 implicit_gap_ny 2024-03-10 07:30:00.000000+00:00
+5 implicit_after_gap 2024-03-10 07:30:00.000000+00:00
+
+-- !sql --
+true
+
diff --git
a/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_gap.groovy
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_gap.groovy
new file mode 100644
index 00000000000..0a1e2337d55
--- /dev/null
+++
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_gap.groovy
@@ -0,0 +1,61 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_timestamptz_dst_gap") {
+ sql """
+ DROP TABLE IF EXISTS tz_dst_gap;
+ """
+
+ sql """
+ CREATE TABLE tz_dst_gap (
+ id INT,
+ label VARCHAR(64),
+ ts_tz TIMESTAMPTZ(6)
+ )
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES('replication_num' = '1');
+ """
+
+ sql "SET time_zone = 'Asia/Shanghai';"
+ sql """
+ INSERT INTO tz_dst_gap VALUES
+ (1, 'named_gap_ny', '2024-03-10 02:30:00 America/New_York'),
+ (2, 'explicit_before_gap', '2024-03-10 01:30:00 -05:00'),
+ (3, 'explicit_after_gap', '2024-03-10 03:30:00 -04:00');
+ """
+
+ sql "SET time_zone = 'America/New_York';"
+ sql """
+ INSERT INTO tz_dst_gap VALUES
+ (4, 'implicit_gap_ny', '2024-03-10 02:30:00'),
+ (5, 'implicit_after_gap', '2024-03-10 03:30:00');
+ """
+
+ sql "SET time_zone = '+00:00';"
+ qt_sql """
+ SELECT id, label, CAST(ts_tz AS VARCHAR(64)) AS rendered_utc
+ FROM tz_dst_gap
+ ORDER BY id;
+ """
+
+ qt_sql """
+ SELECT t1.ts_tz = t2.ts_tz AS named_and_implicit_gap_match
+ FROM tz_dst_gap t1, tz_dst_gap t2
+ WHERE t1.id = 1 AND t2.id = 4;
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]