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

jackietien pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new 838d2903 Implement extract time filters
838d2903 is described below

commit 838d2903189de41c7c0d559f2d0d933c676e87ee
Author: Weihao Li <[email protected]>
AuthorDate: Wed Jul 16 17:29:48 2025 +0800

    Implement extract time filters
---
 .../apache/tsfile/read/filter/basic/Filter.java    |  13 +
 .../tsfile/read/filter/basic/OperatorType.java     |  10 +-
 .../tsfile/read/filter/factory/TimeFilterApi.java  |  38 ++
 .../operator/ExtractTimeFilterOperators.java       | 616 +++++++++++++++++++++
 .../tsfile/read/filter/ExtractTimeFilterTest.java  | 436 +++++++++++++++
 5 files changed, 1112 insertions(+), 1 deletion(-)

diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java
index cc48210d..db5276e8 100755
--- a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/Filter.java
@@ -26,6 +26,7 @@ import org.apache.tsfile.read.filter.factory.FilterFactory;
 import org.apache.tsfile.read.filter.factory.TimeFilterApi;
 import org.apache.tsfile.read.filter.factory.ValueFilterApi;
 import org.apache.tsfile.read.filter.operator.And;
+import org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators;
 import org.apache.tsfile.read.filter.operator.GroupByFilter;
 import org.apache.tsfile.read.filter.operator.GroupByMonthFilter;
 import org.apache.tsfile.read.filter.operator.Not;
@@ -245,6 +246,18 @@ public abstract class Filter {
         return new Or(buffer);
       case NOT:
         return new Not(buffer);
+      case EXTRACT_TIME_EQ:
+        return new ExtractTimeFilterOperators.ExtractTimeEq(buffer);
+      case EXTRACT_TIME_NEQ:
+        return new ExtractTimeFilterOperators.ExtractTimeNotEq(buffer);
+      case EXTRACT_TIME_GT:
+        return new ExtractTimeFilterOperators.ExtractTimeGt(buffer);
+      case EXTRACT_TIME_GTEQ:
+        return new ExtractTimeFilterOperators.ExtractTimeGtEq(buffer);
+      case EXTRACT_TIME_LT:
+        return new ExtractTimeFilterOperators.ExtractTimeLt(buffer);
+      case EXTRACT_TIME_LTEQ:
+        return new ExtractTimeFilterOperators.ExtractTimeLtEq(buffer);
       default:
         throw new UnsupportedOperationException("Unsupported operator type:" + 
type);
     }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/OperatorType.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/OperatorType.java
