Skip to content

Commit

Permalink
Add filtering by date ranges with support for <= and >= edges.
Browse files Browse the repository at this point in the history
Signed-off-by: Łukasz Dywicki <[email protected]>
  • Loading branch information
splatch committed Feb 27, 2024
1 parent bad54f4 commit b7d8d8e
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,12 @@ private static Stream<Entry<ZonedDateTime, State>> paging(Stream<Entry<ZonedDate

private static boolean evaluate(Entry<ZonedDateTime, State> entry, FilterCriteria criteria) {
ZonedDateTime beginDate = criteria.getBeginDate();
if (beginDate != null && entry.getKey().isBefore(beginDate)) {
if (beginDate != null && !(entry.getKey().isAfter(beginDate) || beginDate.isEqual(entry.getKey()))) {
return false;
}

ZonedDateTime endDate = criteria.getEndDate();
if (endDate != null && entry.getKey().isAfter(endDate)) {
if (endDate != null && !(entry.getKey().isBefore(endDate) || endDate.isEqual(entry.getKey()))) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,30 @@
package org.connectorio.addons.persistence.memory.internal;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import org.assertj.core.api.IterableAssert;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.assertj.core.api.ListAssert;
import org.connectorio.addons.test.ItemMutation;
import org.connectorio.addons.test.StubItemBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.ArgumentConverter;
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.items.Item;
Expand Down Expand Up @@ -62,10 +71,10 @@ void testWriteAndQuery() {
.hasSize(1)
.element(0).matches(info -> info.getCount() == 1);

assertThat(service.query(new FilterCriteria().setItemName(TEST_1)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1)))
.hasSize(1);

assertThat(service.query(new FilterCriteria().setItemName(TEST_2)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_2)))
.isEmpty();
}

Expand All @@ -81,12 +90,12 @@ void testWriteAndSortingQuery() {
.hasSize(1)
.element(0).matches(info -> info.getCount() == 2);

IterableAssert<HistoricItem> itemAssert = assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setOrdering(Ordering.DESCENDING)))
ListAssert<HistoricItem> itemAssert = assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1).setOrdering(Ordering.DESCENDING)))
.hasSize(2);
itemAssert.element(0).matches(state -> state.getState().equals(new DecimalType(20)));
itemAssert.element(1).matches(state -> state.getState().equals(new DecimalType(10)));

assertThat(service.query(new FilterCriteria().setItemName(TEST_2)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_2)))
.isEmpty();
}

Expand All @@ -106,29 +115,31 @@ void testPaging() {
.hasSize(1)
.element(0).matches(info -> info.getCount() == 4);

assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageSize(1)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1).setPageSize(1)))
.hasSize(1)
.element(0).matches(state -> state.getState().equals(new DecimalType(40)));

assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(1)))
assertThat(
fetch(service, new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(1)))
.hasSize(1)
.element(0).matches(state -> state.getState().equals(new DecimalType(30)));

assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(2).setPageSize(1)))
assertThat(
fetch(service, new FilterCriteria().setItemName(TEST_1).setPageNumber(2).setPageSize(1)))
.hasSize(1)
.element(0).matches(state -> state.getState().equals(new DecimalType(20)));

assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageSize(3).setOrdering(Ordering.ASCENDING)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1).setPageSize(3).setOrdering(Ordering.ASCENDING)))
.hasSize(3)
.extracting(HistoricItem::getState)
.containsExactly(new DecimalType(10), new DecimalType(20), new DecimalType(30));

assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(2).setOrdering(Ordering.ASCENDING)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(2).setOrdering(Ordering.ASCENDING)))
.hasSize(2)
.extracting(HistoricItem::getState)
.containsExactly(new DecimalType(30), new DecimalType(40));

assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(2)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(2)))
.hasSize(2)
.extracting(HistoricItem::getState)
.containsExactly(new DecimalType(20), new DecimalType(10));
Expand All @@ -145,14 +156,62 @@ void testWriteSameDate() {
.hasSize(1)
.element(0).matches(info -> info.getCount() == 1);

IterableAssert<HistoricItem> itemAssert = assertThat(service.query(new FilterCriteria().setItemName(TEST_2).setOrdering(Ordering.DESCENDING)))
ListAssert<HistoricItem> itemAssert = assertThat(
fetch(service, new FilterCriteria().setItemName(TEST_2).setOrdering(Ordering.DESCENDING)))
.hasSize(1);
itemAssert.element(0).matches(state -> state.getState().equals(new DecimalType(20)));

assertThat(service.query(new FilterCriteria().setItemName(TEST_1)))
assertThat(fetch(service, new FilterCriteria().setItemName(TEST_1)))
.isEmpty();
}

@ParameterizedTest
@MethodSource("source")
void testDateFilters(
@ConvertWith(DateConverter.class) ZonedDateTime begin,
@ConvertWith(DateConverter.class) ZonedDateTime end,
@ConvertWith(DateConverter.class) ZonedDateTime firstTimestamp,
@ConvertWith(DateConverter.class) ZonedDateTime secondTimestamp,
Integer firstValue, Integer secondValue
) {
MemoryPersistenceService service = new MemoryPersistenceService(tz);
service.store(item2, Date.from(firstTimestamp.toInstant()), new DecimalType(10));
service.store(item2, Date.from(secondTimestamp.toInstant()), new DecimalType(20));

assertThat(service.getItemInfo())
.hasSize(1)
.element(0).matches(info -> info.getCount() == 2);

FilterCriteria criteria = new FilterCriteria().setItemName(TEST_2).setOrdering(Ordering.ASCENDING);
if (begin != null) {
criteria.setBeginDate(begin);
}
if (end != null) {
criteria.setEndDate(end);
}

int size = (firstValue == null ? 0 : 1) + (secondValue == null ? 0 : 1);

ListAssert<HistoricItem> itemAssert = assertThat(fetch(service, criteria))
.hasSize(size);
if (firstValue != null) {
itemAssert.element(0).matches(state -> state.getState().equals(new DecimalType(firstValue)))
.describedAs("First value should match %d", firstValue);
}
if (secondValue != null) {
itemAssert.element(1).matches(state -> state.getState().equals(new DecimalType(secondValue)))
.describedAs("Second value should match %d", secondValue);
}
}

public static Stream<Arguments> source() {
return Stream.of(
Arguments.of("2024.02.22 18:59:59.999Z", "2024.02.22 19:15:00.000Z", "2024.02.22 18:59:59.999Z", "2024.02.22 19:15:00.000Z", 10, 20),
Arguments.of("2024.02.22 18:50:59.999Z", "2024.02.22 19:25:00.000Z", "2024.02.22 18:59:59.999Z", "2024.02.22 19:15:00.000Z", 10, 20),
Arguments.of("2024.02.22 18:59:59.999Z", "2024.02.22 19:15:00.000Z", "2024.02.22 18:50:59.999Z", "2024.02.22 19:25:00.000Z", null, null)
);
}

public static ZonedDateTime createInstant(int year, int month, int day, int hour, int minute, int second) {
return createInstant(year, month, day, hour, minute, second, 0);
}
Expand All @@ -161,4 +220,26 @@ public static ZonedDateTime createInstant(int year, int month, int day, int hour
return ZonedDateTime.of(LocalDate.of(year, month, day), LocalTime.of(hour, minute, second, nanos), ZoneOffset.UTC);
}

}
public List<HistoricItem> fetch(MemoryPersistenceService service, FilterCriteria criteria) {
Iterable<HistoricItem> results = service.query(criteria);
if (results instanceof List) {
return (List<HistoricItem>) results;
}
return StreamSupport.stream(results.spliterator(), false).collect(Collectors.toList());
}

static class DateConverter implements ArgumentConverter {

private final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss.SSS'Z'");

@Override
public ZonedDateTime convert(Object source, ParameterContext context) throws ArgumentConversionException {
if (source instanceof String) {
return LocalDateTime.parse((CharSequence) source, FORMAT).atZone(ZoneOffset.UTC);
}
// non-convertible
return null;
}
}

}

0 comments on commit b7d8d8e

Please sign in to comment.