This is an automated email from the ASF dual-hosted git repository.

zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new a9ca36f68ee [fix](timestamptz) Preserve correct DST fold branch to go 
cross the transition point (#63034)
a9ca36f68ee is described below

commit a9ca36f68eec67dd0363e211b55319562178315e
Author: zclllyybb <[email protected]>
AuthorDate: Fri May 8 20:46:59 2026 +0800

    [fix](timestamptz) Preserve correct DST fold branch to go cross the 
transition point (#63034)
    
    Problem Summary: TIMESTAMPTZ values in a DST fold hour could be
    converted through an ambiguous local time, making explicit post-fold
    instants use the earlier branch during FE literal handling and BE
    floor/ceil/date_trunc execution.
    
    The old code would treat DST repeated hours like `2024-11-03 01:xx` as
    local civil time without offset when parsing TIMESTAMPTZ with explicit
    offset in FE, and when BE converts local time to UTC using
    date_trunc/floor/ceil, it would lose the `-04/-05` offset branch
    information and default to the earlier branch. After the fix, FE
    directly constructs TimestampTzLiteral from timezone-aware strings, and
    BE uses the original UTC instant's offset to select pre/post branches
    when converting repeated hours back to UTC, thus preserving the correct
    fold branch.
    
    when `time_zone = 'America/New_York'`:
    ```sql
    SELECT
    CAST(CAST('2024-11-03 01:05:00 -05:00' AS TIMESTAMPTZ(6)) AS VARCHAR(64)) 
AS ts,
    CAST(date_trunc(CAST('2024-11-03 01:05:30 -05:00' AS TIMESTAMPTZ(6)), 
'hour') AS VARCHAR(64)) AS hour_trunc;
    ```
    
    before:
    ```
    ts                                      hour_trunc
    2024-11-03 01:05:00.000000-04:00       2024-11-03 01:00:00.000000-04:00
    ```
    
    now:
    ```
    ts                                      hour_trunc
    2024-11-03 01:05:00.000000-05:00       2024-11-03 01:00:00.000000-05:00
    ```
---
 be/src/core/value/timestamptz_value.cpp            |  32 +++++++
 be/src/core/value/timestamptz_value.h              |   7 +-
 .../function/function_datetime_floor_ceil.cpp      |  19 ++--
 .../function/function_other_types_to_date.cpp      |   8 +-
 .../expressions/literal/StringLikeLiteral.java     |  12 ++-
 .../doris/nereids/util/TypeCoercionUtils.java      |  29 ++++--
 .../doris/nereids/util/TypeCoercionUtilsTest.java  |  18 ++++
 .../timestamptz/test_timestamptz_dst_fold.out      |  17 ++++
 .../test_timestamptz_storage_agg_key.out           |   9 ++
 .../sql-functions/doc_date_functions_test.out      |   2 +-
 .../datetime_functions/test_date_function_v2.out   |   6 +-
 .../timestamptz/test_timestamptz_dst_fold.groovy   | 103 +++++++++++++++++++++
 12 files changed, 235 insertions(+), 27 deletions(-)

diff --git a/be/src/core/value/timestamptz_value.cpp 
b/be/src/core/value/timestamptz_value.cpp
index 40ca96bdf7f..ffa9bf530e2 100644
--- a/be/src/core/value/timestamptz_value.cpp
+++ b/be/src/core/value/timestamptz_value.cpp
@@ -135,4 +135,36 @@ void TimestampTzValue::convert_local_to_utc(const 
cctz::time_zone& local_time_zo
                                dt.microsecond());
 }
 
+int TimestampTzValue::utc_offset(const cctz::time_zone& local_time_zone) const 
{
+    cctz::civil_second utc_cs(_utc_dt.year(), _utc_dt.month(), _utc_dt.day(), 
_utc_dt.hour(),
+                              _utc_dt.minute(), _utc_dt.second());
+    cctz::time_point<cctz::seconds> utc_tp = cctz::convert(utc_cs, 
cctz::utc_time_zone());
+    return local_time_zone.lookup(utc_tp).offset;
+}
+
+void TimestampTzValue::convert_local_to_utc(const cctz::time_zone& 
local_time_zone,
+                                            const 
DateV2Value<DateTimeV2ValueType>& dt,
+                                            int preferred_offset) {
+    cctz::civil_second local_cs(dt.year(), dt.month(), dt.day(), dt.hour(), 
dt.minute(),
+                                dt.second());
+    const auto lookup = local_time_zone.lookup(local_cs);
+    cctz::time_point<cctz::seconds> local_tp = cctz::convert(local_cs, 
local_time_zone);
+
+    if (lookup.kind == cctz::time_zone::civil_lookup::REPEATED) {
+        const auto pre_offset = local_time_zone.lookup(lookup.pre).offset;
+        const auto post_offset = local_time_zone.lookup(lookup.post).offset;
+        if (preferred_offset == pre_offset) {
+            local_tp = lookup.pre;
+        } else if (preferred_offset == post_offset) {
+            local_tp = lookup.post;
+        }
+    }
+
+    auto utc_cs = cctz::convert(local_tp, cctz::utc_time_zone());
+    _utc_dt.unchecked_set_time((uint16_t)utc_cs.year(), 
(uint8_t)utc_cs.month(),
+                               (uint8_t)utc_cs.day(), (uint8_t)utc_cs.hour(),
+                               (uint8_t)utc_cs.minute(), 
(uint8_t)utc_cs.second(),
+                               dt.microsecond());
+}
+
 } // namespace doris
