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

gavinchou 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 f631250a110 [fix](fe) Accept spaced TIMESTAMPTZ offsets in range 
partition bounds (#64795)
f631250a110 is described below

commit f631250a1105f6d4f310d50f81db016557a23a4a
Author: starocean999 <[email protected]>
AuthorDate: Fri Jul 3 16:05:48 2026 +0800

    [fix](fe) Accept spaced TIMESTAMPTZ offsets in range partition bounds 
(#64795)
---
 .../org/apache/doris/catalog/PartitionKey.java     |  20 +-
 .../doris/clone/DynamicPartitionScheduler.java     |  45 +++-
 .../doris/mtmv/MTMVPartitionExprDateTrunc.java     |  21 +-
 .../expressions/literal/StringLikeLiteral.java     |  12 +-
 .../expressions/literal/TimestampTzLiteral.java    | 162 ++++++-----
 .../doris/nereids/util/TypeCoercionUtils.java      |  28 +-
 .../doris/analysis/PartitionExprUtilTest.java      |  44 +++
 .../apache/doris/catalog/CreateTableLikeTest.java  | 157 +++++++++++
 .../doris/catalog/DynamicPartitionTableTest.java   | 299 +++++++++++++++++++++
 .../org/apache/doris/catalog/PartitionKeyTest.java | 158 +++++++++++
 ...TMVRelatedPartitionDescRollUpGeneratorTest.java |  72 +++++
 .../expressions/literal/StringLikeLiteralTest.java |  41 +++
 .../literal/TimestampTzLiteralTest.java            | 122 +++++++++
 ...est_timestamptz_partition_boundary_timezone.out |  26 ++
 ..._timestamptz_partition_boundary_timezone.groovy | 257 ++++++++++++++++++
 15 files changed, 1353 insertions(+), 111 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionKey.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionKey.java
index ad8819567cb..3832876cd84 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionKey.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionKey.java
@@ -31,16 +31,13 @@ import org.apache.doris.analysis.ToSqlParams;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
-import 
org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeExtractAndTransform;
 import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.TimeStampTzType;
 import org.apache.doris.persist.gson.GsonUtils;
-import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
@@ -147,14 +144,7 @@ public class PartitionKey implements 
Comparable<PartitionKey>, Writable {
             } else if (type.isDatetimeV2()) {
                 return new DateTimeV2Literal(value);
             } else if (type.isTimeStampTz()) {
-                DateTimeV2Literal literal = new DateTimeV2Literal(value);
-                DateTimeV2Literal dtV2Lit = (DateTimeV2Literal) 
(DateTimeExtractAndTransform.convertTz(
-                        literal,
-                        new 
StringLiteral(ConnectContext.get().getSessionVariable().timeZone),
-                        new StringLiteral("UTC")));
-                return new TimestampTzLiteral((TimeStampTzType) 
DataType.fromCatalogType(type),
-                        dtV2Lit.getYear(), dtV2Lit.getMonth(), 
dtV2Lit.getDay(),
-                        dtV2Lit.getHour(), dtV2Lit.getMinute(), 
dtV2Lit.getSecond(), dtV2Lit.getMicroSecond());
+                return 
TimestampTzLiteral.fromSessionTimeZone((TimeStampTzType) 
DataType.fromCatalogType(type), value);
 
             }
         } catch (Exception e) {
@@ -199,6 +189,14 @@ public class PartitionKey implements 
Comparable<PartitionKey>, Writable {
         for (int i = 0; i < values.size(); i++) {
             if (values.get(i).isNullPartition()) {
                 partitionKey.keys.add(NullLiteral.create(types.get(i)));
+            } else if (types.get(i).isTimeStampTz()) {
+                // Route TIMESTAMPTZ through the Nereids-aware parser (same as
+                // createPartitionKey) so that named/lowercase timezone values
+                // like "2024-01-15 20:00:00 Asia/Shanghai" are parsed 
correctly.
+                // The legacy DateLiteralUtils path only recognizes uppercase
+                // timezone names and a subset of offsets.
+                Literal dateTimeLiteral = 
getDateTimeLiteral(values.get(i).getStringValue(), types.get(i));
+                partitionKey.keys.add(dateTimeLiteral.toLegacyLiteral());
             } else {
                 partitionKey.keys.add(values.get(i).getValue(types.get(i)));
             }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java
 
b/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java
index 355a88aeb90..4367b27cc39 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java
@@ -36,8 +36,10 @@ import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Partition;
 import org.apache.doris.catalog.PartitionItem;
 import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.RangePartitionInfo;
 import org.apache.doris.catalog.RangePartitionItem;
+import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.catalog.Table;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
@@ -54,8 +56,10 @@ import org.apache.doris.common.util.RangeUtils;
 import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.datasource.InternalCatalog;
 import org.apache.doris.meta.MetaContext;
+import org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral;
 import org.apache.doris.nereids.trees.plans.commands.info.AddPartitionOp;
 import org.apache.doris.nereids.trees.plans.commands.info.DropPartitionOp;
+import org.apache.doris.nereids.types.TimeStampTzType;
 import org.apache.doris.nereids.util.DateUtils;
 import org.apache.doris.persist.PartitionPersistInfo;
 import org.apache.doris.thrift.TStorageMedium;
@@ -81,6 +85,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TimeZone;
 import java.util.stream.Collectors;
 
 /**
@@ -302,6 +307,12 @@ public class DynamicPartitionScheduler extends 
MasterDaemon {
                     dynamicPartitionProperty, now, idx, partitionFormat);
             String nextBorder = DynamicPartitionUtil.getPartitionRangeString(
                     dynamicPartitionProperty, now, idx + 1, partitionFormat);
+            // Save original border for partition name (must not contain UTC 
suffix)
+            String prevBorderForName = prevBorder;
+            prevBorder = convertToUtcTimestamp(partitionColumn, prevBorder,
+                    dynamicPartitionProperty.getTimeZone());
+            nextBorder = convertToUtcTimestamp(partitionColumn, nextBorder,
+                    dynamicPartitionProperty.getTimeZone());
             PartitionValue lowerValue = new PartitionValue(prevBorder);
             PartitionValue upperValue = new PartitionValue(nextBorder);
 
@@ -370,7 +381,7 @@ public class DynamicPartitionScheduler extends MasterDaemon 
{
 
             String partitionName = dynamicPartitionProperty.getPrefix()
                     + 
DynamicPartitionUtil.getFormattedPartitionName(dynamicPartitionProperty.getTimeZone(),
-                    prevBorder, dynamicPartitionProperty.getTimeUnit());
+                    prevBorderForName, dynamicPartitionProperty.getTimeUnit());
             SinglePartitionDesc rangePartitionDesc = new 
SinglePartitionDesc(true, partitionName,
                     partitionKeyDesc, partitionProperties);
 
@@ -483,9 +494,29 @@ public class DynamicPartitionScheduler extends 
MasterDaemon {
         partitionProperties.put(PropertyAnalyzer.PROPERTIES_DATA_BASE_TIME, 
baseTime);
     }
 
+    /**
+     * For TIMESTAMPTZ partition columns, convert the border string from the 
partition's
+     * timezone to a UTC timestamp string (with +00:00 suffix). This ensures 
partition
+     * boundaries are stored as UTC timestamps, so the partition range aligns 
to
+     * 00:00:00—24:00:00 in UTC regardless of the configured partition 
timezone.
+     */
+    private static String convertToUtcTimestamp(Column column, String border, 
TimeZone timeZone) {
+        if (column.getDataType() == PrimitiveType.TIMESTAMPTZ) {
+            TimestampTzLiteral utcLiteral = TimestampTzLiteral.fromTimeZone(
+                    TimeStampTzType.of(((ScalarType) 
column.getType()).getScalarScale()),
+                    border, timeZone.toZoneId().toString());
+            return utcLiteral.getStringValue();
+        }
+        return border;
+    }
+
     private Range<PartitionKey> getClosedRange(Database db, OlapTable 
olapTable, Column partitionColumn,
             String partitionFormat, String lowerBorderOfReservedHistory, 
String upperBorderOfReservedHistory) {
         Range<PartitionKey> reservedHistoryPartitionKeyRange = null;
+        lowerBorderOfReservedHistory = convertToUtcTimestamp(partitionColumn, 
lowerBorderOfReservedHistory,
+                
olapTable.getTableProperty().getDynamicPartitionProperty().getTimeZone());
+        upperBorderOfReservedHistory = convertToUtcTimestamp(partitionColumn, 
upperBorderOfReservedHistory,
+                
olapTable.getTableProperty().getDynamicPartitionProperty().getTimeZone());
         PartitionValue lowerBorderPartitionValue = new 
PartitionValue(lowerBorderOfReservedHistory);
         PartitionValue upperBorderPartitionValue = new 
PartitionValue(upperBorderOfReservedHistory);
         try {
@@ -526,6 +557,10 @@ public class DynamicPartitionScheduler extends 
MasterDaemon {
                 now, realStart, partitionFormat);
         String limitBorder = 
DynamicPartitionUtil.getPartitionRangeString(dynamicPartitionProperty,
                 now, 0, partitionFormat);
+        lowerBorder = convertToUtcTimestamp(partitionColumn, lowerBorder,
+                dynamicPartitionProperty.getTimeZone());
+        limitBorder = convertToUtcTimestamp(partitionColumn, limitBorder,
+                dynamicPartitionProperty.getTimeZone());
         PartitionValue lowerPartitionValue = new PartitionValue(lowerBorder);
         PartitionValue limitPartitionValue = new PartitionValue(limitBorder);
         List<Range<PartitionKey>> reservedHistoryPartitionKeyRangeList = new 
ArrayList<Range<PartitionKey>>();
@@ -625,6 +660,14 @@ public class DynamicPartitionScheduler extends 
MasterDaemon {
         String partitionFormat = 
DynamicPartitionUtil.getPartitionFormat(partitionColumn);
         String currentTimeStr = 
DateTimeFormatter.ofPattern(partitionFormat).format(now);
 
+        // For TIMESTAMPTZ columns, normalize the cutoff time to UTC so it
+        // matches the stored partition bounds, which are also stored as UTC.
+        // Without this, a suffix-free currentTimeStr formatted in the JVM
+        // timezone would be parsed as UTC via 
TimestampTzLiteral.fromSessionTimeZone(),
+        // shifting the cutoff and potentially dropping the current UTC-day 
partition.
+        currentTimeStr = convertToUtcTimestamp(partitionColumn, currentTimeStr,
+                TimeZone.getTimeZone(DateUtils.getTimeZone()));
+
         PartitionValue currentTimeValue = new PartitionValue(currentTimeStr);
         PartitionKey currentTimeKey;
         try {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExprDateTrunc.java
 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExprDateTrunc.java
index 79e95fa56d9..6a23cabdb93 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExprDateTrunc.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionExprDateTrunc.java
@@ -34,7 +34,9 @@ import 
org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeE
 import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
 import org.apache.doris.nereids.trees.expressions.literal.DateV2Literal;
+import org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral;
+import 
org.apache.doris.nereids.trees.expressions.literal.format.DateTimeChecker;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
@@ -179,6 +181,14 @@ public class MTMVPartitionExprDateTrunc implements 
MTMVPartitionExprService {
     private DateTimeV2Literal strToDate(String value,
             Optional<String> dateFormat) throws AnalysisException {
         try {
+            if (DateTimeChecker.hasTimeZone(value)) {
+                // For TIMESTAMPTZ values, parse preserving UTC semantics.
+                // DateTimeV2Literal would convert to session timezone, which 
would
+                // produce incorrect MV partition boundaries when session tz 
!= UTC.
+                TimestampTzLiteral tzLiteral = new TimestampTzLiteral(value);
+                return new DateTimeV2Literal(tzLiteral.getYear(), 
tzLiteral.getMonth(), tzLiteral.getDay(),
+                        tzLiteral.getHour(), tzLiteral.getMinute(), 
tzLiteral.getSecond());
+            }
             return new DateTimeV2Literal(value);
         } catch (Exception e) {
             if (!dateFormat.isPresent()) {
@@ -237,11 +247,18 @@ public class MTMVPartitionExprDateTrunc implements 
MTMVPartitionExprService {
         if (partitionColumnType.isDate() || partitionColumnType.isDateV2()) {
             return String.format(PartitionExprUtil.DATE_FORMATTER, 
literal.getYear(), literal.getMonth(),
                     literal.getDay());
-        } else if (partitionColumnType.isDatetime() || 
partitionColumnType.isDatetimeV2()
-                || partitionColumnType.isTimeStampTz()) {
+        } else if (partitionColumnType.isDatetime() || 
partitionColumnType.isDatetimeV2()) {
             return String.format(PartitionExprUtil.DATETIME_FORMATTER,
                     literal.getYear(), literal.getMonth(), literal.getDay(),
                     literal.getHour(), literal.getMinute(), 
literal.getSecond());
+        } else if (partitionColumnType.isTimeStampTz()) {
+            // The internal DateTimeV2Literal values are always in UTC after 
truncation.
+            // Emit an explicit +00:00 suffix so that downstream consumers
+            // (PartitionKey.createPartitionKey -> 
TimestampTzLiteral.fromSessionTimeZone)
+            // interpret the value as UTC rather than session-local time.
+            return String.format(PartitionExprUtil.DATETIME_FORMATTER,
+                    literal.getYear(), literal.getMonth(), literal.getDay(),
+                    literal.getHour(), literal.getMinute(), 
literal.getSecond()) + "+00:00";
         } else {
             throw new AnalysisException(
                     "MTMV not support partition with column type : " + 
partitionColumnType);
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 621b8d6fa2b..148c3efb852 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
@@ -21,14 +21,12 @@ import org.apache.doris.analysis.LiteralExpr;
 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;
 import org.apache.doris.nereids.types.TimeStampTzType;
 import org.apache.doris.nereids.types.TimeV2Type;
-import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.SessionVariable;
 
 import com.google.common.base.Preconditions;
@@ -148,8 +146,7 @@ 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.
+            // Wildcard targets still need a concrete scale before parsing.
             TimeStampTzType timeStampTzType = (TimeStampTzType) targetType;
             if (timeStampTzType.getScale() < 0) {
                 timeStampTzType = TimeStampTzType.forTypeFromString(value);
@@ -157,11 +154,8 @@ public abstract class StringLikeLiteral extends Literal 
implements ComparableLit
             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, expression.year, 
expression.month,
-                    expression.day, expression.hour, expression.minute, 
expression.second, expression.microSecond);
+            DateTimeV2Literal datetime = castToDateTime(DateTimeV2Type.MAX, 
strictCast, true);
+            return TimestampTzLiteral.fromSessionTimeZone(timeStampTzType, 
datetime);
         } else if (targetType.isDateTimeV2Type()) {
             return castToDateTime(targetType, strictCast, true);
         } else if (targetType.isFloatType()) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteral.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteral.java
index f6a8c25a7da..7caed5ecc01 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteral.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteral.java
@@ -23,12 +23,17 @@ import 
org.apache.doris.nereids.exceptions.NotSupportedException;
 import org.apache.doris.nereids.exceptions.UnboundException;
 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.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DataType;
 import org.apache.doris.nereids.types.DateTimeV2Type;
 import org.apache.doris.nereids.types.TimeStampTzType;
 import org.apache.doris.qe.ConnectContext;
 
+import com.google.common.base.Preconditions;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
 import java.time.LocalDateTime;
 import java.util.Objects;
 
@@ -40,6 +45,8 @@ public class TimestampTzLiteral extends DateTimeLiteral {
     public static final TimestampTzLiteral USE_IN_FLOOR_CEIL
             = new TimestampTzLiteral(0001L, 01L, 01L, 0L, 0L, 0L, 0L);
 
+    private static final Logger LOG = 
LogManager.getLogger(TimestampTzLiteral.class);
+
     public TimestampTzLiteral(String s) {
         this(TimeStampTzType.forTypeFromString(s), s);
     }
@@ -64,6 +71,66 @@ public class TimestampTzLiteral extends DateTimeLiteral {
         roundMicroSecond(dateType.getScale());
     }
 
+    /**
+     * Build a TIMESTAMPTZ literal from a session-local datetime string.
+     * Strings with an explicit zone/offset keep their own timezone semantics;
+     * strings without one are interpreted in the current session timezone and 
converted to UTC.
+     */
+    public static TimestampTzLiteral fromSessionTimeZone(TimeStampTzType 
dateType, String s) {
+        if (DateTimeChecker.hasTimeZone(s)) {
+            return new TimestampTzLiteral(dateType, s);
+        }
+
+        return fromSessionTimeZone(dateType, new DateTimeV2Literal(s));
+    }
+
+    /**
+     * fromSessionTimeZone
+     */
+    public static TimestampTzLiteral fromSessionTimeZone(TimeStampTzType 
dateType, DateTimeV2Literal literal) {
+        return fromTimeZone(dateType, literal, getSessionTimeZone());
+    }
+
+    /**
+     * Build a TIMESTAMPTZ literal from a datetime string, explicitly 
specifying the source timezone.
+     * Strings with an explicit zone/offset keep their own timezone semantics;
+     * strings without one are converted from the given timeZone to UTC.
+     * @param timeZone Fallback timezone used only when the string has no 
explicit offset
+     */
+    public static TimestampTzLiteral fromTimeZone(TimeStampTzType dateType, 
String s, String timeZone) {
+        if (DateTimeChecker.hasTimeZone(s)) {
+            return new TimestampTzLiteral(dateType, s);
+        }
+        return fromTimeZone(dateType, new DateTimeV2Literal(s), timeZone);
+    }
+
+    /**
+     * Build a TIMESTAMPTZ literal from a DateTimeV2Literal, explicitly 
specifying the source timezone.
+     */
+    public static TimestampTzLiteral fromTimeZone(TimeStampTzType dateType, 
DateTimeV2Literal literal,
+            String timeZone) {
+        DateTimeV2Literal utcLiteral = (DateTimeV2Literal) 
DateTimeExtractAndTransform.convertTz(
+                literal,
+                new StringLiteral(timeZone),
+                new StringLiteral("UTC"));
+        return new TimestampTzLiteral(dateType,
+                utcLiteral.year,
+                utcLiteral.month,
+                utcLiteral.day,
+                utcLiteral.hour,
+                utcLiteral.minute,
+                utcLiteral.second,
+                utcLiteral.microSecond);
+    }
+
+    private static String getSessionTimeZone() {
+        ConnectContext context = ConnectContext.get();
+        if (context == null && LOG.isDebugEnabled()) {
+            LOG.debug("context is null, session time zone is fallback to UTC");
+        }
+        return context == null ? "UTC" : context.getSessionVariable().timeZone;
+    }
+
     @Override
     public TimeStampTzType getDataType() throws UnboundException {
         return (TimeStampTzType) super.getDataType();
@@ -92,72 +159,7 @@ public class TimestampTzLiteral extends DateTimeLiteral {
 
     @Override
     public String getStringValue() {
-        int scale = getDataType().getScale();
-        if (scale <= 0) {
-            return super.getStringValue();
-        }
-
-        if (0 <= year && year <= 9999 && 0 <= month && month <= 99 && 0 <= day 
&& day <= 99
-                && 0 <= hour && hour <= 99 && 0 <= minute && minute <= 99 && 0 
<= second && second <= 99
-                && 0 <= microSecond && microSecond <= MAX_MICROSECOND) {
-            char[] format = new char[] {
-                    '0', '0', '0', '0', '-', '0', '0', '-', '0', '0', ' ', 
'0', '0', ':', '0', '0', ':', '0', '0',
-                    '.', '0', '0', '0', '0', '0', '0'};
-            int offset = 3;
-            long year = this.year;
-            while (year > 0) {
-                format[offset--] = (char) ('0' + (year % 10));
-                year /= 10;
-            }
-
-            offset = 6;
-            long month = this.month;
-            while (month > 0) {
-                format[offset--] = (char) ('0' + (month % 10));
-                month /= 10;
-            }
-
-            offset = 9;
-            long day = this.day;
-            while (day > 0) {
-                format[offset--] = (char) ('0' + (day % 10));
-                day /= 10;
-            }
-
-            offset = 12;
-            long hour = this.hour;
-            while (hour > 0) {
-                format[offset--] = (char) ('0' + (hour % 10));
-                hour /= 10;
-            }
-
-            offset = 15;
-            long minute = this.minute;
-            while (minute > 0) {
-                format[offset--] = (char) ('0' + (minute % 10));
-                minute /= 10;
-            }
-
-            offset = 18;
-            long second = this.second;
-            while (second > 0) {
-                format[offset--] = (char) ('0' + (second % 10));
-                second /= 10;
-            }
-
-            offset = 19 + scale;
-            long microSecond = (int) (this.microSecond / Math.pow(10, 
TimeStampTzType.MAX_SCALE - scale));
-            while (microSecond > 0) {
-                format[offset--] = (char) ('0' + (microSecond % 10));
-                microSecond /= 10;
-            }
-            return String.valueOf(format, 0, 20 + scale);
-        }
-
-        return String.format("%04d-%02d-%02d %02d:%02d:%02d"
-                        + (scale > 0 ? ".%0" + scale + "d" : ""),
-                year, month, day, hour, minute, second,
-                (int) (microSecond / Math.pow(10, TimeStampTzType.MAX_SCALE - 
scale)));
+        return toLegacyLiteral().getStringValue();
     }
 
     @Override
@@ -436,6 +438,34 @@ public class TimestampTzLiteral extends DateTimeLiteral {
                 microSecond / (int) Math.pow(10, 6 - newScale) * (int) 
Math.pow(10, 6 - newScale));
     }
 
+    @Override
+    protected void roundMicroSecond(int scale) {
+        Preconditions.checkArgument(scale >= 0 && scale <= 
DateTimeV2Type.MAX_SCALE,
+                "invalid datetime v2 scale: %s", scale);
+        double factor = Math.pow(10, 6 - scale);
+
+        this.microSecond = Math.round(this.microSecond / factor) * (int) 
factor;
+
+        if (this.microSecond >= 1000000) {
+            // Use internal fields directly instead of parsing 
getStringValue(),
+            // because getStringValue() includes a timezone suffix (+00:00) 
that
+            // the parent class's DATE_TIME_FORMATTER_TO_MICRO_SECOND cannot 
parse.
+            LocalDateTime localDateTime = LocalDateTime.of(
+                    (int) year, (int) month, (int) day,
+                    (int) hour, (int) minute, (int) second).plusSeconds(1);
+            this.year = localDateTime.getYear();
+            this.month = localDateTime.getMonthValue();
+            this.day = localDateTime.getDayOfMonth();
+            this.hour = localDateTime.getHour();
+            this.minute = localDateTime.getMinute();
+            this.second = localDateTime.getSecond();
+            this.microSecond -= 1000000;
+        }
+        if (checkRange() || checkDate(year, month, day)) {
+            throw new AnalysisException("datetime literal [" + toString() + "] 
is out of range");
+        }
+    }
+
     public static Expression fromJavaDateType(LocalDateTime dateTime) {
         return fromJavaDateType(dateTime, 6);
     }
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 84f72f954c0..c19250e4060 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
@@ -43,7 +43,6 @@ import org.apache.doris.nereids.trees.expressions.Multiply;
 import org.apache.doris.nereids.trees.expressions.SubqueryExpr;
 import org.apache.doris.nereids.trees.expressions.Subtract;
 import org.apache.doris.nereids.trees.expressions.functions.BoundFunction;
-import 
org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeExtractAndTransform;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.CreateMap;
 import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
@@ -110,7 +109,6 @@ import 
org.apache.doris.nereids.types.coercion.FractionalType;
 import org.apache.doris.nereids.types.coercion.IntegralType;
 import org.apache.doris.nereids.types.coercion.NumericType;
 import org.apache.doris.nereids.types.coercion.PrimitiveType;
-import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.GlobalVariable;
 import org.apache.doris.qe.SessionVariable;
 
@@ -655,27 +653,13 @@ public class TypeCoercionUtils {
                     && DateTimeChecker.isValidDateTime(value)) {
                 ret = DateTimeLiteral.parseDateTimeLiteral(value, 
true).orElse(null);
             } else if (dataType.isTimeStampTzType() && 
DateTimeChecker.isValidDateTime(value)) {
-                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());
-                    }
+                // Signature search can pass TIMESTAMPTZ(*) here. 
TimestampTzLiteral rounds by scale,
+                // so derive a concrete scale from the literal before parsing.
+                TimeStampTzType timeStampTzType = (TimeStampTzType) dataType;
+                if (timeStampTzType.getScale() < 0 || 
timeStampTzType.getScale() == TimeStampTzType.MAX_SCALE) {
+                    timeStampTzType = TimeStampTzType.forTypeFromString(value);
                 }
+                ret = TimestampTzLiteral.fromSessionTimeZone(timeStampTzType, 
value);
             } else if ((dataType.isDateV2Type() || dataType.isDateType()) && 
DateTimeChecker.isValidDateTime(value)) {
                 Result<DateLiteral, AnalysisException> parseResult = 
DateV2Literal.parseDateLiteral(value, true);
                 if (parseResult.isOk()) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/analysis/PartitionExprUtilTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/analysis/PartitionExprUtilTest.java
index 6bfe689f421..2166d69a8c3 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/analysis/PartitionExprUtilTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/analysis/PartitionExprUtilTest.java
@@ -224,4 +224,48 @@ public class PartitionExprUtilTest extends 
TestWithFeService {
         Assertions.assertNotNull(p2);
         Assertions.assertEquals(p1.getDistributionInfo().getBucketNum(), 
p2.getDistributionInfo().getBucketNum());
     }
+
+    @Test
+    public void testAutoPartitionDateTruncTimestampTzKeepsUtcBoundary() throws 
Exception {
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("+00:00");
+            String createOlapTblStmt = "CREATE TABLE 
test.auto_timestamptz_day_partition (\n"
+                    + "  id INT,\n"
+                    + "  ts_tz TIMESTAMPTZ(6) NOT NULL\n"
+                    + ")\n"
+                    + "DUPLICATE KEY(id)\n"
+                    + "AUTO PARTITION BY RANGE (date_trunc(ts_tz, 'day')) ()\n"
+                    + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+                    + "PROPERTIES(\"replication_num\"=\"1\");";
+
+            createTable(createOlapTblStmt);
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("auto_timestamptz_day_partition");
+
+            List<List<TNullableStringLiteral>> partitionValues = new 
ArrayList<>();
+            List<TNullableStringLiteral> values = new ArrayList<>();
+            TNullableStringLiteral truncatedValue = new 
TNullableStringLiteral();
+            truncatedValue.setValue("2024-06-15 00:00:00");
+            values.add(truncatedValue);
+            partitionValues.add(values);
+
+            FrontendServiceImpl impl = new FrontendServiceImpl(exeEnv);
+            TCreatePartitionRequest request = new TCreatePartitionRequest();
+            request.setDbId(db.getId());
+            request.setTableId(table.getId());
+            request.setPartitionValues(partitionValues);
+
+            TCreatePartitionResult result = impl.createPartition(request);
+            Assertions.assertEquals(TStatusCode.OK, 
result.getStatus().getStatusCode());
+            Assertions.assertNotNull(table.getPartition("p20240615000000"));
+
+            List<String> createTableStmt = new ArrayList<>();
+            Env.getDdlStmt(table, createTableStmt, null, null, false, true, 
-1L);
+            Assertions.assertTrue(createTableStmt.get(0).contains(
+                    "PARTITION p20240615000000 VALUES [('2024-06-15 
00:00:00.000000+00:00'), ('2024-06-16 00:00:00.000000+00:00'))"));
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateTableLikeTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateTableLikeTest.java
index 732c81e5b6b..4368ff70c44 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateTableLikeTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateTableLikeTest.java
@@ -95,6 +95,12 @@ public class CreateTableLikeTest {
         command.run(connectContext, new StmtExecutor(connectContext, sql));
     }
 
+    private static String getCreateTableStmt(Table table) {
+        List<String> createTableStmt = Lists.newArrayList();
+        Env.getDdlStmt(table, createTableStmt, null, null, false, true /* hide 
password */, -1L);
+        return createTableStmt.get(0);
+    }
+
     private static void checkTableEqual(Table newTable, Table existedTable, 
int rollupSize) {
         List<String> newCreateTableStmt = Lists.newArrayList();
         List<String> newAddRollupStmt = Lists.newArrayList();
@@ -384,6 +390,157 @@ public class CreateTableLikeTest {
                         existedDbName3, newTblName3, existedTblName3, 1));
     }
 
+    @Test
+    public void 
testTimestampTzLessThanPartitionCreateTableAcceptsExplicitOffset() throws 
Exception {
+        String tableName = "test_timestamptz_less_than_create";
+        String createTableSql = "CREATE TABLE test." + tableName + " (\n"
+                + "  `ts` TIMESTAMPTZ(6) NOT NULL,\n"
+                + "  `seq` INT NOT NULL\n"
+                + ")\n"
+                + "UNIQUE KEY(`ts`)\n"
+                + "PARTITION BY RANGE(`ts`) (\n"
+                + "  PARTITION p0 VALUES LESS THAN ('2024-01-15 13:00:00 
+00:00'),\n"
+                + "  PARTITION p1 VALUES LESS THAN ('2024-01-15 14:00:00 
+00:00')\n"
+                + ")\n"
+                + "DISTRIBUTED BY HASH(`ts`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "  \"replication_num\" = \"1\",\n"
+                + "  \"enable_unique_key_merge_on_write\" = \"true\"\n"
+                + ");";
+
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("+00:00");
+            createTable(createTableSql);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrDdlException("test");
+            OlapTable table = (OlapTable) db.getTableOrDdlException(tableName);
+
+            String createStmt = getCreateTableStmt(table);
+            Assert.assertTrue(createStmt, createStmt.contains(
+                    "PARTITION p0 VALUES [('0000-01-01 
00:00:00.000000+00:00'), ('2024-01-15 13:00:00.000000+00:00'))"));
+            Assert.assertTrue(createStmt, createStmt.contains(
+                    "PARTITION p1 VALUES [('2024-01-15 
13:00:00.000000+00:00'), ('2024-01-15 14:00:00.000000+00:00'))"));
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void 
testTimestampTzRangePartitionCreateTableAcceptsNamedAndLowercaseZones() throws 
Exception {
+        String tableName = "test_timestamptz_named_lowercase_zone_create";
+        String createTableSql = "CREATE TABLE test." + tableName + " (\n"
+                + "  `ts` TIMESTAMPTZ(6) NOT NULL,\n"
+                + "  `seq` INT NOT NULL\n"
+                + ")\n"
+                + "UNIQUE KEY(`ts`)\n"
+                + "PARTITION BY RANGE(`ts`) (\n"
+                + "  PARTITION p1 VALUES [('2024-01-15 
20:00:00Asia/Shanghai'), ('2024-01-15 13:00:00    uTc')),\n"
+                + "  PARTITION p2 VALUES [('2024-01-15 13:00:00    uTc'), 
('2024-01-15 22:00:00 Asia/Shanghai'))\n"
+                + ")\n"
+                + "DISTRIBUTED BY HASH(`ts`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "  \"replication_num\" = \"1\",\n"
+                + "  \"enable_unique_key_merge_on_write\" = \"true\"\n"
+                + ");";
+
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            
connectContext.getSessionVariable().setTimeZone("America/New_York");
+            createTable(createTableSql);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrDdlException("test");
+            OlapTable table = (OlapTable) db.getTableOrDdlException(tableName);
+
+            String createStmt = getCreateTableStmt(table);
+            Assert.assertTrue(createStmt, createStmt.contains(
+                    "PARTITION p1 VALUES [('2024-01-15 
12:00:00.000000+00:00'), ('2024-01-15 13:00:00.000000+00:00'))"));
+            Assert.assertTrue(createStmt, createStmt.contains(
+                    "PARTITION p2 VALUES [('2024-01-15 
13:00:00.000000+00:00'), ('2024-01-15 14:00:00.000000+00:00'))"));
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void 
testTimestampTzLessThanPartitionCreateTableUsesSessionTimezoneWithoutOffset() 
throws Exception {
+        String tableName = "test_timestamptz_less_than_session_tz";
+        String createTableSql = "CREATE TABLE test." + tableName + " (\n"
+                + "  `ts` TIMESTAMPTZ(6) NOT NULL,\n"
+                + "  `seq` INT NOT NULL\n"
+                + ")\n"
+                + "UNIQUE KEY(`ts`)\n"
+                + "PARTITION BY RANGE(`ts`) (\n"
+                + "  PARTITION p0 VALUES LESS THAN ('2024-01-15 13:00:00'),\n"
+                + "  PARTITION p1 VALUES LESS THAN ('2024-01-15 14:00:00')\n"
+                + ")\n"
+                + "DISTRIBUTED BY HASH(`ts`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "  \"replication_num\" = \"1\",\n"
+                + "  \"enable_unique_key_merge_on_write\" = \"true\"\n"
+                + ");";
+
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            
connectContext.getSessionVariable().setTimeZone("America/New_York");
+            createTable(createTableSql);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrDdlException("test");
+            OlapTable table = (OlapTable) db.getTableOrDdlException(tableName);
+
+            String createStmt = getCreateTableStmt(table);
+            Assert.assertTrue(createStmt, createStmt.contains(
+                    "PARTITION p0 VALUES [('0000-01-01 
00:00:00.000000+00:00'), ('2024-01-15 18:00:00.000000+00:00'))"));
+            Assert.assertTrue(createStmt, createStmt.contains(
+                    "PARTITION p1 VALUES [('2024-01-15 
18:00:00.000000+00:00'), ('2024-01-15 19:00:00.000000+00:00'))"));
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testTimestampTzRangePartitionCreateLikeKeepsUtcBoundary() 
throws Exception {
+        String sourceTableName = "test_timestamptz_range_like_src";
+        String cloneTableName = "test_timestamptz_range_like_clone";
+        String createSourceTableSql = "CREATE TABLE test." + sourceTableName + 
" (\n"
+                + "  `ts` TIMESTAMPTZ(6) NOT NULL,\n"
+                + "  `seq` INT NOT NULL\n"
+                + ")\n"
+                + "UNIQUE KEY(`ts`)\n"
+                + "PARTITION BY RANGE(`ts`) (\n"
+                + "  PARTITION p1 VALUES [('2024-01-15 12:00:00 +00:00'), 
('2024-01-15 13:00:00 +00:00')),\n"
+                + "  PARTITION p2 VALUES [('2024-01-15 13:00:00 +00:00'), 
('2024-01-15 14:00:00 +00:00'))\n"
+                + ")\n"
+                + "DISTRIBUTED BY HASH(`ts`) BUCKETS 1\n"
+                + "PROPERTIES (\n"
+                + "  \"replication_num\" = \"1\",\n"
+                + "  \"enable_unique_key_merge_on_write\" = \"true\"\n"
+                + ");";
+        String createLikeSql = "CREATE TABLE test." + cloneTableName + " LIKE 
test." + sourceTableName;
+
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("+00:00");
+            createTable(createSourceTableSql);
+
+            connectContext.getSessionVariable().setTimeZone("Asia/Shanghai");
+            createTableLike(createLikeSql);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrDdlException("test");
+            OlapTable sourceTable = (OlapTable) 
db.getTableOrDdlException(sourceTableName);
+            OlapTable cloneTable = (OlapTable) 
db.getTableOrDdlException(cloneTableName);
+
+            String sourceCreateStmt = getCreateTableStmt(sourceTable);
+            Assert.assertTrue(sourceCreateStmt.contains(
+                    "PARTITION p1 VALUES [('2024-01-15 
12:00:00.000000+00:00'), ('2024-01-15 13:00:00.000000+00:00'))"));
+            Assert.assertTrue(sourceCreateStmt.contains(
+                    "PARTITION p2 VALUES [('2024-01-15 
13:00:00.000000+00:00'), ('2024-01-15 14:00:00.000000+00:00'))"));
+            checkTableEqual(cloneTable, sourceTable, 0);
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
     @Test
     public void checkSyncedTableWithRollup() throws Exception {
         String createTableWithRollup = "CREATE TABLE IF NOT EXISTS 
test.table_with_rollup_synced\n" + "(\n"
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
index 8afaf7d325e..af91938da37 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/DynamicPartitionTableTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.catalog;
 
+import org.apache.doris.analysis.LiteralExpr;
 import org.apache.doris.catalog.MaterializedIndex.IndexExtState;
 import org.apache.doris.clone.DynamicPartitionScheduler;
 import org.apache.doris.clone.RebalancerTestUtil;
@@ -39,6 +40,7 @@ import org.apache.doris.thrift.TStorageMedium;
 import org.apache.doris.utframe.UtFrameUtils;
 
 import com.google.common.collect.Lists;
+import com.google.common.collect.Range;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -49,6 +51,8 @@ import org.junit.rules.ExpectedException;
 import java.lang.reflect.Method;
 import java.time.LocalDate;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Calendar;
 import java.util.Collection;
@@ -59,6 +63,7 @@ import java.util.GregorianCalendar;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.TimeZone;
 import java.util.TreeMap;
 import java.util.UUID;
 
@@ -2014,4 +2019,298 @@ public class DynamicPartitionTableTest {
         // due to partition size eq 0, use previous partition's(54th) bucket 
num
         Assert.assertEquals(53, partitions.get(partitions.size() - 
1).getDistributionInfo().getBucketNum());
     }
+
+    @Test
+    public void testTimeStampTzDynamicPartition() throws Exception {
+        // Set session timezone to something different from the partition 
timezone
+        // to verify scheduler uses dynamic_partition.time_zone, not session 
timezone.
+        // With time_zone = "+00:00" and session = "Asia/Shanghai" (UTC+8),
+        // a session-timezone leak would shift bounds by 8 hours (hour=16, not 
00).
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("Asia/Shanghai");
+
+            String createOlapTblStmt = "CREATE TABLE 
test.`timestamptz_dynamic_partition` (\n"
+                    + "  `k1` TIMESTAMPTZ NULL COMMENT \"\",\n"
+                    + "  `k2` int NULL COMMENT \"\"\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`, `k2`)\n"
+                    + "PARTITION BY RANGE(`k1`)\n"
+                    + "()\n"
+                    + "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"dynamic_partition.enable\" = \"true\",\n"
+                    + "\"dynamic_partition.start\" = \"-3\",\n"
+                    + "\"dynamic_partition.end\" = \"3\",\n"
+                    + "\"dynamic_partition.create_history_partition\" = 
\"true\",\n"
+                    + "\"dynamic_partition.time_unit\" = \"day\",\n"
+                    + "\"dynamic_partition.prefix\" = \"p\",\n"
+                    + "\"dynamic_partition.buckets\" = \"1\",\n"
+                    + "\"dynamic_partition.time_zone\" = \"+00:00\"\n"
+                    + ");";
+            createTable(createOlapTblStmt);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("timestamptz_dynamic_partition");
+            Assert.assertTrue(table.dynamicPartitionExists());
+
+            // Execute dynamic partition scheduling
+            Env.getCurrentEnv().getDynamicPartitionScheduler()
+                    .executeDynamicPartitionFirstTime(db.getId(), 
table.getId());
+
+            // Verify total partitions (7 = start(-3) to end(3), inclusive)
+            int partitionCount = table.getPartitionNames().size();
+            Assert.assertEquals(7, partitionCount);
+
+            // Verify partition names are clean and partition values are UTC 
timestamps
+            RangePartitionInfo partitionInfo = (RangePartitionInfo) 
table.getPartitionInfo();
+            for (Map.Entry<Long, PartitionItem> entry : 
partitionInfo.getIdToItem(false).entrySet()) {
+                RangePartitionItem item = (RangePartitionItem) 
entry.getValue();
+
+                // Verify the partition name is clean
+                String partitionName = 
table.getPartition(entry.getKey()).getName();
+                Assert.assertTrue("Partition name should start with 'p': " + 
partitionName,
+                        partitionName.startsWith("p"));
+                Assert.assertEquals("Partition name should be exactly 9 chars 
(p + yyyyMMdd): " + partitionName,
+                        9, partitionName.length());
+
+                // Verify the range endpoints are valid and correctly ordered
+                Range<PartitionKey> range = item.getItems();
+                PartitionKey lower = range.lowerEndpoint();
+                PartitionKey upper = range.upperEndpoint();
+                Assert.assertTrue("lower must be < upper: " + range,
+                        lower.compareTo(upper) < 0);
+
+                // Verify partition keys are UTC timestamps (with +00:00 
suffix)
+                List<LiteralExpr> lowerKeys = lower.getKeys();
+                Assert.assertEquals(1, lowerKeys.size());
+                String lowerStr = lowerKeys.get(0).getStringValue();
+                Assert.assertTrue("Lower key must be UTC with +00:00 suffix: " 
+ lowerStr,
+                        lowerStr.contains("+00:00"));
+
+                List<LiteralExpr> upperKeys = upper.getKeys();
+                Assert.assertEquals(1, upperKeys.size());
+                String upperStr = upperKeys.get(0).getStringValue();
+                Assert.assertTrue("Upper key must be UTC with +00:00 suffix: " 
+ upperStr,
+                        upperStr.contains("+00:00"));
+
+                // With time_zone = "+00:00", partition boundaries must be 
midnight UTC
+                // (hour = 00). If the scheduler incorrectly used session 
timezone
+                // (Asia/Shanghai, UTC+8), the hour would be 16 instead of 00.
+                String lowerHour = lowerStr.substring(11, 13);
+                Assert.assertEquals("Lower bound hour should be 00 (midnight 
UTC), proving"
+                        + " dynamic_partition.time_zone was used: " + lowerStr,
+                        "00", lowerHour);
+            }
+
+            for (Partition partition : table.getPartitions()) {
+                RangePartitionItem item = (RangePartitionItem) 
partitionInfo.getItem(partition.getId());
+                Assert.assertNotNull("Each partition should have a range 
item", item);
+            }
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testTimeStampTzDynamicPartitionWeekUnit() throws Exception {
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("Europe/London");
+
+            String createOlapTblStmt = "CREATE TABLE 
test.`timestamptz_dynamic_week` (\n"
+                    + "  `k1` TIMESTAMPTZ NULL COMMENT \"\",\n"
+                    + "  `k2` int NULL COMMENT \"\"\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`, `k2`)\n"
+                    + "PARTITION BY RANGE(`k1`)\n"
+                    + "()\n"
+                    + "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"dynamic_partition.enable\" = \"true\",\n"
+                    + "\"dynamic_partition.start\" = \"-3\",\n"
+                    + "\"dynamic_partition.end\" = \"3\",\n"
+                    + "\"dynamic_partition.create_history_partition\" = 
\"true\",\n"
+                    + "\"dynamic_partition.time_unit\" = \"week\",\n"
+                    + "\"dynamic_partition.prefix\" = \"p\",\n"
+                    + "\"dynamic_partition.buckets\" = \"1\",\n"
+                    + "\"dynamic_partition.time_zone\" = \"Asia/Tokyo\"\n"
+                    + ");";
+            createTable(createOlapTblStmt);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("timestamptz_dynamic_week");
+            Assert.assertTrue(table.dynamicPartitionExists());
+
+            Env.getCurrentEnv().getDynamicPartitionScheduler()
+                    .executeDynamicPartitionFirstTime(db.getId(), 
table.getId());
+
+            int partitionCount = table.getPartitionNames().size();
+            Assert.assertEquals(7, partitionCount);
+
+            // Verify partition names follow week pattern (clean, no timezone 
suffix)
+            RangePartitionInfo partitionInfo = (RangePartitionInfo) 
table.getPartitionInfo();
+            for (Map.Entry<Long, PartitionItem> entry : 
partitionInfo.getIdToItem(false).entrySet()) {
+                RangePartitionItem item = (RangePartitionItem) 
entry.getValue();
+                String partitionName = 
table.getPartition(entry.getKey()).getName();
+                Assert.assertTrue("Partition name should start with 'p': " + 
partitionName,
+                        partitionName.startsWith("p"));
+                // Week partition name should be like "p2026_26" (year_week)
+                Assert.assertFalse("Partition name must not contain timezone: 
" + partitionName,
+                        partitionName.contains("Asia") || 
partitionName.contains("Tokyo"));
+
+                // Verify range validity
+                Range<PartitionKey> range = item.getItems();
+                Assert.assertTrue("lower must be < upper",
+                        range.lowerEndpoint().compareTo(range.upperEndpoint()) 
< 0);
+            }
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testTimeStampTzDynamicPartitionHourUnit() throws Exception {
+        String originalTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("America/Chicago");
+
+            String createOlapTblStmt = "CREATE TABLE 
test.`timestamptz_dynamic_hour` (\n"
+                    + "  `k1` TIMESTAMPTZ NULL COMMENT \"\",\n"
+                    + "  `k2` int NULL COMMENT \"\"\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`, `k2`)\n"
+                    + "PARTITION BY RANGE(`k1`)\n"
+                    + "()\n"
+                    + "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"dynamic_partition.enable\" = \"true\",\n"
+                    + "\"dynamic_partition.start\" = \"-3\",\n"
+                    + "\"dynamic_partition.end\" = \"3\",\n"
+                    + "\"dynamic_partition.create_history_partition\" = 
\"true\",\n"
+                    + "\"dynamic_partition.time_unit\" = \"hour\",\n"
+                    + "\"dynamic_partition.prefix\" = \"p\",\n"
+                    + "\"dynamic_partition.buckets\" = \"1\",\n"
+                    + "\"dynamic_partition.time_zone\" = \"+00:00\"\n"
+                    + ");";
+            createTable(createOlapTblStmt);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("timestamptz_dynamic_hour");
+            Assert.assertTrue(table.dynamicPartitionExists());
+
+            Env.getCurrentEnv().getDynamicPartitionScheduler()
+                    .executeDynamicPartitionFirstTime(db.getId(), 
table.getId());
+
+            int partitionCount = table.getPartitionNames().size();
+            Assert.assertEquals(7, partitionCount);
+
+            // Hour partition names are "p" + yyyyMMddHH (10 chars)
+            RangePartitionInfo partitionInfo = (RangePartitionInfo) 
table.getPartitionInfo();
+            for (Map.Entry<Long, PartitionItem> entry : 
partitionInfo.getIdToItem(false).entrySet()) {
+                RangePartitionItem item = (RangePartitionItem) 
entry.getValue();
+                String partitionName = 
table.getPartition(entry.getKey()).getName();
+                Assert.assertTrue("Partition name should start with 'p': " + 
partitionName,
+                        partitionName.startsWith("p"));
+                // Hour partition names: p + yyyyMMddHH → length 11 (p + 10 
digits)
+                Assert.assertEquals("Hour partition name length: " + 
partitionName,
+                        11, partitionName.length());
+
+                // Verify range validity and UTC boundaries
+                Range<PartitionKey> range = item.getItems();
+                Assert.assertTrue("lower must be < upper",
+                        range.lowerEndpoint().compareTo(range.upperEndpoint()) 
< 0);
+
+                // With time_zone = "+00:00", partition boundaries should be 
UTC timestamps
+                List<LiteralExpr> lowerKeys = range.lowerEndpoint().getKeys();
+                Assert.assertEquals(1, lowerKeys.size());
+                String lowerStr = lowerKeys.get(0).getStringValue();
+                Assert.assertTrue("Lower key must have +00:00 suffix: " + 
lowerStr,
+                        lowerStr.contains("+00:00"));
+            }
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(originalTimeZone);
+        }
+    }
+
+    @Test
+    public void testAutoPartitionRetentionTimestampTzCutoffNormalized() throws 
Exception {
+        // The bug occurs on the scheduler thread where there is no 
ConnectContext:
+        // DateUtils.getTimeZone() falls back to JVM default (e.g. 
Asia/Shanghai)
+        // while TimestampTzLiteral.fromSessionTimeZone() falls back to UTC.
+        // This creates a mismatch where currentTimeStr is formatted in the JVM
+        // timezone but parsed as UTC, shifting the cutoff.
+        String originalSessionTz = 
connectContext.getSessionVariable().getTimeZone();
+        ConnectContext savedCtx = ConnectContext.get();
+        TimeZone originalJvmTz = TimeZone.getDefault();
+        try {
+            // Create table and add partitions — use session TZ for table ops.
+            connectContext.getSessionVariable().setTimeZone("Asia/Shanghai");
+
+            String createSql = "CREATE TABLE test.`auto_retention_tstz` (\n"
+                    + "  `k1` TIMESTAMPTZ NOT NULL\n"
+                    + ") ENGINE=OLAP\n"
+                    + "DUPLICATE KEY(`k1`)\n"
+                    + "AUTO PARTITION BY RANGE (date_trunc(k1, 'day')) ()\n"
+                    + "DISTRIBUTED BY HASH(`k1`) BUCKETS 1\n"
+                    + "PROPERTIES (\n"
+                    + "\"replication_num\" = \"1\",\n"
+                    + "\"partition.retention_count\" = \"1\"\n"
+                    + ");";
+            createTable(createSql);
+
+            Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+            OlapTable tbl = (OlapTable) 
db.getTableOrAnalysisException("auto_retention_tstz");
+
+            // Definitively historical partition (year 2000) — always history.
+            alterTable("ALTER TABLE test.auto_retention_tstz ADD PARTITION 
p_old VALUES "
+                    + "[('2000-01-01 00:00:00+00:00'), ('2000-01-02 
00:00:00+00:00'))");
+            // Another historical partition (year 2020) — always history.
+            alterTable("ALTER TABLE test.auto_retention_tstz ADD PARTITION 
p_mid VALUES "
+                    + "[('2020-01-01 00:00:00+00:00'), ('2020-01-02 
00:00:00+00:00'))");
+
+            // Recent partition: upper bound is 4h in the future (UTC).
+            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd 
HH:mm:ss");
+            ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC);
+            String recentLower = utcNow.minusHours(1).format(fmt) + "+00:00";
+            String recentUpper = utcNow.plusHours(4).format(fmt) + "+00:00";
+            alterTable("ALTER TABLE test.auto_retention_tstz ADD PARTITION 
p_recent VALUES "
+                    + "[('" + recentLower + "'), ('" + recentUpper + "'))");
+            Assert.assertEquals(3, tbl.getPartitionNames().size());
+
+            // Simulate the scheduler thread: remove ConnectContext and set
+            // JVM default to a non-UTC zone. DateUtils.getTimeZone() now
+            // returns Asia/Shanghai while TimestampTzLiteral parsing defaults
+            // to UTC — the exact mismatch this fix protects against.
+            ConnectContext.remove();
+            TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
+            try {
+                Env.getCurrentEnv().getDynamicPartitionScheduler()
+                        .executeDynamicPartitionFirstTime(db.getId(), 
tbl.getId());
+            } finally {
+                TimeZone.setDefault(originalJvmTz);
+                if (savedCtx != null) {
+                    savedCtx.setThreadLocalInfo();
+                }
+            }
+
+            // With the fix, cutoff is UTC-normalized: p_recent (upper bound
+            // ahead of UTC now) is not history → kept.
+            // p_old (2000, oldest history) → dropped by retention_count=1.
+            // p_mid (2020, latest history) → kept.
+            // Total: 2 partitions survive.
+            Assert.assertEquals("After retention, 2 partitions should remain", 
2,
+                    tbl.getPartitionNames().size());
+            Assert.assertTrue("p_mid should be kept as the latest history 
partition",
+                    tbl.getPartitionNames().contains("p_mid"));
+            Assert.assertTrue("p_recent should survive (not history)",
+                    tbl.getPartitionNames().contains("p_recent"));
+        } finally {
+            TimeZone.setDefault(originalJvmTz);
+            connectContext.getSessionVariable().setTimeZone(originalSessionTz);
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/PartitionKeyTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/PartitionKeyTest.java
index 1c936da18bc..b5148f982ea 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/PartitionKeyTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/PartitionKeyTest.java
@@ -17,9 +17,11 @@
 
 package org.apache.doris.catalog;
 
+import org.apache.doris.analysis.DateLiteral;
 import org.apache.doris.analysis.PartitionValue;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.FeConstants;
+import org.apache.doris.qe.ConnectContext;
 
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -45,6 +47,7 @@ public class PartitionKeyTest {
     private static Column largeInt;
     private static Column date;
     private static Column datetime;
+    private static Column timestampTz;
     private static Column charString;
     private static Column varchar;
     private static Column bool;
@@ -63,6 +66,7 @@ public class PartitionKeyTest {
         largeInt = new Column("largeint", PrimitiveType.LARGEINT);
         date = new Column("date", PrimitiveType.DATE);
         datetime = new Column("datetime", PrimitiveType.DATETIME);
+        timestampTz = new Column("timestamptz", 
ScalarType.createTimeStampTzType(6), true, null, "", "");
         charString = new Column("char", PrimitiveType.CHAR);
         varchar = new Column("varchar", PrimitiveType.VARCHAR);
         bool = new Column("bool", PrimitiveType.BOOLEAN);
@@ -287,4 +291,158 @@ public class PartitionKeyTest {
         PartitionKey key = PartitionKey.createInfinityPartitionKey(allColumns, 
true);
         Assert.assertEquals("(MAXVALUE, MAXVALUE, MAXVALUE, MAXVALUE, 
MAXVALUE, MAXVALUE, MAXVALUE)", key.toSql());
     }
+
+    @Test
+    public void testTimestampTzPartitionKeyKeepsExplicitOffset() throws 
Exception {
+        boolean originalRunningUnitTest = FeConstants.runningUnitTest;
+        FeConstants.runningUnitTest = true;
+        ConnectContext context = new ConnectContext();
+        context.setThreadLocalInfo();
+        try {
+            context.getSessionVariable().setTimeZone("America/New_York");
+
+            PartitionKey key = PartitionKey.createPartitionKey(
+                    Arrays.asList(new PartitionValue("2024-01-15 12:00:00 
+00:00")),
+                    Arrays.asList(timestampTz));
+
+            DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+            Assert.assertEquals(2024, literal.getYear());
+            Assert.assertEquals(1, literal.getMonth());
+            Assert.assertEquals(15, literal.getDay());
+            Assert.assertEquals(12, literal.getHour());
+            Assert.assertEquals(0, literal.getMinute());
+            Assert.assertEquals(0, literal.getSecond());
+            Assert.assertEquals(0, literal.getMicrosecond());
+            Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
12:00:00"));
+            Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+        } finally {
+            ConnectContext.remove();
+            FeConstants.runningUnitTest = originalRunningUnitTest;
+        }
+    }
+
+    @Test
+    public void testTimestampTzPartitionKeyAcceptsNamedTimezone() throws 
Exception {
+        PartitionKey key = PartitionKey.createPartitionKey(
+                Arrays.asList(new PartitionValue("2024-01-15 
20:00:00Asia/Shanghai")),
+                Arrays.asList(timestampTz));
+
+        DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+        Assert.assertEquals(2024, literal.getYear());
+        Assert.assertEquals(1, literal.getMonth());
+        Assert.assertEquals(15, literal.getDay());
+        Assert.assertEquals(12, literal.getHour());
+        Assert.assertEquals(0, literal.getMinute());
+        Assert.assertEquals(0, literal.getSecond());
+        Assert.assertEquals(0, literal.getMicrosecond());
+        Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
12:00:00"));
+        Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+    }
+
+    @Test
+    public void testTimestampTzPartitionKeyAcceptsLowercaseTimezone() throws 
Exception {
+        PartitionKey key = PartitionKey.createPartitionKey(
+                Arrays.asList(new PartitionValue("2024-01-15 12:00:00    
uTc")),
+                Arrays.asList(timestampTz));
+
+        DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+        Assert.assertEquals(2024, literal.getYear());
+        Assert.assertEquals(1, literal.getMonth());
+        Assert.assertEquals(15, literal.getDay());
+        Assert.assertEquals(12, literal.getHour());
+        Assert.assertEquals(0, literal.getMinute());
+        Assert.assertEquals(0, literal.getSecond());
+        Assert.assertEquals(0, literal.getMicrosecond());
+        Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
12:00:00"));
+        Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+    }
+
+    @Test
+    public void 
testTimestampTzPartitionKeyUsesSessionTimezoneWithoutExplicitOffset() throws 
Exception {
+        boolean originalRunningUnitTest = FeConstants.runningUnitTest;
+        FeConstants.runningUnitTest = true;
+        ConnectContext context = new ConnectContext();
+        context.setThreadLocalInfo();
+        try {
+            context.getSessionVariable().setTimeZone("America/New_York");
+
+            PartitionKey key = PartitionKey.createPartitionKey(
+                    Arrays.asList(new PartitionValue("2024-01-15 12:00:00")),
+                    Arrays.asList(timestampTz));
+
+            DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+            Assert.assertEquals(2024, literal.getYear());
+            Assert.assertEquals(1, literal.getMonth());
+            Assert.assertEquals(15, literal.getDay());
+            Assert.assertEquals(17, literal.getHour());
+            Assert.assertEquals(0, literal.getMinute());
+            Assert.assertEquals(0, literal.getSecond());
+            Assert.assertEquals(0, literal.getMicrosecond());
+            Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
17:00:00"));
+            Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+        } finally {
+            ConnectContext.remove();
+            FeConstants.runningUnitTest = originalRunningUnitTest;
+        }
+    }
+
+    @Test
+    public void testListTimestampTzPartitionKeyAcceptsNamedTimezone() throws 
Exception {
+        // LIST path: createListPartitionKeyWithTypes must parse named 
timezones
+        // the same way createPartitionKey does, routing through 
TimestampTzLiteral.
+        PartitionKey key = PartitionKey.createListPartitionKeyWithTypes(
+                Arrays.asList(new PartitionValue("2024-01-15 
20:00:00Asia/Shanghai")),
+                Arrays.asList((Type) ScalarType.createTimeStampTzType(6)),
+                false);
+
+        DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+        // Asia/Shanghai (UTC+8) → 20:00 - 8h = 12:00 UTC
+        Assert.assertEquals(2024, literal.getYear());
+        Assert.assertEquals(1, literal.getMonth());
+        Assert.assertEquals(15, literal.getDay());
+        Assert.assertEquals(12, literal.getHour());
+        Assert.assertEquals(0, literal.getMinute());
+        Assert.assertEquals(0, literal.getSecond());
+        Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
12:00:00"));
+        Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+    }
+
+    @Test
+    public void testListTimestampTzPartitionKeyAcceptsLowercaseTimezone() 
throws Exception {
+        // LIST path: lowercase timezone names like "uTc" must be recognized.
+        PartitionKey key = PartitionKey.createListPartitionKeyWithTypes(
+                Arrays.asList(new PartitionValue("2024-01-15 12:00:00 uTc")),
+                Arrays.asList((Type) ScalarType.createTimeStampTzType(6)),
+                false);
+
+        DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+        // uTc = UTC → no offset change
+        Assert.assertEquals(2024, literal.getYear());
+        Assert.assertEquals(1, literal.getMonth());
+        Assert.assertEquals(15, literal.getDay());
+        Assert.assertEquals(12, literal.getHour());
+        Assert.assertEquals(0, literal.getMinute());
+        Assert.assertEquals(0, literal.getSecond());
+        Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
12:00:00"));
+        Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+    }
+
+    @Test
+    public void testListTimestampTzPartitionKeyKeepsExplicitOffset() throws 
Exception {
+        // LIST path: explicit +00:00 offset must be preserved as UTC.
+        PartitionKey key = PartitionKey.createListPartitionKeyWithTypes(
+                Arrays.asList(new PartitionValue("2024-01-15 12:00:00+00:00")),
+                Arrays.asList((Type) ScalarType.createTimeStampTzType(6)),
+                false);
+
+        DateLiteral literal = (DateLiteral) key.getKeys().get(0);
+        Assert.assertEquals(2024, literal.getYear());
+        Assert.assertEquals(1, literal.getMonth());
+        Assert.assertEquals(15, literal.getDay());
+        Assert.assertEquals(12, literal.getHour());
+        Assert.assertEquals(0, literal.getMinute());
+        Assert.assertEquals(0, literal.getSecond());
+        Assert.assertTrue(literal.getStringValue().startsWith("2024-01-15 
12:00:00"));
+        Assert.assertTrue(literal.getStringValue().endsWith("+00:00"));
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGeneratorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGeneratorTest.java
index 388c11e6aee..7fdcde6a3cc 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGeneratorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mtmv/MTMVRelatedPartitionDescRollUpGeneratorTest.java
@@ -22,9 +22,13 @@ import org.apache.doris.analysis.PartitionKeyDesc;
 import org.apache.doris.analysis.PartitionValue;
 import org.apache.doris.analysis.SlotRef;
 import org.apache.doris.analysis.StringLiteral;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.catalog.Type;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.mtmv.MTMVPartitionInfo.MTMVPartitionType;
+import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -116,4 +120,72 @@ public class MTMVRelatedPartitionDescRollUpGeneratorTest {
         }
         return PartitionKeyDesc.createIn(partitionValues);
     }
+
+    @Test
+    public void testRollUpRangeTimestampTz() throws AnalysisException {
+        FunctionCallExpr expr = new FunctionCallExpr("date_trunc",
+                Lists.newArrayList(new SlotRef(null, null), new 
StringLiteral("day")), true);
+        ConnectContext context = new ConnectContext();
+        context.getSessionVariable().setTimeZone("America/New_York");
+        context.setThreadLocalInfo();
+        try (MockedStatic<MTMVPartitionUtil> mtmvPartitionUtilStatic = 
Mockito.mockStatic(MTMVPartitionUtil.class)) {
+            mtmvPartitionUtilStatic.when(() -> 
MTMVPartitionUtil.getPartitionColumnType(
+                    Mockito.nullable(MTMVRelatedTableIf.class), 
Mockito.nullable(String.class)))
+                    .thenReturn(ScalarType.createTimeStampTzType(6));
+            Mockito.when(mtmvPartitionInfo.getRelatedTable()).thenReturn(null);
+            Mockito.when(mtmvPartitionInfo.getExpr()).thenReturn(expr);
+            
Mockito.when(mtmvPartitionInfo.getPartitionType()).thenReturn(MTMVPartitionType.EXPR);
+
+            MTMVRelatedPartitionDescRollUpGenerator generator = new 
MTMVRelatedPartitionDescRollUpGenerator();
+            Map<PartitionKeyDesc, Set<String>> relatedPartitionDescs = 
Maps.newHashMap();
+
+            // Two adjacent partitions within the same UTC day.
+            // With session tz America/New_York (UTC-5), DateTimeV2Literal 
would
+            // convert +00:00 to NY local time, shifting the date-trunc result 
to
+            // the previous day. TimestampTzLiteral preserves UTC so date_trunc
+            // correctly keeps both partitions in the same day.
+            PartitionKeyDesc desc1 = PartitionKeyDesc.createFixed(
+                    Lists.newArrayList(new PartitionValue("2024-01-15 
02:00:00.000000+00:00")),
+                    Lists.newArrayList(new PartitionValue("2024-01-15 
12:00:00.000000+00:00")));
+            PartitionKeyDesc desc2 = PartitionKeyDesc.createFixed(
+                    Lists.newArrayList(new PartitionValue("2024-01-15 
12:00:00.000000+00:00")),
+                    Lists.newArrayList(new PartitionValue("2024-01-15 
22:00:00.000000+00:00")));
+            relatedPartitionDescs.put(desc1, Sets.newHashSet("name1"));
+            relatedPartitionDescs.put(desc2, Sets.newHashSet("name2"));
+
+            Map<PartitionKeyDesc, Set<String>> res = 
generator.rollUpRange(relatedPartitionDescs,
+                    mtmvPartitionInfo, null);
+
+            // Both partitions should roll up to the same UTC day range.
+            // The +00:00 suffix ensures TimestampTzLiteral.fromSessionTimeZone
+            // treats the bounds as explicit UTC rather than session-local 
time.
+            PartitionKeyDesc expectDesc = PartitionKeyDesc.createFixed(
+                    Lists.newArrayList(new PartitionValue("2024-01-15 
00:00:00+00:00")),
+                    Lists.newArrayList(new PartitionValue("2024-01-16 
00:00:00+00:00")));
+            Assert.assertEquals(1, res.size());
+            Assert.assertEquals(Sets.newHashSet("name1", "name2"), 
res.get(expectDesc));
+
+            // Verify that the rolled-up PartitionKeyDesc produces correct UTC 
partition keys
+            // regardless of session timezone (America/New_York = UTC-5).
+            // Without the +00:00 suffix, 
TimestampTzLiteral.fromSessionTimeZone would
+            // interpret "2024-01-15 00:00:00" as session-local, shifting the 
UTC bound
+            // to 2024-01-15 05:00:00+00:00.
+            List<Column> partitionColumns = Lists.newArrayList(
+                    new Column("k1", ScalarType.createTimeStampTzType(6)));
+            PartitionKey lowKey = PartitionKey.createPartitionKey(
+                    expectDesc.getLowerValues(), partitionColumns);
+            PartitionKey upperKey = PartitionKey.createPartitionKey(
+                    expectDesc.getUpperValues(), partitionColumns);
+
+            // Both should be stored as midnight UTC, not shifted to 
session-local time.
+            String lowKeyStr = lowKey.getKeys().get(0).getStringValue();
+            String upperKeyStr = upperKey.getKeys().get(0).getStringValue();
+            Assert.assertTrue("Lower bound should be 2024-01-15 midnight UTC, 
but was: " + lowKeyStr,
+                    lowKeyStr.startsWith("2024-01-15 00:00:00"));
+            Assert.assertTrue("Upper bound should be 2024-01-16 midnight UTC, 
but was: " + upperKeyStr,
+                    upperKeyStr.startsWith("2024-01-16 00:00:00"));
+        } finally {
+            ConnectContext.remove();
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteralTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteralTest.java
index 09d9051e6ad..b1b58520b20 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteralTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/StringLikeLiteralTest.java
@@ -24,9 +24,14 @@ import org.apache.doris.nereids.types.DecimalV3Type;
 import org.apache.doris.nereids.types.DoubleType;
 import org.apache.doris.nereids.types.FloatType;
 import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.TimeStampTzType;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.SessionVariable;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 
 public class StringLikeLiteralTest {
     @Test
@@ -198,4 +203,40 @@ public class StringLikeLiteralTest {
         Assertions.assertThrows(CastException.class, () -> 
finalS2.uncheckedCastTo(DecimalV3Type.createDecimalV3Type(6, 2)));
 
     }
+
+    @Test
+    void testUncheckedCastToTimeStampTzRejectsUnstrictNoOffsetInStrictMode() {
+        try (MockedStatic<SessionVariable> mockedSessionVariable = 
Mockito.mockStatic(SessionVariable.class)) {
+            
mockedSessionVariable.when(SessionVariable::enableStrictCast).thenReturn(true);
+
+            StringLiteral literal = new StringLiteral("2020-02-01 00-00-00");
+            Assertions.assertThrows(CastException.class,
+                    () -> 
literal.uncheckedCastTo(TimeStampTzType.SYSTEM_DEFAULT));
+        }
+    }
+
+    @Test
+    void testUncheckedCastToTimeStampTzUsesSessionTimeZoneWithoutOffset() {
+        try (MockedStatic<SessionVariable> mockedSessionVariable = 
Mockito.mockStatic(SessionVariable.class)) {
+            
mockedSessionVariable.when(SessionVariable::enableStrictCast).thenReturn(false);
+
+            ConnectContext context = new ConnectContext();
+            context.getSessionVariable().setTimeZone("America/New_York");
+            context.setThreadLocalInfo();
+            try {
+                TimestampTzLiteral literal = (TimestampTzLiteral) new 
StringLiteral("2024-01-15 12:00:00")
+                        .uncheckedCastTo(TimeStampTzType.of(6));
+
+                Assertions.assertEquals(2024, literal.year);
+                Assertions.assertEquals(1, literal.month);
+                Assertions.assertEquals(15, literal.day);
+                Assertions.assertEquals(17, literal.hour);
+                Assertions.assertEquals(0, literal.minute);
+                Assertions.assertEquals(0, literal.second);
+                Assertions.assertEquals(0, literal.microSecond);
+            } finally {
+                ConnectContext.remove();
+            }
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteralTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteralTest.java
index 3e000de8a82..ac5235fdf34 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteralTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/TimestampTzLiteralTest.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.trees.expressions.literal;
 
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.types.TimeStampTzType;
+import org.apache.doris.qe.ConnectContext;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -95,6 +96,36 @@ class TimestampTzLiteralTest {
         Assertions.assertEquals(0, literal.microSecond);
     }
 
+    @Test
+    void testFromSessionTimeZoneWithoutExplicitOffset() {
+        ConnectContext context = new ConnectContext();
+        context.getSessionVariable().setTimeZone("America/New_York");
+        context.setThreadLocalInfo();
+        try {
+            TimestampTzLiteral literal = 
TimestampTzLiteral.fromSessionTimeZone(
+                    TimeStampTzType.of(6), "2024-01-15 12:00:00");
+
+            Assertions.assertEquals(2024, literal.year);
+            Assertions.assertEquals(1, literal.month);
+            Assertions.assertEquals(15, literal.day);
+            Assertions.assertEquals(17, literal.hour);
+            Assertions.assertEquals(0, literal.minute);
+            Assertions.assertEquals(0, literal.second);
+            Assertions.assertEquals(0, literal.microSecond);
+            Assertions.assertEquals(6, literal.getDataType().getScale());
+        } finally {
+            ConnectContext.remove();
+        }
+    }
+
+    @Test
+    void testGetStringValueKeepsUtcSuffix() {
+        TimestampTzLiteral literal = new 
TimestampTzLiteral(TimeStampTzType.of(6),
+                "2024-01-15 12:00:00 +00:00");
+
+        Assertions.assertEquals("2024-01-15 12:00:00.000000+00:00", 
literal.getStringValue());
+    }
+
     @Test
     void testBasicArithmetic() {
         TimestampTzLiteral base1 = new TimestampTzLiteral("2025-12-31 
23:59:59.999999");
@@ -318,4 +349,95 @@ class TimestampTzLiteralTest {
         Assertions.assertEquals(58, result.second);
         Assertions.assertEquals(900000, result.microSecond);
     }
+
+    @Test
+    void testRoundMicroSecondWithOverflow() {
+        // Scale=3: rounding 9995 → 10000, overflow microsecond to 1000000, 
should carry 1 second
+        // This previously crashed with DateTimeParseException because 
roundMicroSecond
+        // tried to reparse getStringValue() which includes a "+00:00" suffix.
+        TimestampTzLiteral literal = new TimestampTzLiteral(
+                TimeStampTzType.of(3), "2024-01-01 00:00:00.9995 +00:00");
+        Assertions.assertEquals(2024, literal.year);
+        Assertions.assertEquals(1, literal.month);
+        Assertions.assertEquals(1, literal.day);
+        Assertions.assertEquals(0, literal.hour);
+        Assertions.assertEquals(0, literal.minute);
+        Assertions.assertEquals(1, literal.second);
+        Assertions.assertEquals(0, literal.microSecond);
+        Assertions.assertEquals(3, literal.getDataType().getScale());
+        Assertions.assertEquals("2024-01-01 00:00:01.000+00:00", 
literal.getStringValue());
+    }
+
+    @Test
+    void testRoundMicroSecondWithOverflowAndTimezone() {
+        // Rounding overflow with a non-UTC timezone string (parsed via 
explicit zone path)
+        TimestampTzLiteral literal = new TimestampTzLiteral(
+                TimeStampTzType.of(3), "2024-01-01 00:00:00.9995 
America/New_York");
+        Assertions.assertEquals(2024, literal.year);
+        Assertions.assertEquals(1, literal.month);
+        Assertions.assertEquals(1, literal.day);
+        Assertions.assertEquals(5, literal.hour);
+        Assertions.assertEquals(0, literal.minute);
+        Assertions.assertEquals(1, literal.second);
+        Assertions.assertEquals(0, literal.microSecond);
+        Assertions.assertEquals(3, literal.getDataType().getScale());
+    }
+
+    @Test
+    void testRoundMicroSecondWithoutOverflow() {
+        // Scale=3: rounding 9994 → 999, no overflow
+        TimestampTzLiteral literal = new TimestampTzLiteral(
+                TimeStampTzType.of(3), "2024-06-15 12:30:45.9994 +00:00");
+        Assertions.assertEquals(2024, literal.year);
+        Assertions.assertEquals(6, literal.month);
+        Assertions.assertEquals(15, literal.day);
+        Assertions.assertEquals(12, literal.hour);
+        Assertions.assertEquals(30, literal.minute);
+        Assertions.assertEquals(45, literal.second);
+        // microSecond stores microseconds: 999ms = 999000µs
+        Assertions.assertEquals(999000, literal.microSecond);
+    }
+
+    @Test
+    void testRoundMicroSecondOverflowCrossDateBoundary() {
+        // Scale=3: 23:59:59.9995 → overflows to next day 00:00:00
+        TimestampTzLiteral literal = new TimestampTzLiteral(
+                TimeStampTzType.of(3), "2024-01-01 23:59:59.9995 +00:00");
+        Assertions.assertEquals(2024, literal.year);
+        Assertions.assertEquals(1, literal.month);
+        Assertions.assertEquals(2, literal.day);
+        Assertions.assertEquals(0, literal.hour);
+        Assertions.assertEquals(0, literal.minute);
+        Assertions.assertEquals(0, literal.second);
+        Assertions.assertEquals(0, literal.microSecond);
+    }
+
+    @Test
+    void testFromTimeZoneExplicit() {
+        // fromTimeZone with explicit timezone should convert from given tz to 
UTC
+        TimestampTzLiteral literal = TimestampTzLiteral.fromTimeZone(
+                TimeStampTzType.of(6), "2024-01-15 00:00:00", 
"America/New_York");
+        Assertions.assertEquals(2024, literal.year);
+        Assertions.assertEquals(1, literal.month);
+        Assertions.assertEquals(15, literal.day);
+        Assertions.assertEquals(5, literal.hour);
+        Assertions.assertEquals(0, literal.minute);
+        Assertions.assertEquals(0, literal.second);
+        Assertions.assertEquals(0, literal.microSecond);
+    }
+
+    @Test
+    void testFromTimeZoneExplicitWithOffsetString() {
+        // fromTimeZone with a string that already has a timezone should use 
the string's timezone
+        TimestampTzLiteral literal = TimestampTzLiteral.fromTimeZone(
+                TimeStampTzType.of(6), "2024-01-15 00:00:00 +05:00", 
"America/New_York");
+        // +05:00 in string should take precedence; 00:00:00 +05:00 → 
2024-01-14 19:00:00 UTC
+        Assertions.assertEquals(2024, literal.year);
+        Assertions.assertEquals(1, literal.month);
+        Assertions.assertEquals(14, literal.day);
+        Assertions.assertEquals(19, literal.hour);
+        Assertions.assertEquals(0, literal.minute);
+        Assertions.assertEquals(0, literal.second);
+        Assertions.assertEquals(0, literal.microSecond);
+    }
 }
diff --git 
a/regression-test/data/datatype_p0/timestamptz/test_timestamptz_partition_boundary_timezone.out
 
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_partition_boundary_timezone.out
new file mode 100644
index 00000000000..18cbd9e8d53
--- /dev/null
+++ 
b/regression-test/data/datatype_p0/timestamptz/test_timestamptz_partition_boundary_timezone.out
@@ -0,0 +1,26 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !bug107_create_shift --
+2024-01-15 12:30:00.000000+00:00       1
+2024-01-15 13:30:00.000000+00:00       2
+
+-- !bug108_like --
+2024-01-15 12:30:00.000000+00:00       1
+2024-01-15 13:30:00.000000+00:00       2
+
+-- !bug110_src --
+2024-01-15 12:30:00.000000+00:00       1
+2024-01-15 13:30:00.000000+00:00       2
+
+-- !bug111_src --
+2024-01-15 17:30:00.000000+00:00       1
+2024-01-15 18:30:00.000000+00:00       2
+
+-- !bug113_direct_date_trunc_utc --
+2024-06-15 00:00:00.000000+00:00
+
+-- !bug113_auto_tz_range --
+2024-06-15 20:00:00.000000+00:00       1
+
+-- !bug114_named_lowercase_tz --
+2024-01-15 12:30:00.000000+00:00       1
+2024-01-15 13:30:00.000000+00:00       2
diff --git 
a/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_partition_boundary_timezone.groovy
 
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_partition_boundary_timezone.groovy
new file mode 100644
index 00000000000..6fe272c2d0c
--- /dev/null
+++ 
b/regression-test/suites/datatype_p0/timestamptz/test_timestamptz_partition_boundary_timezone.groovy
@@ -0,0 +1,257 @@
+// 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_partition_boundary_timezone") {
+    def dbName = "timestamptz_partition_boundary_timezone"
+    def createBoundary = "PARTITION p1 VALUES [('2024-01-15 
12:00:00.000000+00:00'), ('2024-01-15 13:00:00.000000+00:00'))"
+    def createBoundary2 = "PARTITION p2 VALUES [('2024-01-15 
13:00:00.000000+00:00'), ('2024-01-15 14:00:00.000000+00:00'))"
+
+    sql "DROP DATABASE IF EXISTS ${dbName}"
+    sql "CREATE DATABASE ${dbName}"
+    sql "USE ${dbName}"
+
+    sql "SET enable_nereids_planner = true"
+    sql "SET enable_fallback_to_original_planner = false"
+
+    sql "SET time_zone = 'America/New_York'"
+    sql "DROP TABLE IF EXISTS bug107_create_shift"
+    sql """
+        CREATE TABLE bug107_create_shift (
+            ts TIMESTAMPTZ(6) NOT NULL,
+            seq INT NOT NULL
+        )
+        UNIQUE KEY(ts)
+        PARTITION BY RANGE(ts) (
+            PARTITION p1 VALUES [('2024-01-15 12:00:00 +00:00'), ('2024-01-15 
13:00:00 +00:00')),
+            PARTITION p2 VALUES [('2024-01-15 13:00:00 +00:00'), ('2024-01-15 
14:00:00 +00:00'))
+        )
+        DISTRIBUTED BY HASH(ts) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true"
+        )
+    """
+
+    def createShift = sql "SHOW CREATE TABLE bug107_create_shift"
+    assertTrue(createShift[0][1].contains(createBoundary))
+    assertTrue(createShift[0][1].contains(createBoundary2))
+
+    sql "SET time_zone = '+00:00'"
+    sql """
+        INSERT INTO bug107_create_shift VALUES
+        (CAST('2024-01-15 12:30:00 +00:00' AS TIMESTAMPTZ(6)), 1),
+        (CAST('2024-01-15 13:30:00 +00:00' AS TIMESTAMPTZ(6)), 2)
+    """
+
+    order_qt_bug107_create_shift """
+        SELECT CAST(ts AS STRING), seq
+        FROM bug107_create_shift
+        ORDER BY seq
+    """
+
+    sql "SET time_zone = '+00:00'"
+    sql "DROP TABLE IF EXISTS bug108_src"
+    sql """
+        CREATE TABLE bug108_src (
+            ts TIMESTAMPTZ(6) NOT NULL,
+            seq INT NOT NULL
+        )
+        UNIQUE KEY(ts)
+        PARTITION BY RANGE(ts) (
+            PARTITION p1 VALUES [('2024-01-15 12:00:00 +00:00'), ('2024-01-15 
13:00:00 +00:00')),
+            PARTITION p2 VALUES [('2024-01-15 13:00:00 +00:00'), ('2024-01-15 
14:00:00 +00:00'))
+        )
+        DISTRIBUTED BY HASH(ts) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true"
+        )
+    """
+
+    def srcCreate = sql "SHOW CREATE TABLE bug108_src"
+    assertTrue(srcCreate[0][1].contains(createBoundary))
+    assertTrue(srcCreate[0][1].contains(createBoundary2))
+
+    sql "SET time_zone = 'Asia/Shanghai'"
+    sql "DROP TABLE IF EXISTS bug108_like"
+    sql "CREATE TABLE bug108_like LIKE bug108_src"
+
+    def likeCreate = sql "SHOW CREATE TABLE bug108_like"
+    assertTrue(likeCreate[0][1].contains(createBoundary))
+    assertTrue(likeCreate[0][1].contains(createBoundary2))
+
+    sql "SET time_zone = '+00:00'"
+    sql """
+        INSERT INTO bug108_like VALUES
+        (CAST('2024-01-15 12:30:00 +00:00' AS TIMESTAMPTZ(6)), 1),
+        (CAST('2024-01-15 13:30:00 +00:00' AS TIMESTAMPTZ(6)), 2)
+    """
+
+    order_qt_bug108_like """
+        SELECT CAST(ts AS STRING), seq
+        FROM bug108_like
+        ORDER BY seq
+    """
+
+    sql "SET time_zone = '+00:00'"
+    sql "DROP TABLE IF EXISTS bug110_src"
+    sql """
+        CREATE TABLE bug110_src (
+            ts TIMESTAMPTZ(6) NOT NULL,
+            seq INT NOT NULL
+        )
+        UNIQUE KEY(ts)
+        PARTITION BY RANGE(ts) (
+            PARTITION p0 VALUES LESS THAN ('2024-01-15 13:00:00 +00:00'),
+            PARTITION p1 VALUES LESS THAN ('2024-01-15 14:00:00 +00:00')
+        )
+        DISTRIBUTED BY HASH(ts) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true"
+        )
+    """
+
+    def bug110Create = sql "SHOW CREATE TABLE bug110_src"
+    assertTrue(bug110Create[0][1].contains(
+            "PARTITION p0 VALUES [('0000-01-01 00:00:00.000000+00:00'), 
('2024-01-15 13:00:00.000000+00:00'))"))
+    assertTrue(bug110Create[0][1].contains(
+            "PARTITION p1 VALUES [('2024-01-15 13:00:00.000000+00:00'), 
('2024-01-15 14:00:00.000000+00:00'))"))
+
+    sql """
+        INSERT INTO bug110_src VALUES
+        (CAST('2024-01-15 12:30:00 +00:00' AS TIMESTAMPTZ(6)), 1),
+        (CAST('2024-01-15 13:30:00 +00:00' AS TIMESTAMPTZ(6)), 2)
+    """
+
+    order_qt_bug110_src """
+        SELECT CAST(ts AS STRING), seq
+        FROM bug110_src
+        ORDER BY seq
+    """
+
+    sql "SET time_zone = 'America/New_York'"
+    sql "DROP TABLE IF EXISTS bug111_src"
+    sql """
+        CREATE TABLE bug111_src (
+            ts TIMESTAMPTZ(6) NOT NULL,
+            seq INT NOT NULL
+        )
+        UNIQUE KEY(ts)
+        PARTITION BY RANGE(ts) (
+            PARTITION p0 VALUES LESS THAN ('2024-01-15 13:00:00'),
+            PARTITION p1 VALUES LESS THAN ('2024-01-15 14:00:00')
+        )
+        DISTRIBUTED BY HASH(ts) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true"
+        )
+    """
+
+    def bug111Create = sql "SHOW CREATE TABLE bug111_src"
+    assertTrue(bug111Create[0][1].contains(
+            "PARTITION p0 VALUES [('0000-01-01 00:00:00.000000+00:00'), 
('2024-01-15 18:00:00.000000+00:00'))"))
+    assertTrue(bug111Create[0][1].contains(
+            "PARTITION p1 VALUES [('2024-01-15 18:00:00.000000+00:00'), 
('2024-01-15 19:00:00.000000+00:00'))"))
+
+    sql "SET time_zone = '+00:00'"
+    sql """
+        INSERT INTO bug111_src VALUES
+        (CAST('2024-01-15 17:30:00 +00:00' AS TIMESTAMPTZ(6)), 1),
+        (CAST('2024-01-15 18:30:00 +00:00' AS TIMESTAMPTZ(6)), 2)
+    """
+
+    order_qt_bug111_src """
+        SELECT CAST(ts AS STRING), seq
+        FROM bug111_src
+        ORDER BY seq
+    """
+
+    sql "SET time_zone = '+00:00'"
+    sql "DROP TABLE IF EXISTS bug113_auto_tz_range"
+    sql """
+        CREATE TABLE bug113_auto_tz_range (
+            id INT,
+            ts_tz TIMESTAMPTZ(6) NOT NULL
+        )
+        DUPLICATE KEY(id)
+        AUTO PARTITION BY RANGE (date_trunc(`ts_tz`, 'day')) ()
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1"
+        )
+    """
+
+    order_qt_bug113_direct_date_trunc_utc """
+        SELECT CAST(date_trunc(ts, 'day') AS VARCHAR(64))
+        FROM (
+            SELECT CAST('2024-06-15 20:00:00 +00:00' AS TIMESTAMPTZ(6)) AS ts
+        ) t
+    """
+
+    sql """
+        INSERT INTO bug113_auto_tz_range VALUES
+        (1, CAST('2024-06-15 20:00:00 +00:00' AS TIMESTAMPTZ(6)))
+    """
+
+    def bug113Create = sql "SHOW CREATE TABLE bug113_auto_tz_range"
+    assertTrue(bug113Create[0][1].contains(
+            "PARTITION p20240615000000 VALUES [('2024-06-15 
00:00:00.000000+00:00'), ('2024-06-16 00:00:00.000000+00:00'))"))
+
+    order_qt_bug113_auto_tz_range """
+        SELECT CAST(ts_tz AS STRING), id
+        FROM bug113_auto_tz_range
+        ORDER BY id
+    """
+
+    sql "SET time_zone = 'America/New_York'"
+    sql "DROP TABLE IF EXISTS bug114_named_lowercase_tz"
+    sql """
+        CREATE TABLE bug114_named_lowercase_tz (
+            ts TIMESTAMPTZ(6) NOT NULL,
+            seq INT NOT NULL
+        )
+        UNIQUE KEY(ts)
+        PARTITION BY RANGE(ts) (
+            PARTITION p1 VALUES [('2024-01-15 20:00:00Asia/Shanghai'), 
('2024-01-15 13:00:00    uTc')),
+            PARTITION p2 VALUES [('2024-01-15 13:00:00    uTc'), ('2024-01-15 
22:00:00 Asia/Shanghai'))
+        )
+        DISTRIBUTED BY HASH(ts) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "enable_unique_key_merge_on_write" = "true"
+        )
+    """
+
+    def bug114Create = sql "SHOW CREATE TABLE bug114_named_lowercase_tz"
+    assertTrue(bug114Create[0][1].contains(createBoundary))
+    assertTrue(bug114Create[0][1].contains(createBoundary2))
+
+    sql "SET time_zone = '+00:00'"
+    sql """
+        INSERT INTO bug114_named_lowercase_tz VALUES
+        (CAST('2024-01-15 12:30:00 +00:00' AS TIMESTAMPTZ(6)), 1),
+        (CAST('2024-01-15 13:30:00 +00:00' AS TIMESTAMPTZ(6)), 2)
+    """
+
+    order_qt_bug114_named_lowercase_tz """
+        SELECT CAST(ts AS STRING), seq
+        FROM bug114_named_lowercase_tz
+        ORDER BY seq
+    """
+}
\ No newline at end of file


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

Reply via email to