From 885d554a9123c728d1753ac9c7d40597b97ef923 Mon Sep 17 00:00:00 2001 From: zhaochangle Date: Wed, 29 Apr 2026 17:26:00 +0800 Subject: [PATCH] 1 --- .../expressions/literal/DateLiteral.java | 2 +- .../expressions/literal/DateTimeLiteral.java | 66 +++++++------------ .../timestamptz/test_timestamptz_dst_gap.out | 11 ++++ .../test_timestamptz_dst_gap.groovy | 61 +++++++++++++++++ 4 files changed, 98 insertions(+), 42 deletions(-) create mode 100644 regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_gap.out create mode 100644 regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_gap.groovy 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 68195a0887ba36..df42c3709497b4 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 @@ -376,7 +376,7 @@ protected void init(String s) throws AnalysisException { } 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 6fe85d077ee0e0..d6efcb57e3cb32 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.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 static Result parseDateTimeLiteral(St 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 @@ protected void init(String s) throws AnalysisException { 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 @@ protected void init(String s) throws AnalysisException { } } + 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 00000000000000..ae3b0c924d063b --- /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 00000000000000..0a1e2337d55a35 --- /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; + """ +}