diff --git a/be/src/core/value/timestamptz_value.h 
b/be/src/core/value/timestamptz_value.h
index 3ccf0426fde..1a0fa21f004 100644
--- a/be/src/core/value/timestamptz_value.h
+++ b/be/src/core/value/timestamptz_value.h
@@ -151,6 +151,11 @@ public:
     void convert_local_to_utc(const cctz::time_zone& local_time_zone,
                               const DateV2Value<DateTimeV2ValueType>& dt);
 
+    int utc_offset(const cctz::time_zone& local_time_zone) const;
+
+    void convert_local_to_utc(const cctz::time_zone& local_time_zone,
+                              const DateV2Value<DateTimeV2ValueType>& dt, int 
preferred_offset);
+
     TimestampTzValue& operator++() {
         ++_utc_dt;
         return *this;
@@ -197,4 +202,4 @@ struct std::hash<doris::TimestampTzValue> {
         auto int_val = v.to_date_int_val();
         return doris::HashUtil::hash(&int_val, sizeof(int_val), 0);
     }
-};
\ No newline at end of file
+};
diff --git a/be/src/exprs/function/function_datetime_floor_ceil.cpp 
b/be/src/exprs/function/function_datetime_floor_ceil.cpp
index 94e073f9e12..8863ea42576 100644
--- a/be/src/exprs/function/function_datetime_floor_ceil.cpp
+++ b/be/src/exprs/function/function_datetime_floor_ceil.cpp
@@ -771,18 +771,13 @@ struct DateTimeFloorCeilCore {
         // For TimestampTzValue on date-based units, convert result from local 
time back to UTC
         if constexpr (need_tz_conversion) {
             if (result) {
-                cctz::civil_second local_result_cs(ts_res.year(), 
ts_res.month(), ts_res.day(),
-                                                   ts_res.hour(), 
ts_res.minute(), ts_res.second());
-                cctz::time_point<cctz::sys_seconds> local_tp = 
cctz::convert(local_result_cs, tz);
-                auto utc_result_cs = cctz::convert(local_tp, 
cctz::utc_time_zone());
-
-                
ts_origin.unchecked_set_time(static_cast<uint16_t>(utc_result_cs.year()),
-                                             
static_cast<uint8_t>(utc_result_cs.month()),
-                                             
static_cast<uint8_t>(utc_result_cs.day()),
-                                             
static_cast<uint8_t>(utc_result_cs.hour()),
-                                             
static_cast<uint8_t>(utc_result_cs.minute()),
-                                             
static_cast<uint8_t>(utc_result_cs.second()),
-                                             ts_res.microsecond());
+                DateV2Value<DateTimeV2ValueType> 
local_result(ts_res.to_date_int_val());
+                if constexpr (Flag::Unit == HOUR || Flag::Unit == MINUTE) {
+                    const int preferred_offset = ts_arg.utc_offset(tz);
+                    ts_origin.convert_local_to_utc(tz, local_result, 
preferred_offset);
+                } else {
+                    ts_origin.convert_local_to_utc(tz, local_result);
+                }
             }
         }
 
diff --git a/be/src/exprs/function/function_other_types_to_date.cpp 
b/be/src/exprs/function/function_other_types_to_date.cpp
index 0ccfb8f7e53..002112993fe 100644
--- a/be/src/exprs/function/function_other_types_to_date.cpp
+++ b/be/src/exprs/function/function_other_types_to_date.cpp
@@ -551,7 +551,13 @@ private:
                 DateV2Value<DateTimeV2ValueType> local_dt;
                 dt.convert_utc_to_local(timezone, local_dt);
                 local_dt.template datetime_trunc<Unit>();
-                dt.convert_local_to_utc(timezone, local_dt);
+                if constexpr (Unit == TimeUnit::SECOND || Unit == 
TimeUnit::MINUTE ||
+                              Unit == TimeUnit::HOUR) {
+                    const int preferred_offset = dt.utc_offset(timezone);
+                    dt.convert_local_to_utc(timezone, local_dt, 
preferred_offset);
+                } else {
+                    dt.convert_local_to_utc(timezone, local_dt);
+                }
             } else {
                 dt.template datetime_trunc<Unit>();
             }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteral.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteral.java