index db8be985..bad1ce76 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/OperatorType.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/basic/OperatorType.java
@@ -65,7 +65,15 @@ public enum OperatorType {
 
   // is null
   VALUE_IS_NULL("IS NULL"),
-  VALUE_IS_NOT_NULL("IS NOT NULL");
+  VALUE_IS_NOT_NULL("IS NOT NULL"),
+
+  // extract comparison
+  EXTRACT_TIME_EQ("="),
+  EXTRACT_TIME_NEQ("!="),
+  EXTRACT_TIME_GT(">"),
+  EXTRACT_TIME_GTEQ(">="),
+  EXTRACT_TIME_LT("<"),
+  EXTRACT_TIME_LTEQ("<=");
 
   private final String symbol;
 
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TimeFilterApi.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TimeFilterApi.java
index ce9f6129..3ba55053 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TimeFilterApi.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/factory/TimeFilterApi.java
@@ -19,6 +19,13 @@
 
 package org.apache.tsfile.read.filter.factory;
 
+import 
org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.ExtractTimeEq;
+import 
org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.ExtractTimeGt;
+import 
org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.ExtractTimeGtEq;
+import 
org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.ExtractTimeLt;
+import 
org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.ExtractTimeLtEq;
+import 
org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.ExtractTimeNotEq;
+import org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.Field;
 import org.apache.tsfile.read.filter.operator.GroupByFilter;
 import org.apache.tsfile.read.filter.operator.GroupByMonthFilter;
 import 
org.apache.tsfile.read.filter.operator.TimeFilterOperators.TimeBetweenAnd;
@@ -33,6 +40,7 @@ import 
org.apache.tsfile.read.filter.operator.TimeFilterOperators.TimeNotEq;
 import org.apache.tsfile.read.filter.operator.TimeFilterOperators.TimeNotIn;
 import org.apache.tsfile.utils.TimeDuration;
 
+import java.time.ZoneId;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
@@ -98,4 +106,34 @@ public class TimeFilterApi {
     return new GroupByMonthFilter(
         startTime, endTime, interval, slidingStep, timeZone, currPrecision);
   }
+
+  public static ExtractTimeGt extractTimeGt(
+      long value, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+    return new ExtractTimeGt(value, field, zoneId, currPrecision);
+  }
+
+  public static ExtractTimeGtEq extractTimeGtEq(
+      long value, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+    return new ExtractTimeGtEq(value, field, zoneId, currPrecision);
+  }
+
+  public static ExtractTimeLt extractTimeLt(
+      long value, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+    return new ExtractTimeLt(value, field, zoneId, currPrecision);
+  }
+
+  public static ExtractTimeLtEq extractTimeLtEq(
+      long value, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+    return new ExtractTimeLtEq(value, field, zoneId, currPrecision);
+  }
+
+  public static ExtractTimeEq extractTimeEq(
+      long value, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+    return new ExtractTimeEq(value, field, zoneId, currPrecision);
+  }
+
+  public static ExtractTimeNotEq extractTimeNotEq(
+      long value, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+    return new ExtractTimeNotEq(value, field, zoneId, currPrecision);
+  }
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/ExtractTimeFilterOperators.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/ExtractTimeFilterOperators.java
new file mode 100644
index 00000000..8c43d1bf
--- /dev/null
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/filter/operator/ExtractTimeFilterOperators.java
@@ -0,0 +1,616 @@
+/*
+ * 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.tsfile.read.filter.operator;
+
+import org.apache.tsfile.read.common.TimeRange;
+import org.apache.tsfile.read.filter.basic.Filter;
+import org.apache.tsfile.read.filter.basic.OperatorType;
+import org.apache.tsfile.read.filter.basic.TimeFilter;
+import org.apache.tsfile.read.filter.factory.TimeFilterApi;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.time.DayOfWeek;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
+
+/**
+ * These are the extract time column operators in a filter predicate 
expression tree. They are
+ * constructed by using the methods in {@link TimeFilterApi}
+ */
+public final class ExtractTimeFilterOperators {
+
+  public enum Field {
+    YEAR,
+    QUARTER,
+    MONTH,
+    WEEK,
+    DAY,
+    DAY_OF_MONTH,
+    DAY_OF_WEEK,
+    DOW,
+    DAY_OF_YEAR,
+    DOY,
+    HOUR,
+    MINUTE,
+    SECOND,
+    MS,
+    US,
+    NS
+  }
+
+  private ExtractTimeFilterOperators() {
+    // forbidden construction
+  }
+
+  private static final String OPERATOR_TO_STRING_FORMAT = "extract %s from 
time %s %s";
+
+  abstract static class ExtractTimeCompareFilter extends TimeFilter {
+    protected final long constant;
+
+    protected final Field field;
+    protected final ZoneId zoneId;
+    protected final TimeUnit currPrecision;
+
+    private final transient Function<Long, Long> CAST_TIMESTAMP_TO_MS;
+    private final transient Function<Long, Long> EXTRACT_TIMESTAMP_MS_PART;
+    private final transient Function<Long, Long> EXTRACT_TIMESTAMP_US_PART;
+    private final transient Function<Long, Long> EXTRACT_TIMESTAMP_NS_PART;
+    protected final transient Function<Integer, Long> GET_YEAR_TIMESTAMP;
+
+    // calculate extraction of time
+    protected final transient Function<Long, Long> evaluateFunction;
+    // calculate if the truncations of input times are the same
+    protected final transient BiFunction<Long, Long, Boolean> 
truncatedEqualsFunction;
+
+    // constant cannot be null
+    protected ExtractTimeCompareFilter(
+        long constant, Field field, ZoneId zoneId, TimeUnit currPrecision) {
+      this.constant = constant;
+      this.field = field;
+      this.zoneId = zoneId;
+      this.currPrecision = currPrecision;
+      // make lifestyles of these functions are same with object to avoid 
switch case in calculation
+      switch (currPrecision) {
+        case MICROSECONDS:
+          CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000;
+          EXTRACT_TIMESTAMP_MS_PART = timestamp -> Math.floorMod(timestamp, 
1000_000L) / 1000;
+          EXTRACT_TIMESTAMP_US_PART = timestamp -> Math.floorMod(timestamp, 
1000L);
+          EXTRACT_TIMESTAMP_NS_PART = timestamp -> 0L;
+          GET_YEAR_TIMESTAMP =
+              year ->
+                  Math.multiplyExact(
+                      LocalDate.of(year, 1, 
1).atStartOfDay(zoneId).toEpochSecond(), 1000_000L);
+          break;
+        case NANOSECONDS:
+          CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000000;
+          EXTRACT_TIMESTAMP_MS_PART =
+              timestamp -> Math.floorMod(timestamp, 1000_000_000L) / 1000_000;
+          EXTRACT_TIMESTAMP_US_PART = timestamp -> Math.floorMod(timestamp, 
1000_000L) / 1000;
+          EXTRACT_TIMESTAMP_NS_PART = timestamp -> Math.floorMod(timestamp, 
1000L);
+          GET_YEAR_TIMESTAMP =
+              year ->
+                  Math.multiplyExact(
+                      LocalDate.of(year, 1, 
1).atStartOfDay(zoneId).toEpochSecond(), 1000_000_000L);
+          break;
+        case MILLISECONDS:
+        default:
+          CAST_TIMESTAMP_TO_MS = timestamp -> timestamp;
+          EXTRACT_TIMESTAMP_MS_PART = timestamp -> Math.floorMod(timestamp, 
1000L);
+          EXTRACT_TIMESTAMP_US_PART = timestamp -> 0L;
+          EXTRACT_TIMESTAMP_NS_PART = timestamp -> 0L;
+          GET_YEAR_TIMESTAMP =
+              year ->
+                  Math.multiplyExact(
+                      LocalDate.of(year, 1, 
1).atStartOfDay(zoneId).toEpochSecond(), 1000L);
+          break;
+      }
+      evaluateFunction = constructEvaluateFunction(field, zoneId);
+      truncatedEqualsFunction = constructTruncatedEqualsFunction(field, 
zoneId);
+    }
+
+    protected Function<Long, Long> constructEvaluateFunction(Field field, 
ZoneId zoneId) {
+      switch (field) {
+        case YEAR:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getYear();
+        case QUARTER:
+          return timestamp -> (convertToZonedDateTime(timestamp, 
zoneId).getMonthValue() + 2L) / 3L;
+        case MONTH:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getMonthValue();
+        case WEEK:
+          return timestamp ->
+              convertToZonedDateTime(timestamp, 
zoneId).getLong(ALIGNED_WEEK_OF_YEAR);
+        case DAY:
+        case DAY_OF_MONTH:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getDayOfMonth();
+        case DAY_OF_WEEK:
+        case DOW:
+          return timestamp ->
+              (long) convertToZonedDateTime(timestamp, 
zoneId).getDayOfWeek().getValue();
+        case DAY_OF_YEAR:
+        case DOY:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getDayOfYear();
+        case HOUR:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getHour();
+        case MINUTE:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getMinute();
+        case SECOND:
+          return timestamp -> (long) convertToZonedDateTime(timestamp, 
zoneId).getSecond();
+        case MS:
+          return EXTRACT_TIMESTAMP_MS_PART;
+        case US:
+          return EXTRACT_TIMESTAMP_US_PART;
+        case NS:
+          return EXTRACT_TIMESTAMP_NS_PART;
+        default:
+          throw new UnsupportedOperationException("Unexpected extract field: " 
+ field);
+      }
+    }
+
+    /** Truncate timestamps to based unit then compare */
+    protected BiFunction<Long, Long, Boolean> constructTruncatedEqualsFunction(
+        Field field, ZoneId zoneId) {
+      switch (field) {
+        case YEAR:
+          return (timestamp1, timestamp2) -> true;
+          // base YEAR
+        case QUARTER:
+        case MONTH:
+        case WEEK:
+        case DAY_OF_YEAR:
+        case DOY:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .withMonth(1)
+                  .withDayOfMonth(1)
+                  .truncatedTo(ChronoUnit.DAYS)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, zoneId)
+                          .withMonth(1)
+                          .withDayOfMonth(1)
+                          .truncatedTo(ChronoUnit.DAYS));
+          // base MONTH
+        case DAY:
+        case DAY_OF_MONTH:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .withDayOfMonth(1)
+                  .truncatedTo(ChronoUnit.DAYS)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, zoneId)
+                          .withDayOfMonth(1)
+                          .truncatedTo(ChronoUnit.DAYS));
+          // base WEEK
+        case DAY_OF_WEEK:
+        case DOW:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .with(DayOfWeek.MONDAY)
+                  .truncatedTo(ChronoUnit.DAYS)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, zoneId)
+                          .with(DayOfWeek.MONDAY)
+                          .truncatedTo(ChronoUnit.DAYS));
+          // base DAY
+        case HOUR:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .truncatedTo(ChronoUnit.DAYS)
+                  .equals(convertToZonedDateTime(timestamp2, 
zoneId).truncatedTo(ChronoUnit.DAYS));
+          // base HOUR
+        case MINUTE:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .truncatedTo(ChronoUnit.HOURS)
+                  .equals(convertToZonedDateTime(timestamp2, 
zoneId).truncatedTo(ChronoUnit.HOURS));
+          // base MINUTE
+        case SECOND:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .truncatedTo(ChronoUnit.MINUTES)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, 
zoneId).truncatedTo(ChronoUnit.MINUTES));
+          // base SECOND
+        case MS:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .truncatedTo(ChronoUnit.SECONDS)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, 
zoneId).truncatedTo(ChronoUnit.SECONDS));
+          // base MS
+        case US:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .truncatedTo(ChronoUnit.MILLIS)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, 
zoneId).truncatedTo(ChronoUnit.MILLIS));
+          // base US
+        case NS:
+          return (timestamp1, timestamp2) ->
+              convertToZonedDateTime(timestamp1, zoneId)
+                  .truncatedTo(ChronoUnit.MICROS)
+                  .equals(
+                      convertToZonedDateTime(timestamp2, 
zoneId).truncatedTo(ChronoUnit.MICROS));
+        default:
+          throw new UnsupportedOperationException("Unexpected extract field: " 
+ field);
+      }
+    }
+
+    private ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId 
zoneId) {
+      timestamp = CAST_TIMESTAMP_TO_MS.apply(timestamp);
+      return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId);
+    }
+
+    protected ExtractTimeCompareFilter(ByteBuffer buffer) {
+      this(
+          ReadWriteIOUtils.readLong(buffer),
+          Field.values()[ReadWriteIOUtils.readInt(buffer)],
+          
ZoneId.of(Objects.requireNonNull(ReadWriteIOUtils.readString(buffer))),
+          TimeUnit.values()[ReadWriteIOUtils.readInt(buffer)]);
+    }
+
+    @Override
+    public void serialize(DataOutputStream outputStream) throws IOException {
+      super.serialize(outputStream);
+      ReadWriteIOUtils.write(constant, outputStream);
+      ReadWriteIOUtils.write(field.ordinal(), outputStream);
+      ReadWriteIOUtils.write(zoneId.getId(), outputStream);
+      ReadWriteIOUtils.write(currPrecision.ordinal(), outputStream);
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      ExtractTimeCompareFilter that = (ExtractTimeCompareFilter) o;
+      return constant == that.constant
+          && zoneId.equals(that.zoneId)
+          && currPrecision == that.currPrecision;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          OPERATOR_TO_STRING_FORMAT, field, getOperatorType().getSymbol(), 
constant);
+    }
+  }
+
+  public static final class ExtractTimeEq extends ExtractTimeCompareFilter {
+
+    public ExtractTimeEq(long constant, Field field, ZoneId zoneId, TimeUnit 
currPrecision) {
+      super(constant, field, zoneId, currPrecision);
+    }
+
+    public ExtractTimeEq(ByteBuffer buffer) {
+      super(buffer);
+    }
+
+    @Override
+    public boolean timeSatisfy(long time) {
+      return evaluateFunction.apply(time) == constant;
+    }
+
+    @Override
+    public boolean satisfyStartEndTime(long startTime, long endTime) {
+      return !(truncatedEqualsFunction.apply(startTime, endTime)
+          && (evaluateFunction.apply(endTime) < constant
+              || evaluateFunction.apply(startTime) > constant));
+    }
+
+    @Override
+    public boolean containStartEndTime(long startTime, long endTime) {
+      return truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(startTime) == constant
+          && evaluateFunction.apply(endTime) == constant;
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      if (field == Field.YEAR) {
+        int year = (int) constant;
+        return Collections.singletonList(
+            new TimeRange(GET_YEAR_TIMESTAMP.apply(year), 
GET_YEAR_TIMESTAMP.apply(year + 1) - 1));
+      }
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public Filter reverse() {
+      return new ExtractTimeNotEq(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public OperatorType getOperatorType() {
+      return OperatorType.EXTRACT_TIME_EQ;
+    }
+  }
+
+  public static final class ExtractTimeNotEq extends ExtractTimeCompareFilter {
+
+    public ExtractTimeNotEq(long constant, Field field, ZoneId zoneId, 
TimeUnit currPrecision) {
+      super(constant, field, zoneId, currPrecision);
+    }
+
+    public ExtractTimeNotEq(ByteBuffer buffer) {
+      super(buffer);
+    }
+
+    @Override
+    public boolean timeSatisfy(long time) {
+      return evaluateFunction.apply(time) != constant;
+    }
+
+    @Override
+    public boolean satisfyStartEndTime(long startTime, long endTime) {
+      return !(truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(startTime) == constant
+          && evaluateFunction.apply(endTime) == constant);
+    }
+
+    @Override
+    public boolean containStartEndTime(long startTime, long endTime) {
+      return truncatedEqualsFunction.apply(startTime, endTime)
+          && (evaluateFunction.apply(startTime) > constant
+              || evaluateFunction.apply(endTime) < constant);
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      if (field == Field.YEAR) {
+        List<TimeRange> res = new ArrayList<>();
+        int year = (int) constant;
+        res.add(new TimeRange(Long.MIN_VALUE, GET_YEAR_TIMESTAMP.apply(year) - 
1));
+        res.add(new TimeRange(GET_YEAR_TIMESTAMP.apply(year + 1), 
Long.MAX_VALUE));
+        return res;
+      }
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public Filter reverse() {
+      return new ExtractTimeEq(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public OperatorType getOperatorType() {
+      return OperatorType.EXTRACT_TIME_NEQ;
+    }
+  }
+
+  public static final class ExtractTimeLt extends ExtractTimeCompareFilter {
+
+    public ExtractTimeLt(long constant, Field field, ZoneId zoneId, TimeUnit 
currPrecision) {
+      super(constant, field, zoneId, currPrecision);
+    }
+
+    public ExtractTimeLt(ByteBuffer buffer) {
+      super(buffer);
+    }
+
+    @Override
+    public boolean timeSatisfy(long time) {
+      return evaluateFunction.apply(time) < constant;
+    }
+
+    @Override
+    public boolean satisfyStartEndTime(long startTime, long endTime) {
+      return !(truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(startTime) >= constant);
+    }
+
+    @Override
+    public boolean containStartEndTime(long startTime, long endTime) {
+      return truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(endTime) < constant;
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      if (field == Field.YEAR) {
+        int year = (int) constant;
+        return Collections.singletonList(
+            new TimeRange(Long.MIN_VALUE, GET_YEAR_TIMESTAMP.apply(year) - 1));
+      }
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public Filter reverse() {
+      return new ExtractTimeGtEq(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public OperatorType getOperatorType() {
+      return OperatorType.EXTRACT_TIME_LT;
+    }
+  }
+
+  public static final class ExtractTimeLtEq extends ExtractTimeCompareFilter {
+
+    public ExtractTimeLtEq(long constant, Field field, ZoneId zoneId, TimeUnit 
currPrecision) {
+      super(constant, field, zoneId, currPrecision);
+    }
+
+    public ExtractTimeLtEq(ByteBuffer buffer) {
+      super(buffer);
+    }
+
+    @Override
+    public boolean timeSatisfy(long time) {
+      return evaluateFunction.apply(time) <= constant;
+    }
+
+    @Override
+    public boolean satisfyStartEndTime(long startTime, long endTime) {
+      return !(truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(startTime) > constant);
+    }
+
+    @Override
+    public boolean containStartEndTime(long startTime, long endTime) {
+      return truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(endTime) <= constant;
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      if (field == Field.YEAR) {
+        int year = (int) constant;
+        return Collections.singletonList(
+            new TimeRange(Long.MIN_VALUE, GET_YEAR_TIMESTAMP.apply(year + 1) - 
1));
+      }
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public Filter reverse() {
+      return new ExtractTimeGt(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public OperatorType getOperatorType() {
+      return OperatorType.EXTRACT_TIME_LTEQ;
+    }
+  }
+
+  public static final class ExtractTimeGt extends ExtractTimeCompareFilter {
+
+    public ExtractTimeGt(long constant, Field field, ZoneId zoneId, TimeUnit 
currPrecision) {
+      super(constant, field, zoneId, currPrecision);
+    }
+
+    public ExtractTimeGt(ByteBuffer buffer) {
+      super(buffer);
+    }
+
+    @Override
+    public boolean timeSatisfy(long time) {
+      return evaluateFunction.apply(time) > constant;
+    }
+
+    @Override
+    public boolean satisfyStartEndTime(long startTime, long endTime) {
+      return !(truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(endTime) <= constant);
+    }
+
+    @Override
+    public boolean containStartEndTime(long startTime, long endTime) {
+      return truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(startTime) > constant;
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      if (field == Field.YEAR) {
+        int year = (int) constant;
+        return Collections.singletonList(
+            new TimeRange(GET_YEAR_TIMESTAMP.apply(year + 1), Long.MAX_VALUE));
+      }
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public Filter reverse() {
+      return new ExtractTimeLtEq(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public OperatorType getOperatorType() {
+      return OperatorType.EXTRACT_TIME_GT;
+    }
+  }
+
+  public static final class ExtractTimeGtEq extends ExtractTimeCompareFilter {
+
+    public ExtractTimeGtEq(long constant, Field field, ZoneId zoneId, TimeUnit 
currPrecision) {
+      super(constant, field, zoneId, currPrecision);
+    }
+
+    public ExtractTimeGtEq(ByteBuffer buffer) {
+      super(buffer);
+    }
+
+    @Override
+    public boolean timeSatisfy(long time) {
+      return evaluateFunction.apply(time) >= constant;
+    }
+
+    @Override
+    public boolean satisfyStartEndTime(long startTime, long endTime) {
+      return !(truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(endTime) < constant);
+    }
+
+    @Override
+    public boolean containStartEndTime(long startTime, long endTime) {
+      return truncatedEqualsFunction.apply(startTime, endTime)
+          && evaluateFunction.apply(startTime) >= constant;
+    }
+
+    @Override
+    public List<TimeRange> getTimeRanges() {
+      if (field == Field.YEAR) {
+        int year = (int) constant;
+        return Collections.singletonList(
+            new TimeRange(GET_YEAR_TIMESTAMP.apply(year), Long.MAX_VALUE));
+      }
+      return Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE));
+    }
+
+    @Override
+    public Filter reverse() {
+      return new ExtractTimeLt(constant, field, zoneId, currPrecision);
+    }
+
+    @Override
+    public OperatorType getOperatorType() {
+      return OperatorType.EXTRACT_TIME_GTEQ;
+    }
+  }
+}
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/read/filter/ExtractTimeFilterTest.java
 
b/java/tsfile/src/test/java/org/apache/tsfile/read/filter/ExtractTimeFilterTest.java
new file mode 100644
index 00000000..aff2001c
--- /dev/null
+++ 
b/java/tsfile/src/test/java/org/apache/tsfile/read/filter/ExtractTimeFilterTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.tsfile.read.filter;
+
+import org.apache.tsfile.read.common.TimeRange;
+import org.apache.tsfile.read.filter.basic.Filter;
+import org.apache.tsfile.read.filter.factory.TimeFilterApi;
+import org.apache.tsfile.read.filter.operator.ExtractTimeFilterOperators.Field;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+public class ExtractTimeFilterTest {
+  // 2025/07/08 09:18:51 00:00:00+8:00
+  private final long testTime1 = 1751937531000L;
+  // 2025/07/08 10:18:51 00:00:00+8:00
+  private final long testTime2 = 1751941131000L;
+  private final ZoneId zoneId1 = ZoneId.of("+0000");
+  private final ZoneId zoneId2 = ZoneId.of("+0800");
+
+  private final long DAY_INTERVAL = TimeUnit.DAYS.toMillis(1);
+
+  @Test
+  public void testEq() {
+    // 1751936400000L -> 2025/07/08 09:00:00+8:00
+    // 1751940000000L -> 2025/07/08 10:00:00+8:00
+    Filter extractTimeEq1 =
+        TimeFilterApi.extractTimeEq(1, Field.HOUR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertFalse(extractTimeEq1.satisfy(testTime2, 100));
+    Assert.assertTrue(extractTimeEq1.satisfyStartEndTime(testTime1 - 1, 
testTime1 + 1));
+    Assert.assertTrue(extractTimeEq1.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(extractTimeEq1.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(extractTimeEq1.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertTrue(extractTimeEq1.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertTrue(extractTimeEq1.containStartEndTime(testTime1 - 1, 
testTime1 + 1));
+    Assert.assertFalse(extractTimeEq1.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(extractTimeEq1.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(extractTimeEq1.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(extractTimeEq1.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    Filter extractTimeEq2 =
+        TimeFilterApi.extractTimeEq(9, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq2.satisfy(testTime1, 100));
+    Assert.assertFalse(extractTimeEq2.satisfy(testTime2, 100));
+    Assert.assertTrue(extractTimeEq2.satisfyStartEndTime(testTime1 - 1, 
testTime1 + 1));
+    Assert.assertTrue(extractTimeEq2.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(extractTimeEq2.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(extractTimeEq2.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertTrue(extractTimeEq2.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertTrue(extractTimeEq2.containStartEndTime(testTime1 - 1, 
testTime1 + 1));
+    Assert.assertFalse(extractTimeEq2.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(extractTimeEq2.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(extractTimeEq2.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(extractTimeEq2.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq2.getTimeRanges());
+
+    // test other extracted results
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(2025, Field.YEAR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    // 1735689600000L -> 2025/01/01 00:00:00+00:00
+    // 1767225600000L -> 2026/01/01 00:00:00+00:00
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(1735689600000L, 1767225600000L 
- 1)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(3, Field.QUARTER, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(7, Field.MONTH, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(27, Field.WEEK, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(8, Field.DAY, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 =
+        TimeFilterApi.extractTimeEq(8, Field.DAY_OF_MONTH, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 =
+        TimeFilterApi.extractTimeEq(2, Field.DAY_OF_WEEK, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 =
+        TimeFilterApi.extractTimeEq(189, Field.DAY_OF_YEAR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(18, Field.MINUTE, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(51, Field.SECOND, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(0, Field.MS, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(0, Field.US, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(0, Field.NS, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(testTime1, 100));
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 
Long.MAX_VALUE)),
+        extractTimeEq1.getTimeRanges());
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(0, Field.MS, zoneId1, 
TimeUnit.MICROSECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(1751937531000025L, 100));
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(25, Field.US, zoneId1, 
TimeUnit.MICROSECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(1751937531000025L, 100));
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(0, Field.NS, zoneId1, 
TimeUnit.MICROSECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(1751937531000025L, 100));
+
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(0, Field.MS, zoneId1, 
TimeUnit.NANOSECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(1751937531000025026L, 100));
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(25, Field.US, zoneId1, 
TimeUnit.NANOSECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(1751937531000025026L, 100));
+    extractTimeEq1 = TimeFilterApi.extractTimeEq(26, Field.NS, zoneId1, 
TimeUnit.NANOSECONDS);
+    Assert.assertTrue(extractTimeEq1.satisfy(1751937531000025026L, 100));
+  }
+
+  @Test
+  public void testNotEq() {
+    // 1751936400000L -> 2025/07/08 09:00:00+8:00
+    // 1751940000000L -> 2025/07/08 10:00:00+8:00
+    Filter filter1 = TimeFilterApi.extractTimeNotEq(1, Field.HOUR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter1.satisfy(testTime1, 100));
+    Assert.assertTrue(filter1.satisfy(testTime2, 100));
+    Assert.assertFalse(filter1.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter1.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    // attention: actual contains, but the method returns false
+    Assert.assertFalse(
+        filter1.containStartEndTime(1751940000000L, 1751936400000L + 
DAY_INTERVAL - 1));
+
+    // 1735689600000L -> 2025/01/01 00:00:00+00:00
+    // 1767225600000L -> 2026/01/01 00:00:00+00:00
+    filter1 = TimeFilterApi.extractTimeNotEq(2025, Field.YEAR, zoneId1, 
TimeUnit.MICROSECONDS);
+    Assert.assertEquals(
+        Arrays.asList(
+            new TimeRange(Long.MIN_VALUE, 1735689600000_000L - 1),
+            new TimeRange(1767225600000_000L, Long.MAX_VALUE)),
+        filter1.getTimeRanges());
+
+    Filter filter2 = TimeFilterApi.extractTimeNotEq(9, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter2.satisfy(testTime1, 100));
+    Assert.assertTrue(filter2.satisfy(testTime2, 100));
+    Assert.assertFalse(filter2.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    // attention: actual contains, but the method returns false
+    Assert.assertFalse(
+        filter2.containStartEndTime(1751940000000L, 1751936400000L + 
DAY_INTERVAL - 1));
+
+    // 1735660800000L -> 2025/01/01 00:00:00+08:00
+    // 1767196800000L -> 2026/01/01 00:00:00+08:00
+    filter2 = TimeFilterApi.extractTimeNotEq(2025, Field.YEAR, zoneId2, 
TimeUnit.MICROSECONDS);
+    Assert.assertEquals(
+        Arrays.asList(
+            new TimeRange(Long.MIN_VALUE, 1735660800000_000L - 1),
+            new TimeRange(1767196800000_000L, Long.MAX_VALUE)),
+        filter2.getTimeRanges());
+  }
+
+  @Test
+  public void testGt() {
+    // 1751936400000L -> 2025/07/08 09:00:00+8:00
+    // 1751940000000L -> 2025/07/08 10:00:00+8:00
+    Filter filter1 = TimeFilterApi.extractTimeGt(5, Field.HOUR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter1.satisfy(testTime1, 100));
+    Assert.assertFalse(filter1.satisfy(testTime2, 100));
+    Assert.assertFalse(filter1.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertFalse(filter1.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter1.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    // 1735689600000L -> 2025/01/01 00:00:00+00:00
+    // 1767225600000L -> 2026/01/01 00:00:00+00:00
+    filter1 = TimeFilterApi.extractTimeGt(2025, Field.YEAR, zoneId1, 
TimeUnit.MICROSECONDS);
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(1767225600000_000L, 
Long.MAX_VALUE)),
+        filter1.getTimeRanges());
+
+    Filter filter2 = TimeFilterApi.extractTimeGt(5, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(filter2.satisfy(testTime1, 100));
+    Assert.assertTrue(filter2.satisfy(testTime2, 100));
+    Assert.assertTrue(filter2.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertTrue(filter2.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    Filter filter3 = TimeFilterApi.extractTimeGt(9, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter3.satisfy(testTime1, 100));
+    Assert.assertTrue(filter3.satisfy(testTime2, 100));
+    Assert.assertTrue(filter3.satisfyStartEndTime(testTime1, testTime2));
+    Assert.assertFalse(filter3.containStartEndTime(testTime1, testTime2));
+  }
+
+  @Test
+  public void testGtEq() {
+    Filter filter1 = TimeFilterApi.extractTimeGtEq(5, Field.HOUR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter1.satisfy(testTime1, 100));
+    Assert.assertFalse(filter1.satisfy(testTime2, 100));
+    Assert.assertFalse(filter1.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertFalse(filter1.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter1.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    // 1735689600000L -> 2025/01/01 00:00:00+00:00
+    // 1767225600000L -> 2026/01/01 00:00:00+00:00
+    filter1 = TimeFilterApi.extractTimeGtEq(2025, Field.YEAR, zoneId1, 
TimeUnit.NANOSECONDS);
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(1735689600000_000_000L, 
Long.MAX_VALUE)),
+        filter1.getTimeRanges());
+
+    Filter filter2 = TimeFilterApi.extractTimeGtEq(5, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(filter2.satisfy(testTime1, 100));
+    Assert.assertTrue(filter2.satisfy(testTime2, 100));
+    Assert.assertTrue(filter2.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertTrue(filter2.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter2.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    Filter filter3 = TimeFilterApi.extractTimeGtEq(9, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(filter3.satisfy(testTime1, 100));
+    Assert.assertTrue(filter3.satisfy(testTime2, 100));
+    Assert.assertTrue(filter3.satisfyStartEndTime(testTime1, testTime2));
+    Assert.assertTrue(filter3.containStartEndTime(testTime1, testTime2));
+  }
+
+  @Test
+  public void testLt() {
+    Filter filter1 = TimeFilterApi.extractTimeLt(5, Field.HOUR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(filter1.satisfy(testTime1, 100));
+    Assert.assertTrue(filter1.satisfy(testTime2, 100));
+    Assert.assertTrue(filter1.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertTrue(filter1.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    // 1735689600000L -> 2025/01/01 00:00:00+00:00
+    // 1767225600000L -> 2026/01/01 00:00:00+00:00
+    filter1 = TimeFilterApi.extractTimeLt(2025, Field.YEAR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 1735689600000L 
- 1)),
+        filter1.getTimeRanges());
+
+    Filter filter2 = TimeFilterApi.extractTimeLt(5, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter2.satisfy(testTime1, 100));
+    Assert.assertFalse(filter2.satisfy(testTime2, 100));
+    Assert.assertFalse(filter2.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertFalse(filter2.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter2.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    Filter filter3 = TimeFilterApi.extractTimeGtEq(9, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(filter3.satisfy(testTime1, 100));
+    Assert.assertTrue(filter3.satisfy(testTime2, 100));
+    Assert.assertTrue(filter3.satisfyStartEndTime(testTime1, testTime2));
+    Assert.assertTrue(filter3.containStartEndTime(testTime1, testTime2));
+  }
+
+  @Test
+  public void testLtEq() {
+    // 1751936400000L -> 2025/07/08 09:00:00+8:00
+    // 1751940000000L -> 2025/07/08 10:00:00+8:00
+    Filter filter1 = TimeFilterApi.extractTimeLtEq(5, Field.HOUR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertTrue(filter1.satisfy(testTime1, 100));
+    Assert.assertTrue(filter1.satisfy(testTime2, 100));
+    Assert.assertTrue(filter1.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter1.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertTrue(filter1.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertTrue(filter1.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter1.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    // 1735689600000L -> 2025/01/01 00:00:00+00:00
+    // 1767225600000L -> 2026/01/01 00:00:00+00:00
+    filter1 = TimeFilterApi.extractTimeLtEq(2025, Field.YEAR, zoneId1, 
TimeUnit.MILLISECONDS);
+    Assert.assertEquals(
+        Collections.singletonList(new TimeRange(Long.MIN_VALUE, 1767225600000L 
- 1)),
+        filter1.getTimeRanges());
+
+    Filter filter2 = TimeFilterApi.extractTimeLt(5, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter2.satisfy(testTime1, 100));
+    Assert.assertFalse(filter2.satisfy(testTime2, 100));
+    Assert.assertFalse(filter2.satisfyStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertTrue(filter2.satisfyStartEndTime(1751936400000L, 
2751936400000L));
+    Assert.assertFalse(filter2.satisfyStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter2.satisfyStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
1751940000000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(testTime1 - 1, testTime1 + 
1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 1, 
1751940000000L));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 1, 
testTime1 + 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L - 2, 
1751936400000L - 1));
+    Assert.assertFalse(filter2.containStartEndTime(1751936400000L, 
2751936400000L));
+
+    Filter filter3 = TimeFilterApi.extractTimeGt(9, Field.HOUR, zoneId2, 
TimeUnit.MILLISECONDS);
+    Assert.assertFalse(filter3.satisfy(testTime1, 100));
+    Assert.assertTrue(filter3.satisfy(testTime2, 100));
+    Assert.assertTrue(filter3.satisfyStartEndTime(testTime1, testTime2));
+    Assert.assertFalse(filter3.containStartEndTime(testTime1, testTime2));
+  }
+}

Reply via email to