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]

Reply via email to