index 4e9d395415f..621b8d6fa2b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteral.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteral.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.exceptions.CastException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeExtractAndTransform;
+import 
org.apache.doris.nereids.trees.expressions.literal.format.DateTimeChecker;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.DateTimeType;
 import org.apache.doris.nereids.types.DateTimeV2Type;
@@ -147,10 +148,19 @@ public abstract class StringLikeLiteral extends Literal 
implements ComparableLit
             return new DateTimeLiteral((DateTimeType) targetType, 
datetime.year, datetime.month, datetime.day,
                     datetime.hour, datetime.minute, datetime.second, 
datetime.microSecond);
         } else if (targetType.isTimeStampTzType()) {
+            // Explicit offsets must not round-trip through session local 
time; that loses the selected
+            // branch in DST fold hours. Wildcard targets still need a 
concrete scale before parsing.
+            TimeStampTzType timeStampTzType = (TimeStampTzType) targetType;
+            if (timeStampTzType.getScale() < 0) {
+                timeStampTzType = TimeStampTzType.forTypeFromString(value);
+            }
+            if (DateTimeChecker.hasTimeZone(value)) {
+                return new TimestampTzLiteral(timeStampTzType, value);
+            }
             DateTimeV2Literal expression = castToDateTime(DateTimeV2Type.MAX, 
strictCast, true);
             expression = (DateTimeV2Literal) 
(DateTimeExtractAndTransform.convertTz(expression,
                     new 
StringLiteral(ConnectContext.get().getSessionVariable().timeZone), new 
StringLiteral("UTC")));
-            return new TimestampTzLiteral((TimeStampTzType) targetType, 
expression.year, expression.month,
+            return new TimestampTzLiteral(timeStampTzType, expression.year, 
expression.month,
                     expression.day, expression.hour, expression.minute, 
expression.second, expression.microSecond);
         } else if (targetType.isDateTimeV2Type()) {
             return castToDateTime(targetType, strictCast, true);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java
index ca7b04d7ed6..2970e9bbe46 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java
@@ -638,15 +638,26 @@ public class TypeCoercionUtils {
                     && DateTimeChecker.isValidDateTime(value)) {
                 ret = DateTimeLiteral.parseDateTimeLiteral(value, 
true).orElse(null);
             } else if (dataType.isTimeStampTzType() && 
DateTimeChecker.isValidDateTime(value)) {
-                DateTimeV2Literal dtV2Lit = (DateTimeV2Literal) DateTimeLiteral
-                                .parseDateTimeLiteral(value, 
true).orElse(null);
-                if (dtV2Lit != null) {
-                    dtV2Lit = (DateTimeV2Literal) 
(DateTimeExtractAndTransform.convertTz(
-                            dtV2Lit,
-                            new 
StringLiteral(ConnectContext.get().getSessionVariable().timeZone),
-                            new StringLiteral("UTC")));
-                    ret = new TimestampTzLiteral(dtV2Lit.getYear(), 
dtV2Lit.getMonth(), dtV2Lit.getDay(),
-                            dtV2Lit.getHour(), dtV2Lit.getMinute(), 
dtV2Lit.getSecond(), dtV2Lit.getMicroSecond());
+                if (DateTimeChecker.hasTimeZone(value)) {
+                    // Signature search can pass TIMESTAMPTZ(*) here. 
TimestampTzLiteral rounds by scale,
+                    // so derive a concrete scale from the literal before 
preserving its explicit offset.
+                    TimeStampTzType timeStampTzType = (TimeStampTzType) 
dataType;
+                    if (timeStampTzType.getScale() < 0) {
+                        timeStampTzType = 
TimeStampTzType.forTypeFromString(value);
+                    }
+                    ret = new TimestampTzLiteral(timeStampTzType, value);
+                } else {
+                    DateTimeV2Literal dtV2Lit = (DateTimeV2Literal) 
DateTimeLiteral
+                                    .parseDateTimeLiteral(value, 
true).orElse(null);
+                    if (dtV2Lit != null) {
+                        dtV2Lit = (DateTimeV2Literal) 
(DateTimeExtractAndTransform.convertTz(
+                                dtV2Lit,
+                                new 
StringLiteral(ConnectContext.get().getSessionVariable().timeZone),
+                                new StringLiteral("UTC")));
+                        ret = new TimestampTzLiteral(dtV2Lit.getYear(), 
dtV2Lit.getMonth(), dtV2Lit.getDay(),
+                                dtV2Lit.getHour(), dtV2Lit.getMinute(), 
dtV2Lit.getSecond(),
+                                dtV2Lit.getMicroSecond());
+                    }
                 }
             } else if ((dataType.isDateV2Type() || dataType.isDateType()) && 
DateTimeChecker.isValidDateTime(value)) {
                 Result<DateLiteral, AnalysisException> parseResult = 
DateV2Literal.parseDateLiteral(value, true);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/TypeCoercionUtilsTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/TypeCoercionUtilsTest.java
index 7aa41181374..d2ead4e326d 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/TypeCoercionUtilsTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/TypeCoercionUtilsTest.java
@@ -63,10 +63,12 @@ import org.apache.doris.nereids.types.QuantileStateType;
 import org.apache.doris.nereids.types.SmallIntType;
 import org.apache.doris.nereids.types.StringType;
 import org.apache.doris.nereids.types.StructType;
+import org.apache.doris.nereids.types.TimeStampTzType;
 import org.apache.doris.nereids.types.TimeV2Type;
 import org.apache.doris.nereids.types.TinyIntType;
 import org.apache.doris.nereids.types.VarcharType;
 import org.apache.doris.nereids.types.coercion.IntegralType;
+import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.collect.ImmutableList;
 import org.junit.jupiter.api.Assertions;
@@ -286,6 +288,22 @@ public class TypeCoercionUtilsTest {
         // datetime
         Assertions.assertEquals(DateTimeV2Type.SYSTEM_DEFAULT,
                                 
TypeCoercionUtils.characterLiteralTypeCoercion("2020-02-02", 
DateTimeType.INSTANCE).get().getDataType());
+        // timestamptz wildcard
+        Assertions.assertEquals(TimeStampTzType.SYSTEM_DEFAULT,
+                
TypeCoercionUtils.characterLiteralTypeCoercion("2023-08-17T01:41:18Z", 
TimeStampTzType.WILDCARD)
+                        .get().getDataType());
+        // No-zone TIMESTAMPTZ coercion uses the session timezone to define 
the local civil time.
+        ConnectContext connectContext = new ConnectContext();
+        connectContext.getSessionVariable().setTimeZone("Asia/Shanghai");
+        connectContext.setThreadLocalInfo();
+        try {
+            // timestamptz without explicit timezone keeps the literal scale 
during signature search
+            Assertions.assertEquals(TimeStampTzType.SYSTEM_DEFAULT,
+                    
TypeCoercionUtils.characterLiteralTypeCoercion("2004-12-31", 
TimeStampTzType.MAX)
+                            .get().getDataType());
+        } finally {
+            ConnectContext.remove();
+        }
     }
 
     @Test
diff --git 
a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_fold.out 
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_fold.out
new file mode 100644
index 00000000000..a2dba24a802
--- /dev/null
+++ b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_dst_fold.out
@@ -0,0 +1,17 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !sql --
+1      pre_fold_utc    2024-11-03 01:05:00.000000-04:00
+2      post_fold_utc   2024-11-03 01:05:00.000000-05:00
+3      pre_explicit    2024-11-03 01:05:00.000000-04:00
+4      post_explicit   2024-11-03 01:05:00.000000-05:00
+
+-- !sql --
+2      post_fold_utc
+4      post_explicit
+
+-- !sql --
+4      2024-11-03 01:00:00.000000-05:00        2024-11-03 
01:10:00.000000-05:00        2024-11-03 01:00:00.000000-05:00        2024-11-03 
01:05:00.000000-05:00        2024-11-03 01:05:00.000000-05:00        2024-11-03 
01:00:00.000000-05:00
+
+-- !sql --
+2024-11-03 06:05:00.000000+00:00       2024-11-03 06:05:00.000000+00:00        
2024-11-03 06:00:00.000000+00:00
+
diff --git 
a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_storage_agg_key.out
 
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_storage_agg_key.out
index 4b519c03308..d57bd6e3deb 100644
--- 
a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_storage_agg_key.out
+++ 
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_storage_agg_key.out
@@ -458,6 +458,15 @@
 2025-12-12 12:12:12.123456+08:00       3023-09-09 16:09:09.000000+08:00        
3023-09-09 16:09:09.000000+08:00        1023-09-09 16:09:09.000000+08:00        
3023-09-09 16:09:09.000000+08:00
 
 -- !scale_not_in --
+0000-01-01 08:00:00.123456+08:00       0000-01-01 08:00:00.000000+08:00        
0000-01-01 08:00:00.000000+08:00        0000-01-01 08:00:00.000000+08:00        
0000-01-01 08:00:00.000000+08:00
+0000-01-01 08:00:00.999999+08:00       0000-01-01 08:00:00.000000+08:00        
0000-01-01 08:00:00.000000+08:00        0000-01-01 08:00:00.000000+08:00        
0000-01-01 08:00:00.000000+08:00
+2025-12-12 12:12:12.000000+08:00       3023-09-09 16:09:09.000000+08:00        
2023-09-09 16:09:09.000000+08:00        2023-09-09 16:09:09.000000+08:00        
2023-09-09 16:09:09.000000+08:00
+2025-12-12 12:12:12.000001+08:00       3023-09-09 16:09:09.000000+08:00        
3023-09-09 16:09:09.000000+08:00        1023-09-09 16:09:09.000000+08:00        
3023-09-09 16:09:09.000000+08:00
+2025-12-12 12:12:12.999999+08:00       3023-09-09 16:09:09.000000+08:00        
3023-09-09 16:09:09.000000+08:00        1023-09-09 16:09:09.000000+08:00        
3023-09-09 16:09:09.000000+08:00
+9999-12-31 23:59:59.000000+08:00       0000-01-01 08:00:00.000000+08:00        
0000-01-01 08:00:00.000000+08:00        0000-01-01 08:00:00.000000+08:00        
2023-04-05 07:59:59.000000+08:00
+9999-12-31 23:59:59.000001+08:00       0000-01-01 08:00:00.000000+08:00        
9999-12-31 23:59:59.999999+08:00        0000-01-01 08:00:00.000000+08:00        
9999-12-31 23:59:59.999999+08:00
+9999-12-31 23:59:59.123456+08:00       0000-01-01 08:00:00.000000+08:00        
9999-12-31 23:59:59.999999+08:00        0000-01-01 08:00:00.000000+08:00        
9999-12-31 23:59:59.999999+08:00
+9999-12-31 23:59:59.999999+08:00       0000-01-01 08:00:00.000000+08:00        
9999-12-31 23:59:59.999999+08:00        0000-01-01 08:00:00.000000+08:00        
9999-12-31 23:59:59.999999+08:00
 
 -- !scale_is_null --
 \N     2025-12-12 12:12:12.000000+08:00        2025-12-12 
12:12:12.000000+08:00        0000-01-01 08:00:00.000000+08:00        2025-12-12 
12:12:12.000000+08:00
diff --git 
a/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out 
b/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
index 06b53aebae1..63cdecd6d68 100644
--- 
a/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
+++ 
b/regression-test/data/doc/sql-manual/sql-functions/doc_date_functions_test.out
@@ -2097,7 +2097,7 @@ da fanadur
 1196389819
 
 -- !unix_timestamp_4 --
-1196386219.000000
+1196386219
 
 -- !unix_timestamp_5 --
 1196389819.000000
diff --git 
a/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function_v2.out
 
b/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function_v2.out
index 3a9a3534780..25e5ac66919 100644
--- 
a/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function_v2.out
+++ 
b/regression-test/data/query_p0/sql_functions/datetime_functions/test_date_function_v2.out
@@ -121,6 +121,7 @@
 
 -- !get_format_time_4 --
 \N
+
 -- !sql_addtime1 --
 2023-10-14T22:35:22
 
@@ -154,10 +155,10 @@
 2025-06-30T17:15:30.999999
 
 -- !sql_addtime9 --
-2025-10-10T17:24:36+08:00
+2025-10-10 17:24:36+08:00
 
 -- !sql_addtime10 --
-2025-10-10T17:24:36.123457+08:00
+2025-10-10 17:24:36.123457+08:00
 
 -- !sql_addtime11 --
 44:22:32
@@ -220,3 +221,4 @@
 
 -- !sql_subtime14 --
 0001-01-01 07:59:59.999999+08:00
+
diff --git 
a/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_fold.groovy
 
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_fold.groovy
new file mode 100644
index 00000000000..a090b6f0957
--- /dev/null
+++ 
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_dst_fold.groovy
@@ -0,0 +1,103 @@
+// 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_fold") {
+    sql "SET enable_nereids_planner = true;"
+    sql "SET enable_fallback_to_original_planner = false;"
+
+    sql "DROP TABLE IF EXISTS tz_dst_fold_events;"
+    sql "DROP TABLE IF EXISTS tz_dst_fold_trunc_out;"
+    sql """
+        CREATE TABLE tz_dst_fold_events (
+            id INT,
+            label VARCHAR(64),
+            ts TIMESTAMPTZ(6)
+        )
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES('replication_num' = '1');
+    """
+
+    sql "SET time_zone = 'America/New_York';"
+    sql """
+        INSERT INTO tz_dst_fold_events VALUES
+        (1, 'pre_fold_utc',  CAST('2024-11-03 05:05:00 +00:00' AS 
TIMESTAMPTZ(6))),
+        (2, 'post_fold_utc', CAST('2024-11-03 06:05:00 +00:00' AS 
TIMESTAMPTZ(6))),
+        (3, 'pre_explicit',  CAST('2024-11-03 01:05:00 -04:00' AS 
TIMESTAMPTZ(6))),
+        (4, 'post_explicit', CAST('2024-11-03 01:05:00 -05:00' AS 
TIMESTAMPTZ(6)));
+    """
+
+    sql "SET debug_skip_fold_constant = true;"
+    qt_sql """
+        SELECT id, label, CAST(ts AS VARCHAR(64)) AS rendered
+        FROM tz_dst_fold_events
+        ORDER BY id;
+    """
+
+    sql "SET debug_skip_fold_constant = false;"
+    qt_sql """
+        SELECT id, label
+        FROM tz_dst_fold_events
+        WHERE ts = CAST('2024-11-03 01:05:00 -05:00' AS TIMESTAMPTZ(6))
+        ORDER BY id;
+    """
+
+    sql "SET debug_skip_fold_constant = true;"
+    qt_sql """
+        SELECT id,
+               CAST(minute_floor(ts, 10) AS VARCHAR(64)) AS 
minute_floor_rendered,
+               CAST(minute_ceil(ts, 10) AS VARCHAR(64)) AS 
minute_ceil_rendered,
+               CAST(hour_floor(ts, 1) AS VARCHAR(64)) AS hour_floor_rendered,
+               CAST(date_trunc(ts, 'second') AS VARCHAR(64)) AS 
second_trunc_rendered,
+               CAST(date_trunc(ts, 'minute') AS VARCHAR(64)) AS 
minute_trunc_rendered,
+               CAST(date_trunc(ts, 'hour') AS VARCHAR(64)) AS 
hour_trunc_rendered
+        FROM tz_dst_fold_events
+        WHERE id = 4
+        ORDER BY id;
+    """
+
+    sql """
+        CREATE TABLE tz_dst_fold_trunc_out (
+            second_trunc TIMESTAMPTZ(6),
+            minute_trunc TIMESTAMPTZ(6),
+            hour_trunc TIMESTAMPTZ(6)
+        )
+        DUPLICATE KEY(second_trunc)
+        DISTRIBUTED BY HASH(second_trunc) BUCKETS 1
+        PROPERTIES('replication_num' = '1');
+    """
+    sql """
+        INSERT INTO tz_dst_fold_trunc_out
+        SELECT date_trunc(ts, 'second'),
+               date_trunc(ts, 'minute'),
+               date_trunc(ts, 'hour')
+        FROM tz_dst_fold_events
+        WHERE id = 4;
+    """
+
+    sql "SET time_zone = '+00:00';"
+    qt_sql """
+        SELECT CAST(second_trunc AS VARCHAR(64)) AS stored_second_utc,
+               CAST(minute_trunc AS VARCHAR(64)) AS stored_minute_utc,
+               CAST(hour_trunc AS VARCHAR(64)) AS stored_hour_utc
+        FROM tz_dst_fold_trunc_out
+        ORDER BY 1, 2, 3;
+    """
+
+    sql "SET time_zone = default;"
+    sql "SET debug_skip_fold_constant = false;"
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to