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

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new f96a95be715 branch-4.2: [fix](function) Keep date partition pruning 
correct across time zones #68681 (#70058)
f96a95be715 is described below

commit f96a95be7154fbc86a2f35f075e9fbf82e69fc62
Author: morrySnow <[email protected]>
AuthorDate: Wed Nov 4 08:17:49 2026 +0800

    branch-4.2: [fix](function) Keep date partition pruning correct across time 
zones #68681 (#70058)
    
    picked from master #68681
    
    Co-authored-by: Mryange <[email protected]>
---
 .../doris/nereids/trees/expressions/Cast.java      | 110 +-------
 .../expressions/functions/DateAddSubMonotonic.java |   2 +-
 .../functions/DateCeilFloorMonotonic.java          |  11 +-
 .../expressions/functions/DateDiffMonotonic.java   |   3 +-
 .../expressions/functions/FromSecondMonotonic.java |   9 +-
 .../expressions/functions/MonotonicityUtils.java   | 296 +++++++++++++++++++++
 .../expressions/functions/scalar/ConvertTz.java    |   7 +-
 .../expressions/functions/scalar/DateFormat.java   |  10 +-
 .../expressions/functions/scalar/DayOfMonth.java   |   8 +-
 .../expressions/functions/scalar/DayOfYear.java    |   7 +-
 .../functions/scalar/FromMicrosecond.java          |   6 +
 .../functions/scalar/FromMillisecond.java          |   6 +
 .../expressions/functions/scalar/FromSecond.java   |   6 +
 .../expressions/functions/scalar/FromUnixtime.java |  12 +-
 .../trees/expressions/functions/scalar/Hour.java   |   9 +-
 .../expressions/functions/scalar/Microsecond.java  |  27 +-
 .../trees/expressions/functions/scalar/Minute.java |  23 +-
 .../trees/expressions/functions/scalar/Month.java  |   7 +-
 .../expressions/functions/scalar/Nanosecond.java   |  26 +-
 .../expressions/functions/scalar/Quarter.java      |   7 +-
 .../trees/expressions/functions/scalar/Second.java |  25 +-
 .../expressions/functions/scalar/TimeFormat.java   |  10 +-
 .../functions/scalar/UnixTimestamp.java            |   7 +-
 .../expressions/functions/scalar/YearWeek.java     |   6 +-
 .../org/apache/doris/nereids/util/DateUtils.java   |  33 +--
 .../rules/rewrite/PruneOlapScanPartitionTest.java  |  19 ++
 .../functions/scalar/FromSecondMonotonicTest.java  |  98 +++++++
 .../month_quarter_cast_in_prune.groovy             |  70 +++--
 28 files changed, 521 insertions(+), 339 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java
index 591042711da..fd9d7138134 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Cast.java
@@ -17,20 +17,15 @@
 
 package org.apache.doris.nereids.trees.expressions;
 
-import org.apache.doris.common.util.TimeUtils;
-import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.exceptions.UnboundException;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
-import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
-import org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.ArrayType;
 import org.apache.doris.nereids.types.BigIntType;
 import org.apache.doris.nereids.types.DataType;
-import org.apache.doris.nereids.types.DateTimeV2Type;
 import org.apache.doris.nereids.types.DecimalV2Type;
 import org.apache.doris.nereids.types.DecimalV3Type;
 import org.apache.doris.nereids.types.IntegerType;
@@ -40,20 +35,12 @@ import org.apache.doris.nereids.types.SmallIntType;
 import org.apache.doris.nereids.types.StructField;
 import org.apache.doris.nereids.types.StructType;
 import org.apache.doris.nereids.types.TimeStampNsType;
-import org.apache.doris.nereids.types.TimeStampTzType;
 import org.apache.doris.nereids.types.TinyIntType;
 import org.apache.doris.nereids.types.VariantType;
-import org.apache.doris.nereids.types.coercion.DateLikeType;
-import org.apache.doris.nereids.util.DateUtils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
-import java.time.DateTimeException;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
 import java.util.List;
 import java.util.Objects;
 
