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 cc48210d1..db5276e8d 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.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 static Filter deserialize(ByteBuffer buffer) { 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 db8be9854..bad1ce76e 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 ce9f61290..3ba550531 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.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 static GroupByMonthFilter groupByMonth( 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 000000000..8c43d1bfe --- /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 CAST_TIMESTAMP_TO_MS; + private final transient Function EXTRACT_TIMESTAMP_MS_PART; + private final transient Function EXTRACT_TIMESTAMP_US_PART; + private final transient Function EXTRACT_TIMESTAMP_NS_PART; + protected final transient Function GET_YEAR_TIMESTAMP; + + // calculate extraction of time + protected final transient Function evaluateFunction; + // calculate if the truncations of input times are the same + protected final transient BiFunction 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 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 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 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 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 getTimeRanges() { + if (field == Field.YEAR) { + List 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 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 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 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 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 000000000..aff2001cf --- /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)); + } +}