@@ -403,99 +390,6 @@ public class Cast extends Expression implements 
UnaryExpression, Monotonic {
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        DataType childType = child().getDataType();
-        if (!(childType instanceof DateLikeType && targetType instanceof 
DateLikeType)) {
-            return false;
-        }
-
-        if (targetType instanceof TimeStampNsType && 
!isRangeWithinTimeStampNs(lower, upper)) {
-            return false;
-        }
-
-        if (childType instanceof TimeStampTzType
-                && (targetType instanceof DateTimeV2Type || targetType 
instanceof TimeStampNsType)) {
-            int destinationScale = targetType instanceof DateTimeV2Type
-                    ? ((DateTimeV2Type) targetType).getScale() : 
TimeStampNsType.SCALE;
-            return isTimeStampTzToLocalDateTimeMonotonic(
-                    (TimeStampTzType) childType, destinationScale, lower, 
upper);
-        }
-        if (childType instanceof TimeStampNsType && targetType instanceof 
TimeStampTzType) {
-            return isTimeStampNsToTimeStampTzMonotonic(
-                    (TimeStampTzType) targetType, lower, upper);
-        }
-        return true;
-    }
-
-    private boolean isRangeWithinTimeStampNs(Literal lower, Literal upper) {
-        if (lower == null || upper == null) {
-            return false;
-        }
-        try {
-            return !(lower.checkedCastTo(targetType) instanceof NullLiteral)
-                    && !(upper.checkedCastTo(targetType) instanceof 
NullLiteral);
-        } catch (AnalysisException e) {
-            return false;
-        }
-    }
-
-    private boolean isTimeStampTzToLocalDateTimeMonotonic(
-            TimeStampTzType sourceType, int destinationScale, Literal lower, 
Literal upper) {
-        ZoneId timeZone;
-        try {
-            timeZone = TimeUtils.getDorisZoneId();
-        } catch (DateTimeException e) {
-            return false;
-        }
-        if (timeZone.getRules().isFixedOffset()) {
-            return true;
-        }
-        // Scale reduction rounds the UTC value before applying the session 
timezone. That rounding
-        // can move values across a fall-back transition just outside the 
original partition range.
-        if (destinationScale < sourceType.getScale()) {
-            return false;
-        }
-        if (!(lower instanceof TimestampTzLiteral) || !(upper instanceof 
TimestampTzLiteral)) {
-            return false;
-        }
-
-        // TimestampTzLiteral stores UTC civil fields. The cast renders those 
instants in the
-        // session timezone, which moves backward at a fall-back transition.
-        Instant lowerInstant = ((TimestampTzLiteral) 
lower).toJavaDateType().toInstant(ZoneOffset.UTC);
-        Instant upperInstant = ((TimestampTzLiteral) 
upper).toJavaDateType().toInstant(ZoneOffset.UTC);
-        if (upperInstant.isBefore(lowerInstant)) {
-            return false;
-        }
-        return !DateUtils.hasFallbackTransitionInInstantRange(timeZone, 
lowerInstant, upperInstant);
-    }
-
-    private boolean isTimeStampNsToTimeStampTzMonotonic(
-            TimeStampTzType destinationType, Literal lower, Literal upper) {
-        ZoneId timeZone;
-        try {
-            timeZone = TimeUtils.getDorisZoneId();
-        } catch (DateTimeException e) {
-            return false;
-        }
-        if (timeZone.getRules().isFixedOffset()) {
-            return true;
-        }
-        if (!(lower instanceof TimeStampNsLiteral) || !(upper instanceof 
TimeStampNsLiteral)) {
-            return false;
-        }
-        LocalDateTime lowerDateTime = roundTimeStampNs(
-                (TimeStampNsLiteral) lower, destinationType.getScale());
-        LocalDateTime upperDateTime = roundTimeStampNs(
-                (TimeStampNsLiteral) upper, destinationType.getScale());
-        if (upperDateTime.isBefore(lowerDateTime)) {
-            return false;
-        }
-        return !DateUtils.hasGapTransitionInLocalDateTimeRange(
-                timeZone, lowerDateTime, upperDateTime);
-    }
-
-    private LocalDateTime roundTimeStampNs(TimeStampNsLiteral literal, int 
scale) {
-        long factor = (long) Math.pow(10, DateUtils.NANOSECOND_SCALE - scale);
-        LocalDateTime dateTime = literal.toJavaDateType().plusNanos(factor / 
2);
-        return dateTime.withNano((int) (dateTime.getNano() / factor * factor));
+        return 
MonotonicityUtils.isDateLikeCastMonotonic(child().getDataType(), targetType, 
lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateAddSubMonotonic.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateAddSubMonotonic.java
index 7fec22fd9d3..96265850456 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateAddSubMonotonic.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateAddSubMonotonic.java
@@ -23,7 +23,7 @@ import 
org.apache.doris.nereids.trees.expressions.literal.Literal;
 public interface DateAddSubMonotonic extends Monotonic {
     @Override
     default boolean isMonotonic(Literal lower, Literal upper) {
-        return child(1) instanceof Literal;
+        return MonotonicityUtils.hasConstantOtherArguments(this, 0);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateCeilFloorMonotonic.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateCeilFloorMonotonic.java
index 35e3caafc4f..cecb622684e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateCeilFloorMonotonic.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateCeilFloorMonotonic.java
@@ -30,16 +30,7 @@ import java.util.Optional;
 public interface DateCeilFloorMonotonic extends RoundingMonotonic {
     @Override
     default boolean isMonotonic(Literal lower, Literal upper) {
-        switch (arity()) {
-            case 1:
-                return true;
-            case 2:
-                return !(child(0) instanceof Literal) && child(1) instanceof 
Literal;
-            case 3:
-                return !(child(0) instanceof Literal) && child(1) instanceof 
Literal && child(2) instanceof Literal;
-            default:
-                return false;
-        }
+        return MonotonicityUtils.hasConstantRoundingArguments(this);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateDiffMonotonic.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateDiffMonotonic.java
index daaea895b6d..1fb17f2b2dd 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateDiffMonotonic.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/DateDiffMonotonic.java
@@ -23,8 +23,7 @@ import 
org.apache.doris.nereids.trees.expressions.literal.Literal;
 public interface DateDiffMonotonic extends Monotonic {
     @Override
     default boolean isMonotonic(Literal lower, Literal upper) {
-        return !(child(0) instanceof Literal) && child(1) instanceof Literal
-                || child(0) instanceof Literal && !(child(1) instanceof 
Literal);
+        return MonotonicityUtils.hasOneVariableArgument(this);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FromSecondMonotonic.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FromSecondMonotonic.java
index 96dfa7ac650..6df932c9577 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FromSecondMonotonic.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FromSecondMonotonic.java
@@ -17,17 +17,16 @@
 
 package org.apache.doris.nereids.trees.expressions.functions;
 
-import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 
 /** monotonicity for from_{xx}second */
 public interface FromSecondMonotonic extends Monotonic {
+    /** Number of input units in one epoch second. */
+    long getEpochUnitsPerSecond();
+
     @Override
     default boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof BigIntLiteral) {
-            return ((BigIntLiteral) lower).getValue() >= 0;
-        }
-        return false;
+        return MonotonicityUtils.isEpochToLocalMonotonic(lower, upper, 
getEpochUnitsPerSecond());
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/MonotonicityUtils.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/MonotonicityUtils.java
new file mode 100644
index 00000000000..7efcce3a102
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/MonotonicityUtils.java
@@ -0,0 +1,296 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions;
+
+import org.apache.doris.common.util.TimeUtils;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.DateTimeV2Type;
+import org.apache.doris.nereids.types.TimeStampNsType;
+import org.apache.doris.nereids.types.TimeStampTzType;
+import org.apache.doris.nereids.types.coercion.DateLikeType;
+import org.apache.doris.nereids.util.DateUtils;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
+
+/** Shared range predicates for monotonic date and time expressions. */
+public final class MonotonicityUtils {
+    // The last second accepted by FE constant folding for 
from_{second,millisecond,microsecond}.
+    private static final long MAX_EPOCH_SECONDS = 253402271999L;
+
+    private MonotonicityUtils() {
+    }
+
+    /** All arguments other than the range argument must be literals. */
+    public static boolean hasConstantOtherArguments(ExpressionTrait 
expression, int rangeArgument) {
+        for (int i = 0; i < expression.arity(); i++) {
+            if (i != rangeArgument && !(expression.child(i) instanceof 
Literal)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean hasOneVariableArgument(ExpressionTrait expression) {
+        return expression.child(0) instanceof Literal ^ expression.child(1) 
instanceof Literal;
+    }
+
+    public static boolean hasConstantRoundingArguments(ExpressionTrait 
expression) {
+        return expression.arity() >= 1 && expression.arity() <= 3
+                && (expression.arity() == 1 || !(expression.child(0) 
instanceof Literal))
+                && hasConstantOtherArguments(expression, 0);
+    }
+
+    public static boolean hasMonotonicFormat(Expression format) {
+        return format instanceof StringLikeLiteral
+                && DateUtils.monoFormat.contains(((StringLikeLiteral) 
format).getValue());
+    }
+
+    /** A calendar field cannot wrap while its enclosing date fields are 
unchanged. */
+    public static boolean isWithinSameYear(Literal lower, Literal upper) {
+        return hasSameDatePrefix(lower, upper, 1);
+    }
+
+    public static boolean isWithinSameMonth(Literal lower, Literal upper) {
+        return hasSameDatePrefix(lower, upper, 2);
+    }
+
+    public static boolean isWithinSameDay(Literal lower, Literal upper) {
+        return hasSameDatePrefix(lower, upper, 3);
+    }
+
+    private static boolean hasSameDatePrefix(Literal lower, Literal upper, int 
fields) {
+        if (!(lower instanceof DateLiteral && upper instanceof DateLiteral)) {
+            return false;
+        }
+        DateLiteral start = (DateLiteral) lower;
+        DateLiteral end = (DateLiteral) upper;
+        return start.getYear() == end.getYear()
+                && (fields < 2 || start.getMonth() == end.getMonth())
+                && (fields < 3 || start.getDay() == end.getDay());
+    }
+
+    /** Preserve the date-only behavior of sub-day extractors. */
+    public static boolean isWithinSameHour(Literal lower, Literal upper) {
+        return hasSameTimePrefix(lower, upper, 1);
+    }
+
+    public static boolean isWithinSameMinute(Literal lower, Literal upper) {
+        return hasSameTimePrefix(lower, upper, 2);
+    }
+
+    public static boolean isWithinSameSecond(Literal lower, Literal upper) {
+        return hasSameTimePrefix(lower, upper, 3);
+    }
+
+    private static boolean hasSameTimePrefix(Literal lower, Literal upper, int 
fields) {
+        if (lower instanceof TimeStampNsLiteral && upper instanceof 
TimeStampNsLiteral) {
+            TimeStampNsLiteral start = (TimeStampNsLiteral) lower;
+            TimeStampNsLiteral end = (TimeStampNsLiteral) upper;
+            return hasSameDatePrefix(lower, upper, 3)
+                    && start.getHour() == end.getHour()
+                    && (fields < 2 || start.getMinute() == end.getMinute())
+                    && (fields < 3 || start.getSecond() == end.getSecond());
+        }
+        if (lower instanceof DateTimeLiteral && upper instanceof 
DateTimeLiteral) {
+            DateTimeLiteral start = (DateTimeLiteral) lower;
+            DateTimeLiteral end = (DateTimeLiteral) upper;
+            return hasSameDatePrefix(lower, upper, 3)
+                    && start.getHour() == end.getHour()
+                    && (fields < 2 || start.getMinute() == end.getMinute())
+                    && (fields < 3 || start.getSecond() == end.getSecond());
+        }
+        return lower instanceof DateLiteral && upper instanceof DateLiteral;
+    }
+
+    /** Check the range in the unit used by from_second, from_millisecond, or 
from_microsecond. */
+    public static boolean isEpochToLocalMonotonic(Literal lower, Literal 
upper, long unitsPerSecond) {
+        if (!(lower instanceof BigIntLiteral) || ((BigIntLiteral) 
lower).getValue() < 0) {
+            return false;
+        }
+        ZoneId zoneId;
+        try {
+            zoneId = TimeUtils.getDorisZoneId();
+        } catch (DateTimeException e) {
+            return false;
+        }
+        if (zoneId.getRules().isFixedOffset()) {
+            return true;
+        }
+        if (!(upper instanceof BigIntLiteral)) {
+            return false;
+        }
+        long lowerValue = ((BigIntLiteral) lower).getValue();
+        long upperValue = ((BigIntLiteral) upper).getValue();
+        if (upperValue < lowerValue || upperValue / unitsPerSecond > 
MAX_EPOCH_SECONDS) {
+            return false;
+        }
+        Instant lowerInstant = epochToInstant(lowerValue, unitsPerSecond);
+        Instant upperInstant = epochToInstant(upperValue, unitsPerSecond);
+        if (LocalDateTime.ofInstant(upperInstant, zoneId).getYear() > 9999) {
+            return false;
+        }
+        return isInstantToLocalMonotonic(zoneId, lowerInstant, upperInstant);
+    }
+
+    private static Instant epochToInstant(long value, long unitsPerSecond) {
+        return Instant.ofEpochSecond(value / unitsPerSecond,
+                value % unitsPerSecond * (1000000000L / unitsPerSecond));
+    }
+
+    /** Instant-to-local conversion can move backward only at a fall-back 
transition. */
+    public static boolean isInstantToLocalMonotonic(ZoneId zoneId, Instant 
lower, Instant upper) {
+        return zoneId.getRules().isFixedOffset()
+                || lower != null && upper != null && !upper.isBefore(lower)
+                && !hasFallbackTransitionInInstantRange(zoneId, lower, upper);
+    }
+
+    /** Local-to-instant conversion may lose its order across a spring-forward 
gap. */
+    public static boolean isLocalToInstantMonotonic(ZoneId zoneId, 
LocalDateTime lower, LocalDateTime upper) {
+        return zoneId.getRules().isFixedOffset()
+                || lower != null && upper != null && !upper.isBefore(lower)
+                && !hasGapTransitionInLocalDateTimeRange(zoneId, lower, upper);
+    }
+
+    /** Whether the instant interval (lower, upper] crosses a fall-back 
transition. */
+    private static boolean hasFallbackTransitionInInstantRange(ZoneId zoneId, 
Instant lower, Instant upper) {
+        ZoneRules rules = zoneId.getRules();
+        ZoneOffsetTransition transition = rules.nextTransition(lower);
+        while (transition != null && !transition.getInstant().isAfter(upper)) {
+            if (transition.isOverlap()) {
+                return true;
+            }
+            transition = rules.nextTransition(transition.getInstant());
+        }
+        return false;
+    }
+
+    /** Whether the local interval intersects a spring-forward gap. */
+    private static boolean hasGapTransitionInLocalDateTimeRange(
+            ZoneId zoneId, LocalDateTime lower, LocalDateTime upper) {
+        ZoneRules rules = zoneId.getRules();
+        Instant searchStart = lower.minusDays(2).atZone(zoneId).toInstant();
+        ZoneOffsetTransition transition = rules.nextTransition(searchStart);
+        while (transition != null && 
!transition.getDateTimeBefore().isAfter(upper)) {
+            if (transition.isGap()
+                    && upper.isAfter(transition.getDateTimeBefore())
+                    && lower.isBefore(transition.getDateTimeAfter())) {
+                return true;
+            }
+            transition = rules.nextTransition(transition.getInstant());
+        }
+        return false;
+    }
+
+    /** Dispatch date-like casts to their range checks. */
+    public static boolean isDateLikeCastMonotonic(
+            DataType sourceType, DataType targetType, Literal lower, Literal 
upper) {
+        if (!(sourceType instanceof DateLikeType && targetType instanceof 
DateLikeType)) {
+            return false;
+        }
+        if (targetType instanceof TimeStampNsType && 
!isRangeWithinTimeStampNs(targetType, lower, upper)) {
+            return false;
+        }
+        if (sourceType instanceof TimeStampTzType
+                && (targetType instanceof DateTimeV2Type || targetType 
instanceof TimeStampNsType)) {
+            int destinationScale = targetType instanceof DateTimeV2Type
+                    ? ((DateTimeV2Type) targetType).getScale() : 
TimeStampNsType.SCALE;
+            return isTimeStampTzToLocalMonotonic(
+                    (TimeStampTzType) sourceType, destinationScale, lower, 
upper);
+        }
+        if (sourceType instanceof TimeStampNsType && targetType instanceof 
TimeStampTzType) {
+            return isTimeStampNsToTimeStampTzMonotonic((TimeStampTzType) 
targetType, lower, upper);
+        }
+        return true;
+    }
+
+    private static boolean isRangeWithinTimeStampNs(DataType targetType, 
Literal lower, Literal upper) {
+        if (lower == null || upper == null) {
+            return false;
+        }
+        try {
+            return !(lower.checkedCastTo(targetType) instanceof NullLiteral)
+                    && !(upper.checkedCastTo(targetType) instanceof 
NullLiteral);
+        } catch (AnalysisException e) {
+            return false;
+        }
+    }
+
+    /** Check a TIMESTAMPTZ cast to a local datetime after accounting for its 
output scale. */
+    public static boolean isTimeStampTzToLocalMonotonic(
+            TimeStampTzType sourceType, int destinationScale, Literal lower, 
Literal upper) {
+        ZoneId zoneId;
+        try {
+            zoneId = TimeUtils.getDorisZoneId();
+        } catch (DateTimeException e) {
+            return false;
+        }
+        if (zoneId.getRules().isFixedOffset()) {
+            return true;
+        }
+        // Rounding UTC before zone conversion may cross a fall-back just 
outside the input range.
+        if (destinationScale < sourceType.getScale()
+                || !(lower instanceof TimestampTzLiteral) || !(upper 
instanceof TimestampTzLiteral)) {
+            return false;
+        }
+        Instant lowerInstant = ((TimestampTzLiteral) 
lower).toJavaDateType().toInstant(ZoneOffset.UTC);
+        Instant upperInstant = ((TimestampTzLiteral) 
upper).toJavaDateType().toInstant(ZoneOffset.UTC);
+        return isInstantToLocalMonotonic(zoneId, lowerInstant, upperInstant);
+    }
+
+    /** Check a local TIMESTAMP_NS cast after rounding to the TIMESTAMPTZ 
output scale. */
+    public static boolean isTimeStampNsToTimeStampTzMonotonic(
+            TimeStampTzType destinationType, Literal lower, Literal upper) {
+        ZoneId zoneId;
+        try {
+            zoneId = TimeUtils.getDorisZoneId();
+        } catch (DateTimeException e) {
+            return false;
+        }
+        if (zoneId.getRules().isFixedOffset()) {
+            return true;
+        }
+        if (!(lower instanceof TimeStampNsLiteral) || !(upper instanceof 
TimeStampNsLiteral)) {
+            return false;
+        }
+        LocalDateTime lowerDateTime = roundTimeStampNs((TimeStampNsLiteral) 
lower, destinationType.getScale());
+        LocalDateTime upperDateTime = roundTimeStampNs((TimeStampNsLiteral) 
upper, destinationType.getScale());
+        return isLocalToInstantMonotonic(zoneId, lowerDateTime, upperDateTime);
+    }
+
+    private static LocalDateTime roundTimeStampNs(TimeStampNsLiteral literal, 
int scale) {
+        long factor = (long) Math.pow(10, DateUtils.NANOSECOND_SCALE - scale);
+        LocalDateTime dateTime = literal.toJavaDateType().plusNanos(factor / 
2);
+        return dateTime.withNano((int) (dateTime.getNano() / factor * factor));
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
index 5606b023987..317f5bb1894 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
@@ -24,6 +24,7 @@ import org.apache.doris.nereids.trees.expressions.Cast;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import 
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
@@ -36,7 +37,6 @@ import 
org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
 import org.apache.doris.nereids.types.TimeStampNsType;
 import org.apache.doris.nereids.types.VarcharType;
-import org.apache.doris.nereids.util.DateUtils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -147,10 +147,7 @@ public class ConvertTz extends ScalarFunction
          */
         Instant lowerInstant = 
DateTimeLiteral.convertLocalToInstant(lowerDateTime, fromZone);
         Instant upperInstant = 
DateTimeLiteral.convertLocalToInstant(upperDateTime, fromZone);
-        if (upperInstant.isBefore(lowerInstant)) {
-            return false;
-        }
-        return !DateUtils.hasFallbackTransitionInInstantRange(toZone, 
lowerInstant, upperInstant);
+        return MonotonicityUtils.isInstantToLocalMonotonic(toZone, 
lowerInstant, upperInstant);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DateFormat.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DateFormat.java
index 5a5884d75ab..809afe0e366 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DateFormat.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DateFormat.java
@@ -21,16 +21,15 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import 
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
 import org.apache.doris.nereids.types.TimeStampNsType;
 import org.apache.doris.nereids.types.VarcharType;
-import org.apache.doris.nereids.util.DateUtils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -82,12 +81,7 @@ public class DateFormat extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        Expression format = child(1);
-        if (!(format instanceof StringLikeLiteral)) {
-            return false;
-        }
-        String str = ((StringLikeLiteral) format).getValue();
-        return DateUtils.monoFormat.contains(str);
+        return MonotonicityUtils.hasMonotonicFormat(child(1));
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfMonth.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfMonth.java
index 07ef40c2814..0229c5a04ed 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfMonth.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfMonth.java
@@ -21,8 +21,8 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
@@ -95,10 +95,6 @@ public class DayOfMonth extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof DateLiteral && upper instanceof DateLiteral) {
-            return ((DateLiteral) lower).getYear() == ((DateLiteral) 
upper).getYear()
-                    && ((DateLiteral) lower).getMonth() == ((DateLiteral) 
upper).getMonth();
-        }
-        return false;
+        return MonotonicityUtils.isWithinSameMonth(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfYear.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfYear.java
index 917e2cf95c9..a9687d0db1d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfYear.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/DayOfYear.java
@@ -21,8 +21,8 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
@@ -95,9 +95,6 @@ public class DayOfYear extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof DateLiteral && upper instanceof DateLiteral) {
-            return ((DateLiteral) lower).getYear() == ((DateLiteral) 
upper).getYear();
-        }
-        return false;
+        return MonotonicityUtils.isWithinSameYear(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMicrosecond.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMicrosecond.java
index a7c21daae0b..0247fe2e24a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMicrosecond.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMicrosecond.java
@@ -41,6 +41,7 @@ public class FromMicrosecond extends ScalarFunction
         FromSecondMonotonic {
 
     public static final int RESULT_SCALE = 6;
+
     private static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
             
FunctionSignature.ret(DateTimeV2Type.of(RESULT_SCALE)).args(BigIntType.INSTANCE));
 
@@ -69,6 +70,11 @@ public class FromMicrosecond extends ScalarFunction
         return visitor.visitScalarFunction(this, context);
     }
 
+    @Override
+    public long getEpochUnitsPerSecond() {
+        return 1000000;
+    }
+
     @Override
     public Expression withConstantArgs(Expression literal) {
         return new FromMicrosecond(literal);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMillisecond.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMillisecond.java
index b2a719f17fa..3e5eee219cb 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMillisecond.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromMillisecond.java
@@ -40,6 +40,7 @@ public class FromMillisecond extends ScalarFunction
         implements BinaryExpression, ExplicitlyCastableSignature, 
PropagateNullLiteral, PropagateNullable,
         FromSecondMonotonic {
     public static final int RESULT_SCALE = 3;
+
     private static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
             
FunctionSignature.ret(DateTimeV2Type.of(RESULT_SCALE)).args(BigIntType.INSTANCE));
 
@@ -68,6 +69,11 @@ public class FromMillisecond extends ScalarFunction
         return visitor.visitScalarFunction(this, context);
     }
 
+    @Override
+    public long getEpochUnitsPerSecond() {
+        return 1000;
+    }
+
     @Override
     public Expression withConstantArgs(Expression literal) {
         return new FromMillisecond(literal);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecond.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecond.java
index bde4678aefe..026c125c12c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecond.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecond.java
@@ -41,6 +41,7 @@ public class FromSecond extends ScalarFunction
         FromSecondMonotonic {
 
     public static final int RESULT_SCALE = 0;
+
     private static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
             
FunctionSignature.ret(DateTimeV2Type.of(RESULT_SCALE)).args(BigIntType.INSTANCE));
 
@@ -69,6 +70,11 @@ public class FromSecond extends ScalarFunction
         return visitor.visitScalarFunction(this, context);
     }
 
+    @Override
+    public long getEpochUnitsPerSecond() {
+        return 1;
+    }
+
     @Override
     public Expression withConstantArgs(Expression literal) {
         return new FromSecond(literal);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
index 2c218c4342d..46323676aed 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import 
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
@@ -157,10 +158,7 @@ public class FromUnixtime extends ScalarFunction
         }
         Instant lowerInstant = toInstant(lower);
         Instant upperInstant = toInstant(upper);
-        if (lowerInstant == null || upperInstant == null || 
upperInstant.isBefore(lowerInstant)) {
-            return false;
-        }
-        return !DateUtils.hasFallbackTransitionInInstantRange(timeZone, 
lowerInstant, upperInstant);
+        return MonotonicityUtils.isInstantToLocalMonotonic(timeZone, 
lowerInstant, upperInstant);
     }
 
     @Override
@@ -182,11 +180,7 @@ public class FromUnixtime extends ScalarFunction
     }
 
     private boolean isMonotonicFormat() {
-        Expression format = child(1);
-        if (!(format instanceof StringLikeLiteral)) {
-            return false;
-        }
-        return DateUtils.monoFormat.contains(((StringLikeLiteral) 
format).getValue());
+        return MonotonicityUtils.hasMonotonicFormat(child(1));
     }
 
     private void checkFractionFormatSpecifiers() {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Hour.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Hour.java
index 664412c4571..0a2873d5f3e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Hour.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Hour.java
@@ -21,8 +21,8 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
@@ -98,11 +98,6 @@ public class Hour extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof DateLiteral && upper instanceof DateLiteral) {
-            return ((DateLiteral) lower).getYear() == ((DateLiteral) 
upper).getYear()
-                    && ((DateLiteral) lower).getMonth() == ((DateLiteral) 
upper).getMonth()
-                    && ((DateLiteral) lower).getDay() == ((DateLiteral) 
upper).getDay();
-        }
-        return false;
+        return MonotonicityUtils.isWithinSameDay(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Microsecond.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Microsecond.java
index 444838d9edd..b5bc6ee1c6f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Microsecond.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Microsecond.java
@@ -21,11 +21,9 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
-import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
@@ -98,27 +96,6 @@ public class Microsecond extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof TimeStampNsLiteral && upper instanceof 
TimeStampNsLiteral) {
-            TimeStampNsLiteral lowerDateTime = (TimeStampNsLiteral) lower;
-            TimeStampNsLiteral upperDateTime = (TimeStampNsLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour()
-                    && lowerDateTime.getMinute() == upperDateTime.getMinute()
-                    && lowerDateTime.getSecond() == upperDateTime.getSecond();
-        }
-        if (lower instanceof DateTimeLiteral && upper instanceof 
DateTimeLiteral) {
-            DateTimeLiteral lowerDateTime = (DateTimeLiteral) lower;
-            DateTimeLiteral upperDateTime = (DateTimeLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour()
-                    && lowerDateTime.getMinute() == upperDateTime.getMinute()
-                    && lowerDateTime.getSecond() == upperDateTime.getSecond();
-        } else {
-            return lower instanceof DateLiteral && upper instanceof 
DateLiteral;
-        }
+        return MonotonicityUtils.isWithinSameSecond(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Minute.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Minute.java
index 18454ea4b05..4e24e41553a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Minute.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Minute.java
@@ -21,11 +21,9 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
-import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
@@ -99,23 +97,6 @@ public class Minute extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof TimeStampNsLiteral && upper instanceof 
TimeStampNsLiteral) {
-            TimeStampNsLiteral lowerDateTime = (TimeStampNsLiteral) lower;
-            TimeStampNsLiteral upperDateTime = (TimeStampNsLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour();
-        }
-        if (lower instanceof DateTimeLiteral && upper instanceof 
DateTimeLiteral) {
-            DateTimeLiteral lowerDateTime = (DateTimeLiteral) lower;
-            DateTimeLiteral upperDateTime = (DateTimeLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour();
-        } else {
-            return lower instanceof DateLiteral && upper instanceof 
DateLiteral;
-        }
+        return MonotonicityUtils.isWithinSameHour(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Month.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Month.java
index 550d9c465ad..6517e63e4b0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Month.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Month.java
@@ -21,8 +21,8 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
@@ -96,9 +96,6 @@ public class Month extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof DateLiteral && upper instanceof DateLiteral) {
-            return ((DateLiteral) lower).getYear() == ((DateLiteral) 
upper).getYear();
-        }
-        return false;
+        return MonotonicityUtils.isWithinSameYear(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Nanosecond.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Nanosecond.java
index a730f41353b..b631f0cb643 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Nanosecond.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Nanosecond.java
@@ -21,11 +21,9 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
-import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
@@ -90,26 +88,6 @@ public class Nanosecond extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof TimeStampNsLiteral && upper instanceof 
TimeStampNsLiteral) {
-            TimeStampNsLiteral lowerTimestamp = (TimeStampNsLiteral) lower;
-            TimeStampNsLiteral upperTimestamp = (TimeStampNsLiteral) upper;
-            return lowerTimestamp.getYear() == upperTimestamp.getYear()
-                    && lowerTimestamp.getMonth() == upperTimestamp.getMonth()
-                    && lowerTimestamp.getDay() == upperTimestamp.getDay()
-                    && lowerTimestamp.getHour() == upperTimestamp.getHour()
-                    && lowerTimestamp.getMinute() == upperTimestamp.getMinute()
-                    && lowerTimestamp.getSecond() == 
upperTimestamp.getSecond();
-        }
-        if (lower instanceof DateTimeLiteral && upper instanceof 
DateTimeLiteral) {
-            DateTimeLiteral lowerDateTime = (DateTimeLiteral) lower;
-            DateTimeLiteral upperDateTime = (DateTimeLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour()
-                    && lowerDateTime.getMinute() == upperDateTime.getMinute()
-                    && lowerDateTime.getSecond() == upperDateTime.getSecond();
-        }
-        return lower instanceof DateLiteral && upper instanceof DateLiteral;
+        return MonotonicityUtils.isWithinSameSecond(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Quarter.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Quarter.java
index 60cbe488b84..a38dae15ea9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Quarter.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Quarter.java
@@ -21,8 +21,8 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
@@ -95,9 +95,6 @@ public class Quarter extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof DateLiteral && upper instanceof DateLiteral) {
-            return ((DateLiteral) lower).getYear() == ((DateLiteral) 
upper).getYear();
-        }
-        return false;
+        return MonotonicityUtils.isWithinSameYear(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Second.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Second.java
index 9ddcf11f7ba..7bad0e30f62 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Second.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Second.java
@@ -21,11 +21,9 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
-import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
-import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
@@ -99,25 +97,6 @@ public class Second extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (lower instanceof TimeStampNsLiteral && upper instanceof 
TimeStampNsLiteral) {
-            TimeStampNsLiteral lowerDateTime = (TimeStampNsLiteral) lower;
-            TimeStampNsLiteral upperDateTime = (TimeStampNsLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour()
-                    && lowerDateTime.getMinute() == upperDateTime.getMinute();
-        }
-        if (lower instanceof DateTimeLiteral && upper instanceof 
DateTimeLiteral) {
-            DateTimeLiteral lowerDateTime = (DateTimeLiteral) lower;
-            DateTimeLiteral upperDateTime = (DateTimeLiteral) upper;
-            return lowerDateTime.getYear() == upperDateTime.getYear()
-                    && lowerDateTime.getMonth() == upperDateTime.getMonth()
-                    && lowerDateTime.getDay() == upperDateTime.getDay()
-                    && lowerDateTime.getHour() == upperDateTime.getHour()
-                    && lowerDateTime.getMinute() == upperDateTime.getMinute();
-        } else {
-            return lower instanceof DateLiteral && upper instanceof 
DateLiteral;
-        }
+        return MonotonicityUtils.isWithinSameMinute(lower, upper);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TimeFormat.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TimeFormat.java
index 12b08ce1fac..2995fc17f8d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TimeFormat.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/TimeFormat.java
@@ -22,15 +22,14 @@ import 
org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
-import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
 import org.apache.doris.nereids.trees.expressions.shape.BinaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
 import org.apache.doris.nereids.types.TimeStampNsType;
 import org.apache.doris.nereids.types.TimeV2Type;
 import org.apache.doris.nereids.types.VarcharType;
-import org.apache.doris.nereids.util.DateUtils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -85,12 +84,7 @@ public class TimeFormat extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        Expression format = child(1);
-        if (!(format instanceof StringLikeLiteral)) {
-            return false;
-        }
-        String str = ((StringLikeLiteral) format).getValue();
-        return DateUtils.monoFormat.contains(str);
+        return MonotonicityUtils.hasMonotonicFormat(child(1));
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UnixTimestamp.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UnixTimestamp.java
index 2b6b8fb1aa4..9a19e7ee8b2 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UnixTimestamp.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/UnixTimestamp.java
@@ -21,6 +21,7 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import 
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
@@ -171,11 +172,7 @@ public class UnixTimestamp extends ScalarFunction
         }
         LocalDateTime lowerDateTime = toLocalDateTime(lower);
         LocalDateTime upperDateTime = toLocalDateTime(upper);
-        if (lowerDateTime == null || upperDateTime == null || 
upperDateTime.isBefore(lowerDateTime)) {
-            return false;
-        }
-        return !DateUtils.hasGapTransitionInLocalDateTimeRange(
-                timeZone, lowerDateTime, upperDateTime);
+        return MonotonicityUtils.isLocalToInstantMonotonic(timeZone, 
lowerDateTime, upperDateTime);
     }
 
     private LocalDateTime toLocalDateTime(Literal literal) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/YearWeek.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/YearWeek.java
index 632dd951e94..c5b857f3021 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/YearWeek.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/YearWeek.java
@@ -21,6 +21,7 @@ import org.apache.doris.catalog.FunctionSignature;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.functions.MonotonicityUtils;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
@@ -88,10 +89,7 @@ public class YearWeek extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (arity() == 1) {
-            return true;
-        }
-        return child(1) instanceof Literal;
+        return MonotonicityUtils.hasConstantOtherArguments(this, 0);
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java
index f34f67b2bc7..4110a055e81 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.nereids.util;
 
+import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
 import org.apache.doris.qe.ConnectContext;
@@ -503,36 +504,6 @@ public class DateUtils {
         if (ConnectContext.get() == null || 
ConnectContext.get().getSessionVariable() == null) {
             return ZoneId.systemDefault();
         }
-        return 
ZoneId.of(ConnectContext.get().getSessionVariable().getTimeZone());
-    }
-
-    /**Determine whether there is a fallback transition within the interval 
(lower, upper].
-     * @return If there is one, return true.*/
-    public static boolean hasFallbackTransitionInInstantRange(ZoneId zoneId, 
Instant lower, Instant upper) {
-        ZoneOffsetTransition transition = 
zoneId.getRules().nextTransition(lower);
-        while (transition != null && !transition.getInstant().isAfter(upper)) {
-            if (transition.isOverlap()) {
-                return true;
-            }
-            transition = 
zoneId.getRules().nextTransition(transition.getInstant());
-        }
-        return false;
-    }
-
-    /** Determine whether the local interval intersects a spring-forward gap. 
*/
-    public static boolean hasGapTransitionInLocalDateTimeRange(
-            ZoneId zoneId, LocalDateTime lower, LocalDateTime upper) {
-        ZoneRules rules = zoneId.getRules();
-        Instant searchStart = lower.minusDays(2).atZone(zoneId).toInstant();
-        ZoneOffsetTransition transition = rules.nextTransition(searchStart);
-        while (transition != null && 
!transition.getDateTimeBefore().isAfter(upper)) {
-            if (transition.isGap()
-                    && upper.isAfter(transition.getDateTimeBefore())
-                    && lower.isBefore(transition.getDateTimeAfter())) {
-                return true;
-            }
-            transition = rules.nextTransition(transition.getInstant());
-        }
-        return false;
+        return TimeUtils.getDorisZoneId();
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
index 107c3debe1f..6b7d10b308a 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
@@ -157,6 +157,25 @@ class PruneOlapScanPartitionTest extends TestWithFeService 
implements MemoPatter
         test("testOlapScanPartitionWithSingleColumnCase", "col1 >= 0 and col1 
<= 5", 2);
     }
 
+    @Test
+    void testEpochConversionWithTimeZoneAlias() throws Exception {
+        createTable("create table epoch_conversion_alias_prune ("
+                + "epoch_sec bigint not null) "
+                + "partition by range(epoch_sec) ("
+                + "partition p1 values[('1719792000'), ('1721001600')),"
+                + "partition p2 values[('1721001600'), ('1722470400'))"
+                + ") distributed by hash(epoch_sec) buckets 1 "
+                + "properties ('replication_num'='1')");
+
+        String previousTimeZone = 
connectContext.getSessionVariable().getTimeZone();
+        try {
+            connectContext.getSessionVariable().setTimeZone("CST");
+            test("epoch_conversion_alias_prune", "from_second(epoch_sec) < 
'2024-07-10 00:00:00'", 1);
+        } finally {
+            connectContext.getSessionVariable().setTimeZone(previousTimeZone);
+        }
+    }
+
     @Test
     void testOlapScanPartitionPruneWithTablet() throws Exception {
         Database db = 
Env.getCurrentInternalCatalog().getDbOrMetaException("test");
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecondMonotonicTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecondMonotonicTest.java
new file mode 100644
index 00000000000..153545e6eb7
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromSecondMonotonicTest.java
@@ -0,0 +1,98 @@
+// 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.scalar;
+
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import 
org.apache.doris.nereids.trees.expressions.functions.FromSecondMonotonic;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.qe.ConnectContext;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+class FromSecondMonotonicTest {
+    private final SlotReference epochSlot = new SlotReference("epoch", 
BigIntType.INSTANCE);
+    private ConnectContext previousContext;
+
+    @BeforeEach
+    void setUp() {
+        previousContext = ConnectContext.get();
+        new ConnectContext().setThreadLocalInfo();
+    }
+
+    @AfterEach
+    void tearDown() {
+        ConnectContext.remove();
+        if (previousContext != null) {
+            previousContext.setThreadLocalInfo();
+        }
+    }
+
+    @Test
+    void testTimeZoneFallbackWithinRangeDisablesMonotonicity() {
+        List<FromSecondMonotonic> functions = Arrays.asList(
+                new FromSecond(epochSlot), new FromMillisecond(epochSlot), new 
FromMicrosecond(epochSlot));
+        long[] fallbackSeconds = {1730613600L, 684867600L};
+        List<String> timeZones = Arrays.asList("America/New_York", 
"Asia/Shanghai");
+        for (int i = 0; i < timeZones.size(); i++) {
+            
ConnectContext.get().getSessionVariable().setTimeZone(timeZones.get(i));
+            for (FromSecondMonotonic function : functions) {
+                long units = function.getEpochUnitsPerSecond();
+                Assertions.assertFalse(function.isMonotonic(
+                        new BigIntLiteral((fallbackSeconds[i] - 1) * units),
+                        new BigIntLiteral((fallbackSeconds[i] + 1) * units)));
+                Assertions.assertTrue(function.isMonotonic(
+                        new BigIntLiteral(1719792000L * units),
+                        new BigIntLiteral(1719795600L * units)));
+                Assertions.assertFalse(function.isMonotonic(new 
BigIntLiteral(1719792000), null));
+            }
+        }
+    }
+
+    @Test
+    void testShanghaiHistoricalFallbackAndEpochUnits() {
+        ConnectContext.get().getSessionVariable().setTimeZone("Asia/Shanghai");
+        Assertions.assertFalse(new FromMillisecond(epochSlot).isMonotonic(
+                new BigIntLiteral(100000), new BigIntLiteral(1000000009999L)));
+        Assertions.assertTrue(new FromMicrosecond(epochSlot).isMonotonic(
+                new BigIntLiteral(100000), new BigIntLiteral(1000000009999L)));
+        Assertions.assertFalse(new FromSecond(epochSlot).isMonotonic(
+                new BigIntLiteral(253402272000L), new 
BigIntLiteral(253402272001L)));
+    }
+
+    @Test
+    void testFixedOffsetPreservesMonotonicityForNonnegativeInput() {
+        List<FromSecondMonotonic> functions = Arrays.asList(
+                new FromSecond(epochSlot), new FromMillisecond(epochSlot), new 
FromMicrosecond(epochSlot));
+        for (String timeZone : Arrays.asList("+00:00", "+08:00")) {
+            ConnectContext.get().getSessionVariable().setTimeZone(timeZone);
+            for (FromSecondMonotonic function : functions) {
+                Assertions.assertTrue(function.isMonotonic(new 
BigIntLiteral(0), new BigIntLiteral(1)));
+                Assertions.assertTrue(function.isMonotonic(new 
BigIntLiteral(0), null));
+                Assertions.assertFalse(function.isMonotonic(new 
BigIntLiteral(-1), new BigIntLiteral(1)));
+                Assertions.assertFalse(function.isMonotonic(null, new 
BigIntLiteral(1)));
+            }
+        }
+    }
+}
diff --git 
a/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
 
b/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
index be89d056e94..3a77c478977 100644
--- 
a/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
+++ 
b/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
@@ -125,29 +125,55 @@ suite("month_quarter_cast_in_prune") {
     ) distributed by hash(a) properties("replication_num"="1");"""
     sql """INSERT INTO monotonic_function_t 
values(10000,'1979-01-01','1979-01-01','abc'),(100000009999,'2012-01-01','2012-01-01','abc'),(100000009999999,'2020-01-01','2020-01-01','abc'),(10000000099999999,'2045-01-01','2045-01-01','abc')"""
 
-    explain {
-        sql """select * from monotonic_function_t where from_second(a) < 
'2001-09-09 12:33:19' """
-        contains("partitions=4/4 (p1,p2,p3,p4)")
-    }
-    explain {
-        sql """select * from monotonic_function_t where from_second(a) > 
'2001-09-09 12:33:19' """
-        contains("partitions=4/4 (p1,p2,p3,p4)")
-    }
-    explain {
-        sql """select * from monotonic_function_t where from_millisecond(a) < 
'2001-09-09 12:33:19' """
-        contains("partitions=4/4 (p1,p2,p3,p4)")
-    }
-    explain {
-        sql """select * from monotonic_function_t where from_millisecond(a) > 
'2001-09-09 12:33:19' """
-        contains("partitions=3/4 (p1,p3,p4)")
-    }
-    explain {
-        sql """select * from monotonic_function_t where from_microsecond(a) < 
'2000-09-09 12:33:19' """
-        contains("partitions=3/4 (p1,p2,p3)")
+    def originalTimeZone = sql("select @@time_zone")[0][0]
+    try {
+        sql "set time_zone = 'Asia/Shanghai'"
+        explain {
+            sql """select * from monotonic_function_t where from_second(a) < 
'2001-09-09 12:33:19' """
+            contains("partitions=4/4 (p1,p2,p3,p4)")
+        }
+        explain {
+            sql """select * from monotonic_function_t where from_second(a) > 
'2001-09-09 12:33:19' """
+            contains("partitions=4/4 (p1,p2,p3,p4)")
+        }
+        explain {
+            sql """select * from monotonic_function_t where 
from_millisecond(a) < '2001-09-09 12:33:19' """
+            contains("partitions=4/4 (p1,p2,p3,p4)")
+        }
+        explain {
+            sql """select * from monotonic_function_t where 
from_millisecond(a) > '2001-09-09 12:33:19' """
+            // p2 spans the 1991 Asia/Shanghai fall-back, so it is not 
monotonic.
+            contains("partitions=4/4 (p1,p2,p3,p4)")
+        }
+        explain {
+            sql """select * from monotonic_function_t where 
from_microsecond(a) < '2000-09-09 12:33:19' """
+            contains("partitions=4/4 (p1,p2,p3,p4)")
+        }
+        explain {
+            sql """select * from monotonic_function_t where 
from_microsecond(a) > '2002-09-09 12:33:19' """
+            // p2 is a safe 1970 interval; p3 spans the 1991 fall-back.
+            contains("partitions=3/4 (p1,p3,p4)")
+        }
+    } finally {
+        sql "set time_zone = '${originalTimeZone}'"
     }
-    explain {
-        sql """select * from monotonic_function_t where from_microsecond(a) > 
'2002-09-09 12:33:19' """
-        contains("partitions=2/4 (p1,p4)")
+
+    sql "drop table if exists epoch_cst_prune"
+    sql """create table epoch_cst_prune (a bigint) duplicate key(a)
+    partition by range(a) (
+            partition p2000 values [('946684800'), ('978307200')),
+            partition p2001 values [('978307200'), ('1009843200')),
+            partition p2002 values [('1009843200'), ('1041379200'))
+    ) distributed by hash(a) properties('replication_num'='1');"""
+    sql "insert into epoch_cst_prune values (946684800), (978307200), 
(1009843200)"
+    try {
+        sql "set time_zone = 'CST'"
+        explain {
+            sql "select * from epoch_cst_prune where from_second(a) > 
'2001-06-01 00:00:00'"
+            contains("partitions=2/3 (p2001,p2002)")
+        }
+    } finally {
+        sql "set time_zone = '${originalTimeZone}'"
     }
 
     // test makedate


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

Reply via email to