From 0e82bfc57ef3379281b6e850ab87cdb80632f953 Mon Sep 17 00:00:00 2001 From: Mark Herwege Date: Wed, 10 Jan 2024 16:59:34 +0100 Subject: [PATCH] persistence extensions future and QuantityTypes Signed-off-by: Mark Herwege --- .../extensions/PersistenceExtensions.java | 1617 +++++++++--- .../extensions/PersistenceExtensionsTest.java | 2181 +++++++++++++---- .../TestCachedValuesPersistenceService.java | 32 +- .../extensions/TestPersistenceService.java | 126 +- 4 files changed, 3066 insertions(+), 890 deletions(-) diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java index 62909962d04..94b7efeaffd 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/extensions/PersistenceExtensions.java @@ -15,6 +15,7 @@ import java.math.BigDecimal; import java.math.MathContext; import java.time.Duration; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; @@ -22,16 +23,24 @@ import java.util.List; import java.util.stream.StreamSupport; +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.items.Item; +import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; import org.openhab.core.persistence.FilterCriteria; import org.openhab.core.persistence.FilterCriteria.Ordering; import org.openhab.core.persistence.HistoricItem; +import org.openhab.core.persistence.ModifiablePersistenceService; import org.openhab.core.persistence.PersistenceService; import org.openhab.core.persistence.PersistenceServiceRegistry; import org.openhab.core.persistence.QueryablePersistenceService; import org.openhab.core.types.State; +import org.openhab.core.types.TimeSeries; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -48,15 +57,30 @@ * @author Jan N. Klug - Added sumSince * @author John Cocula - Added sumSince * @author Jan N. Klug - Added interval methods and refactoring + * @author Mark Herwege - Changed return types to State for some interval methods to also return unit + * @author Mark Herwege - Extended for future dates */ @Component(immediate = true) +@NonNullByDefault public class PersistenceExtensions { - private static PersistenceServiceRegistry registry; + private static @Nullable PersistenceServiceRegistry registry; + private static @Nullable TimeZoneProvider timeZoneProvider; @Activate - public PersistenceExtensions(@Reference PersistenceServiceRegistry registry) { + public PersistenceExtensions(@Reference PersistenceServiceRegistry registry, + @Reference TimeZoneProvider timeZoneProvider) { PersistenceExtensions.registry = registry; + PersistenceExtensions.timeZoneProvider = timeZoneProvider; + } + + /** + * Persists the state of a given item through the default persistence service. + * + * @param item the item to store + */ + public static void persist(Item item) { + internalPersist(item); } /** @@ -67,42 +91,137 @@ public PersistenceExtensions(@Reference PersistenceServiceRegistry registry) { * @param serviceId the name of the {@link PersistenceService} to use */ public static void persist(Item item, String serviceId) { + internalPersist(item, serviceId); + } + + private static void internalPersist(Item item) { + String serviceId = getDefaultServiceId(); + if (serviceId == null) { + return; + } + internalPersist(item, serviceId); + } + + private static void internalPersist(Item item, String serviceId) { PersistenceService service = getService(serviceId); if (service != null) { service.store(item); - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("There is no persistence service registered with the id '{}'", serviceId); + return; } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no persistence service registered with the id '{}'", serviceId); } /** - * Persists the state of a given item through the default persistence service. + * Persists a state at a given timestamp of an item through the default + * persistence service. * * @param item the item to store + * @param timestamp the date for the item state to be stored + * @param state the state to be stored */ - public static void persist(Item item) { - persist(item, getDefaultServiceId()); + public static void persist(Item item, ZonedDateTime timestamp, State state) { + internalPersist(item, timestamp, state); + } + + /** + * Persists a state at a given timestamp of an item through a + * {@link PersistenceService} identified by the serviceId. + * + * @param item the item + * @param timestamp the date for the item state to be stored + * @param state the state to be stored + * @param serviceId the name of the {@link PersistenceService} to use + */ + public static void persist(Item item, ZonedDateTime timestamp, State state, String serviceId) { + internalPersist(item, timestamp, state, serviceId); + } + + private static void internalPersist(Item item, ZonedDateTime timestamp, State state) { + String serviceId = getDefaultServiceId(); + if (serviceId == null) { + return; + } + internalPersist(item, timestamp, state, serviceId); + } + + private static void internalPersist(Item item, ZonedDateTime timestamp, State state, String serviceId) { + PersistenceService service = getService(serviceId); + if (service != null && service instanceof ModifiablePersistenceService modifiableService) { + modifiableService.store(item, timestamp, state, serviceId); + return; + } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no modifiable persistence service registered with the id '{}'", serviceId); + } + + /** + * Persists a timeSeries of an item through the default persistence service. + * + * @param item the item to store + * @param timeSeries the timeSeries of states to be stored + */ + public static void persist(Item item, TimeSeries timeSeries) { + internalPersist(item, timeSeries); + } + + /** + * Persists a timeSeries of an item through a {@link PersistenceService} identified by the + * serviceId. + * + * @param item the item + * @param timeSeries the timeSeries of states to be stored + * @param serviceId the name of the {@link PersistenceService} to use + */ + public static void persist(Item item, TimeSeries timeSeries, String serviceId) { + internalPersist(item, timeSeries, serviceId); + } + + private static void internalPersist(Item item, TimeSeries timeSeries) { + String serviceId = getDefaultServiceId(); + if (serviceId == null) { + return; + } + internalPersist(item, timeSeries, serviceId); + } + + private static void internalPersist(Item item, TimeSeries timeSeries, String serviceId) { + PersistenceService service = getService(serviceId); + ZoneId timeZone = timeZoneProvider != null ? timeZoneProvider.getTimeZone() : ZoneId.systemDefault(); + if (service != null && service instanceof ModifiablePersistenceService modifiableService) { + timeSeries.getStates() + .forEach(s -> modifiableService.store(item, s.timestamp().atZone(timeZone), s.state(), serviceId)); + return; + } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no modifiable persistence service registered with the id '{}'", serviceId); } /** * Retrieves the historic item for a given item at a certain point in time through the default * persistence service. * + * This method has been deprecated and {@link #persistedState(Item, ZonedDateTime)} should be used instead. + * * @param item the item for which to retrieve the historic item * @param timestamp the point in time for which the historic item should be retrieved * @return the historic item at the given point in time, or null if no historic item could be found, * the default persistence service is not available or does not refer to a * {@link QueryablePersistenceService} */ + @Deprecated public static @Nullable HistoricItem historicState(Item item, ZonedDateTime timestamp) { - return historicState(item, timestamp, getDefaultServiceId()); + LoggerFactory.getLogger(PersistenceExtensions.class).info( + "The historicState method has been deprecated and will be removed in a future version, use persistedState instead."); + return internalPersistedState(item, timestamp); } /** * Retrieves the historic item for a given item at a certain point in time through a * {@link PersistenceService} identified by the serviceId. * + * This method has been deprecated and {@link #persistedState(Item, ZonedDateTime, String)} should be used instead. + * * @param item the item for which to retrieve the historic item * @param timestamp the point in time for which the historic item should be retrieved * @param serviceId the name of the {@link PersistenceService} to use @@ -110,9 +229,55 @@ public static void persist(Item item) { * if the provided serviceId does not refer to an available * {@link QueryablePersistenceService} */ + @Deprecated public static @Nullable HistoricItem historicState(Item item, ZonedDateTime timestamp, String serviceId) { + LoggerFactory.getLogger(PersistenceExtensions.class).info( + "The historicState method has been deprecated and will be removed in a future version, use persistedState instead."); + return internalPersistedState(item, timestamp, serviceId); + } + + /** + * Retrieves the persisted item for a given item at a certain point in time through the default + * persistence service. + * + * @param item the item for which to retrieve the persisted item + * @param timestamp the point in time for which the persisted item should be retrieved + * @return the historic item at the given point in time, or null if no persisted item could be found, + * the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem persistedState(Item item, ZonedDateTime timestamp) { + return internalPersistedState(item, timestamp); + } + + /** + * Retrieves the persisted item for a given item at a certain point in time through a + * {@link PersistenceService} identified by the serviceId. + * + * @param item the item for which to retrieve the persisted item + * @param timestamp the point in time for which the persisted item should be retrieved + * @param serviceId the name of the {@link PersistenceService} to use + * @return the persisted item at the given point in time, or null if no persisted item could be found + * or + * if the provided serviceId does not refer to an available + * {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem persistedState(Item item, ZonedDateTime timestamp, String serviceId) { + return internalPersistedState(item, timestamp, serviceId); + } + + private static @Nullable HistoricItem internalPersistedState(Item item, @Nullable ZonedDateTime timestamp) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalPersistedState(item, timestamp, serviceId) : null; + } + + private static @Nullable HistoricItem internalPersistedState(Item item, @Nullable ZonedDateTime timestamp, + String serviceId) { + if (timestamp == null) { + return null; + } PersistenceService service = getService(serviceId); - if (service instanceof QueryablePersistenceService qService) { + if (service != null && service instanceof QueryablePersistenceService qService) { FilterCriteria filter = new FilterCriteria(); filter.setEndDate(timestamp); filter.setItemName(item.getName()); @@ -124,40 +289,82 @@ public static void persist(Item item) { } else { return null; } - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("There is no queryable persistence service registered with the id '{}'", serviceId); - return null; } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no queryable persistence service registered with the id '{}'", serviceId); + return null; } /** - * Query the last update time of a given item. The default persistence service is used. + * Query the last historic update time of a given item. The default persistence service is used. * - * @param item the item for which the last update time is to be returned - * @return point in time of the last update to item, or null if there are no previously + * @param item the item for which the last historic update time is to be returned + * @return point in time of the last historic update to item, or null if there are no + * historic * persisted updates or the default persistence service is not available or a * {@link QueryablePersistenceService} */ public static @Nullable ZonedDateTime lastUpdate(Item item) { - return lastUpdate(item, getDefaultServiceId()); + return internalAdjacentUpdate(item, false); } /** - * Query for the last update time of a given item. + * Query for the last historic update time of a given item. * - * @param item the item for which the last update time is to be returned + * @param item the item for which the last historic update time is to be returned * @param serviceId the name of the {@link PersistenceService} to use - * @return last time item was updated, or null if there are no previously + * @return point in time of the last historic update to item, or null if there are no + * historic * persisted updates or if persistence service given by serviceId does not refer to an * available {@link QueryablePersistenceService} */ public static @Nullable ZonedDateTime lastUpdate(Item item, String serviceId) { + return internalAdjacentUpdate(item, false, serviceId); + } + + /** + * Query the first future update time of a given item. The default persistence service is used. + * + * @param item the item for which the first future update time is to be returned + * @return point in time of the first future update to item, or null if there are no + * future + * persisted updates or the default persistence service is not available or a + * {@link QueryablePersistenceService} + */ + public static @Nullable ZonedDateTime nextUpdate(Item item) { + return internalAdjacentUpdate(item, true); + } + + /** + * Query for the first future update time of a given item. + * + * @param item the item for which the first future update time is to be returned + * @param serviceId the name of the {@link PersistenceService} to use + * @return point in time of the first future update to item, or null if there are no + * future + * persisted updates or if persistence service given by serviceId does not refer to an + * available {@link QueryablePersistenceService} + */ + public static @Nullable ZonedDateTime nextUpdate(Item item, String serviceId) { + return internalAdjacentUpdate(item, true, serviceId); + } + + private static @Nullable ZonedDateTime internalAdjacentUpdate(Item item, boolean forward) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalAdjacentUpdate(item, forward, serviceId) : null; + } + + private static @Nullable ZonedDateTime internalAdjacentUpdate(Item item, boolean forward, String serviceId) { PersistenceService service = getService(serviceId); - if (service instanceof QueryablePersistenceService qService) { + if (service != null && service instanceof QueryablePersistenceService qService) { FilterCriteria filter = new FilterCriteria(); filter.setItemName(item.getName()); - filter.setOrdering(Ordering.DESCENDING); + if (forward) { + filter.setBeginDate(ZonedDateTime.now()); + } else { + filter.setEndDate(ZonedDateTime.now()); + } + filter.setOrdering(forward ? Ordering.ASCENDING : Ordering.DESCENDING); filter.setPageSize(1); Iterable result = qService.query(filter); if (result.iterator().hasNext()) { @@ -165,11 +372,10 @@ public static void persist(Item item) { } else { return null; } - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("There is no queryable persistence service registered with the id '{}'", serviceId); - return null; } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no queryable persistence service registered with the id '{}'", serviceId); + return null; } /** @@ -180,7 +386,7 @@ public static void persist(Item item) { * persistence service is not configured or does not refer to a {@link QueryablePersistenceService} */ public static @Nullable HistoricItem previousState(Item item) { - return previousState(item, false); + return internalAdjacentState(item, false, false); } /** @@ -192,7 +398,20 @@ public static void persist(Item item) { * persistence service is not configured or does not refer to a {@link QueryablePersistenceService} */ public static @Nullable HistoricItem previousState(Item item, boolean skipEqual) { - return previousState(item, skipEqual, getDefaultServiceId()); + return internalAdjacentState(item, skipEqual, false); + } + + /** + * Returns the previous state of a given item. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to get the previous state value for + * @param serviceId the name of the {@link PersistenceService} to use + * @return the previous state or null if no previous state could be found, or if the default + * persistence service is not configured or does not refer to a {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem previousState(Item item, String serviceId) { + return internalAdjacentState(item, false, false, serviceId); } /** @@ -207,11 +426,78 @@ public static void persist(Item item) { * serviceId is not available or does not refer to a {@link QueryablePersistenceService} */ public static @Nullable HistoricItem previousState(Item item, boolean skipEqual, String serviceId) { + return internalAdjacentState(item, skipEqual, false, serviceId); + + } + + /** + * Returns the next state of a given item. + * + * @param item the item to get the next state value for + * @return the next state or null if no next state could be found, or if the default + * persistence service is not configured or does not refer to a {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem nextState(Item item) { + return internalAdjacentState(item, false, true); + } + + /** + * Returns the next state of a given item. + * + * @param item the item to get the previous state value for + * @param skipEqual if true, skips equal state values and searches the first state not equal the current state + * @return the next state or null if no next state could be found, or if the default + * persistence service is not configured or does not refer to a {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem nextState(Item item, boolean skipEqual) { + return internalAdjacentState(item, skipEqual, true); + } + + /** + * Returns the next state of a given item. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to get the next state value for + * @param serviceId the name of the {@link PersistenceService} to use + * @return the next state or null if no next state could be found, or if the default + * persistence service is not configured or does not refer to a {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem nextState(Item item, String serviceId) { + return internalAdjacentState(item, false, true, serviceId); + } + + /** + * Returns the next state of a given item. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to get the next state value for + * @param skipEqual if true, skips equal state values and searches the first state not equal the + * current state + * @param serviceId the name of the {@link PersistenceService} to use + * @return the next state or null if no next state could be found, or if the given + * serviceId is not available or does not refer to a {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem nextState(Item item, boolean skipEqual, String serviceId) { + return internalAdjacentState(item, skipEqual, true, serviceId); + } + + private static @Nullable HistoricItem internalAdjacentState(Item item, boolean skipEqual, boolean forward) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalAdjacentState(item, skipEqual, forward, serviceId) : null; + } + + private static @Nullable HistoricItem internalAdjacentState(Item item, boolean skipEqual, boolean forward, + String serviceId) { PersistenceService service = getService(serviceId); - if (service instanceof QueryablePersistenceService qService) { + if (service != null && service instanceof QueryablePersistenceService qService) { FilterCriteria filter = new FilterCriteria(); filter.setItemName(item.getName()); - filter.setOrdering(Ordering.DESCENDING); + if (forward) { + filter.setBeginDate(ZonedDateTime.now()); + } else { + filter.setEndDate(ZonedDateTime.now()); + } + filter.setOrdering(forward ? Ordering.ASCENDING : Ordering.DESCENDING); filter.setPageSize(skipEqual ? 1000 : 1); int startPage = 0; @@ -235,10 +521,9 @@ public static void persist(Item item) { items = null; } } - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("There is no queryable persistence service registered with the id '{}'", serviceId); } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no queryable persistence service registered with the id '{}'", serviceId); return null; } @@ -248,24 +533,40 @@ public static void persist(Item item) { * * @param item the item to check for state changes * @param timestamp the point in time to start the check - * @return true if item state has changed, false if it has not changed or if the default - * persistence service is not available or does not refer to a {@link QueryablePersistenceService} + * @return true if item state has changed, false if it has not changed, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable Boolean changedSince(Item item, ZonedDateTime timestamp) { + return internalChangedBetween(item, timestamp, null); + } + + /** + * Checks if the state of a given item will change by a certain point in time. + * The default persistence service is used. + * + * @param item the item to check for state changes + * @param timestamp the point in time to end the check + * @return true if item state will change, false if it will not change, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static boolean changedSince(Item item, ZonedDateTime timestamp) { - return changedSince(item, timestamp, getDefaultServiceId()); + public static @Nullable Boolean changedTill(Item item, ZonedDateTime timestamp) { + return internalChangedBetween(item, null, timestamp); } /** - * Checks if the state of a given item has changed between two points in time. + * Checks if the state of a given item changes between two points in time. * The default persistence service is used. * * @param item the item to check for state changes - * @return true if item state changed, false if either item has not been changed in - * the given interval or if the default persistence does not refer to a {@link QueryablePersistenceService}, - * or null if the default persistence service is not available + * @return true if item state changes, false if either item does not change in + * the given interval, null if the default persistence does not refer to a + * {@link QueryablePersistenceService}, or null if the default persistence service is not + * available */ - public static boolean changedBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return changedBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable Boolean changedBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalChangedBetween(item, begin, end); } /** @@ -275,49 +576,75 @@ public static boolean changedBetween(Item item, ZonedDateTime begin, ZonedDateTi * @param item the item to check for state changes * @param timestamp the point in time to start the check * @param serviceId the name of the {@link PersistenceService} to use - * @return true if item state has changed, or false if it has not changed or if the - * provided serviceId does not refer to an available {@link QueryablePersistenceService} + * @return true if item state has changed, or false if it has not changed, + * null if the provided serviceId does not refer to an available + * {@link QueryablePersistenceService} + */ + public static @Nullable Boolean changedSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalChangedBetween(item, timestamp, null, serviceId); + } + + /** + * Checks if the state of a given item will change by a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to check for state changes + * @param timestamp the point in time to end the check + * @param serviceId the name of the {@link PersistenceService} to use + * @return true if item state will change, or false if it will not change, + * null if the provided serviceId does not refer to an available + * {@link QueryablePersistenceService} */ - public static boolean changedSince(Item item, ZonedDateTime timestamp, String serviceId) { - return internalChanged(item, timestamp, null, serviceId); + public static @Nullable Boolean changedTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalChangedBetween(item, null, timestamp, serviceId); } /** - * Checks if the state of a given item changed between two points in time. + * Checks if the state of a given item changes between two points in time. * The {@link PersistenceService} identified by the serviceId is used. * * @param item the item to check for state changes * @param begin the point in time to start the check * @param end the point in time to stop the check * @param serviceId the name of the {@link PersistenceService} to use - * @return true if item state changed or false if either the item has not changed - * in the given interval or if the given serviceId does not refer to a + * @return true if item state changed or false if either the item does not change + * in the given interval, null if the given serviceId does not refer to a * {@link QueryablePersistenceService} */ - public static boolean changedBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - return internalChanged(item, begin, end, serviceId); + public static @Nullable Boolean changedBetween(Item item, ZonedDateTime begin, ZonedDateTime end, + String serviceId) { + return internalChangedBetween(item, begin, end, serviceId); } - private static boolean internalChanged(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, - String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); - Iterator it = result.iterator(); - HistoricItem itemThen = historicState(item, begin, serviceId); - if (itemThen == null) { - // Can't get the state at the start time - // If we've got results more recent than this, it must have changed - return it.hasNext(); - } + private static @Nullable Boolean internalChangedBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalChangedBetween(item, begin, end, serviceId) : null; + } - State state = itemThen.getState(); - while (it.hasNext()) { - HistoricItem hItem = it.next(); - if (!hItem.getState().equals(state)) { - return true; + private static @Nullable Boolean internalChangedBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + Iterable result = internalGetAllStatesBetween(item, begin, end, serviceId); + if (result != null) { + Iterator it = result.iterator(); + HistoricItem itemThen = internalPersistedState(item, begin, serviceId); + if (itemThen == null) { + // Can't get the state at the start time + // If we've got results more recent than this, it must have changed + return it.hasNext(); + } + + State state = itemThen.getState(); + while (it.hasNext()) { + HistoricItem hItem = it.next(); + if (!hItem.getState().equals(state)) { + return true; + } + state = hItem.getState(); } - state = hItem.getState(); + return false; } - return false; + return null; } /** @@ -327,12 +654,27 @@ private static boolean internalChanged(Item item, ZonedDateTime begin, @Nullable * @param item the item to check for state updates * @param timestamp the point in time to start the check * @return true if item state was updated, false if either item has not been updated since - * timestamp or if the default persistence does not refer to a + * timestamp, null if the default persistence does not refer to a + * {@link QueryablePersistenceService}, or null if the default persistence service is not + * available + */ + public static @Nullable Boolean updatedSince(Item item, ZonedDateTime timestamp) { + return internalUpdatedBetween(item, timestamp, null); + } + + /** + * Checks if the state of a given item will be updated till a certain point in time. + * The default persistence service is used. + * + * @param item the item to check for state updates + * @param timestamp the point in time to end the check + * @return true if item state is updated, false if either item is not updated till + * timestamp, null if the default persistence does not refer to a * {@link QueryablePersistenceService}, or null if the default persistence service is not * available */ - public static boolean updatedSince(Item item, ZonedDateTime timestamp) { - return updatedSince(item, timestamp, getDefaultServiceId()); + public static @Nullable Boolean updatedTill(Item item, ZonedDateTime timestamp) { + return internalUpdatedBetween(item, null, timestamp); } /** @@ -343,12 +685,12 @@ public static boolean updatedSince(Item item, ZonedDateTime timestamp) { * @param begin the point in time to start the check * @param end the point in time to stop the check * @return true if item state was updated, false if either item has not been updated in - * the given interval or if the default persistence does not refer to a + * the given interval, null if the default persistence does not refer to a * {@link QueryablePersistenceService}, or null if the default persistence service is not * available */ - public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return updatedBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable Boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalUpdatedBetween(item, begin, end); } /** @@ -359,29 +701,58 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * @param timestamp the point in time to start the check * @param serviceId the name of the {@link PersistenceService} to use * @return true if item state was updated or false if either the item has not been updated - * since timestamp or if the given serviceId does not refer to a + * since timestamp, null if the given serviceId does not refer to a * {@link QueryablePersistenceService} */ - public static boolean updatedSince(Item item, ZonedDateTime timestamp, String serviceId) { - Iterable result = getAllStatesBetween(item, timestamp, null, serviceId); - return result.iterator().hasNext(); + public static @Nullable Boolean updatedSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalUpdatedBetween(item, timestamp, null, serviceId); } /** - * Checks if the state of a given item has been updated between two points in time. + * Checks if the state of a given item will be updated till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to check for state changes + * @param timestamp the point in time to end the check + * @param serviceId the name of the {@link PersistenceService} to use + * @return true if item state was updated or false if either the item is not updated + * since timestamp, null if the given serviceId does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable Boolean updatedTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalUpdatedBetween(item, null, timestamp, serviceId); + } + + /** + * Checks if the state of a given item is updated between two points in time. * The {@link PersistenceService} identified by the serviceId is used. * * @param item the item to check for state changes * @param begin the point in time to start the check * @param end the point in time to stop the check * @param serviceId the name of the {@link PersistenceService} to use - * @return true if item state was updated or false if either the item has not been updated - * in the given interval or if the given serviceId does not refer to a + * @return true if item state was updated or false if either the item is not updated + * in the given interval, null if the given serviceId does not refer to a * {@link QueryablePersistenceService} */ - public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); - return result.iterator().hasNext(); + public static @Nullable Boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTime end, + String serviceId) { + return internalUpdatedBetween(item, begin, end, serviceId); + } + + private static @Nullable Boolean internalUpdatedBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalUpdatedBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable Boolean internalUpdatedBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + Iterable result = internalGetAllStatesBetween(item, begin, end, serviceId); + if (result != null) { + return result.iterator().hasNext(); + } + return null; } /** @@ -395,22 +766,36 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * {@link QueryablePersistenceService} */ public static @Nullable HistoricItem maximumSince(Item item, ZonedDateTime timestamp) { - return maximumSince(item, timestamp, getDefaultServiceId()); + return internalMaximumBetween(item, timestamp, null); } /** - * Gets the historic item with the maximum value of the state of a given item since + * Gets the historic item with the maximum value of the state of a given item till * a certain point in time. The default persistence service is used. * * @param item the item to get the maximum state value for + * @param timestamp the point in time to end the check + * @return a historic item with the maximum state value till the given point in time, or a {@link HistoricItem} + * constructed from the item if the default persistence service does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem maximumTill(Item item, ZonedDateTime timestamp) { + return internalMaximumBetween(item, null, timestamp); + } + + /** + * Gets the historic item with the maximum value of the state of a given item between two points in + * time. The default persistence service is used. + * + * @param item the item to get the maximum state value for * @param begin the point in time to start the check * @param end the point in time to stop the check - * @return a {@link HistoricItem} with the maximum state value since the given point in time, or null + * @return a {@link HistoricItem} with the maximum state value between two points in time, or null * if no states found or if the default persistence service does not refer to an available * {@link QueryablePersistenceService} */ public static @Nullable HistoricItem maximumBetween(final Item item, ZonedDateTime begin, ZonedDateTime end) { - return internalMaximum(item, begin, end, getDefaultServiceId()); + return internalMaximumBetween(item, begin, end); } /** @@ -426,33 +811,58 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * {@link QueryablePersistenceService} */ public static @Nullable HistoricItem maximumSince(final Item item, ZonedDateTime timestamp, String serviceId) { - return internalMaximum(item, timestamp, null, serviceId); + return internalMaximumBetween(item, timestamp, null, serviceId); } /** - * Gets the historic item with the maximum value of the state of a given item since + * Gets the historic item with the maximum value of the state of a given item till * a certain point in time. The {@link PersistenceService} identified by the serviceId is used. * * @param item the item to get the maximum state value for + * @param timestamp the point in time to end the check + * @param serviceId the name of the {@link PersistenceService} to use + * @return a {@link HistoricItem} with the maximum state value till the given point in time, or a + * {@link HistoricItem} constructed from the item's state if item's state is the + * maximum value or if the given serviceId does not refer to an available + * {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem maximumTill(final Item item, ZonedDateTime timestamp, String serviceId) { + return internalMaximumBetween(item, null, timestamp, serviceId); + } + + /** + * Gets the historic item with the maximum value of the state of a given item between two points in + * time. The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to get the maximum state value for * @param begin the point in time to start the check * @param end the point in time to stop the check * @param serviceId the name of the {@link PersistenceService} to use - * @return a {@link HistoricItem} with the maximum state value since the given point in time, or + * @return a {@link HistoricItem} with the maximum state value between two points in time, or * null no states found or if the given serviceId does not refer to an * available {@link QueryablePersistenceService} */ public static @Nullable HistoricItem maximumBetween(final Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - return internalMaximum(item, begin, end, serviceId); + return internalMaximumBetween(item, begin, end, serviceId); + } + + private static @Nullable HistoricItem internalMaximumBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalMaximumBetween(item, begin, end, serviceId) : null; } - private static @Nullable HistoricItem internalMaximum(final Item item, ZonedDateTime begin, + private static @Nullable HistoricItem internalMaximumBetween(final Item item, @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); + if (result == null) { + return null; + } Iterator it = result.iterator(); HistoricItem maximumHistoricItem = null; - // include current state only if no end time is given - DecimalType maximum = end == null ? item.getStateAs(DecimalType.class) : null; + + DecimalType maximum = null; while (it.hasNext()) { HistoricItem historicItem = it.next(); DecimalType value = historicItem.getState().as(DecimalType.class); @@ -463,7 +873,7 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi } } } - return historicItemOrCurrentState(item, maximumHistoricItem, maximum); + return historicItemOrCurrentState(item, maximumHistoricItem); } /** @@ -477,22 +887,36 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * the default persistence service does not refer to an available {@link QueryablePersistenceService} */ public static @Nullable HistoricItem minimumSince(Item item, ZonedDateTime timestamp) { - return minimumSince(item, timestamp, getDefaultServiceId()); + return internalMinimumBetween(item, timestamp, null); } /** - * Gets the historic item with the minimum value of the state of a given item between - * two certain points in time. The default persistence service is used. + * Gets the historic item with the minimum value of the state of a given item till + * a certain point in time. The default persistence service is used. * * @param item the item to get the minimum state value for - * @param begin the beginning point in time - * @param end the end point in time to + * @param timestamp the point in time to which to search for the minimum state value + * @return the historic item with the minimum state value till the given point in time or a {@link HistoricItem} + * constructed from the item's state if item's state is the minimum value or if + * the default persistence service does not refer to an available {@link QueryablePersistenceService} + */ + public static @Nullable HistoricItem minimumTill(Item item, ZonedDateTime timestamp) { + return internalMinimumBetween(item, null, timestamp); + } + + /** + * Gets the historic item with the minimum value of the state of a given item between + * two certain points in time. The default persistence service is used. + * + * @param item the item to get the minimum state value for + * @param begin the beginning point in time + * @param end the ending point in time to * @return the historic item with the minimum state value between the given points in time, or null if * not state was found or if * the default persistence service does not refer to an available {@link QueryablePersistenceService} */ public static @Nullable HistoricItem minimumBetween(final Item item, ZonedDateTime begin, ZonedDateTime end) { - return internalMinimum(item, begin, end, getDefaultServiceId()); + return internalMinimumBetween(item, begin, end); } /** @@ -507,7 +931,22 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * the given serviceId does not refer to an available {@link QueryablePersistenceService}. */ public static @Nullable HistoricItem minimumSince(final Item item, ZonedDateTime timestamp, String serviceId) { - return internalMinimum(item, timestamp, null, serviceId); + return internalMinimumBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the historic item with the minimum value of the state of a given item till + * a certain point in time. The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to get the minimum state value for + * @param timestamp the point in time to which to search for the minimum state value + * @param serviceId the name of the {@link PersistenceService} to use + * @return the historic item with the minimum state value till the given point in time, or a {@link HistoricItem} + * constructed from the item's state if item's state is the minimum value or if + * the given serviceId does not refer to an available {@link QueryablePersistenceService}. + */ + public static @Nullable HistoricItem minimumTill(final Item item, ZonedDateTime timestamp, String serviceId) { + return internalMinimumBetween(item, null, timestamp, serviceId); } /** @@ -524,15 +963,25 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi */ public static @Nullable HistoricItem minimumBetween(final Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - return internalMinimum(item, begin, end, serviceId); + return internalMinimumBetween(item, begin, end, serviceId); + } + + private static @Nullable HistoricItem internalMinimumBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalMinimumBetween(item, begin, end, serviceId) : null; } - private static @Nullable HistoricItem internalMinimum(final Item item, ZonedDateTime begin, + private static @Nullable HistoricItem internalMinimumBetween(final Item item, @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); + if (result == null) { + return null; + } Iterator it = result.iterator(); HistoricItem minimumHistoricItem = null; - DecimalType minimum = end == null ? item.getStateAs(DecimalType.class) : null; + + DecimalType minimum = null; while (it.hasNext()) { HistoricItem historicItem = it.next(); DecimalType value = historicItem.getState().as(DecimalType.class); @@ -543,7 +992,7 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi } } } - return historicItemOrCurrentState(item, minimumHistoricItem, minimum); + return historicItemOrCurrentState(item, minimumHistoricItem); } /** @@ -552,12 +1001,26 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * * @param item the {@link Item} to get the variance for * @param timestamp the point in time from which to compute the variance + * @return the variance between then and now, or null if there is no default persistence service + * available, or it is not a {@link QueryablePersistenceService}, or if there is no persisted state for the + * given item at the given timestamp + */ + public static @Nullable State varianceSince(Item item, ZonedDateTime timestamp) { + return internalVarianceBetween(item, timestamp, null); + } + + /** + * Gets the variance of the state of the given {@link Item} till a certain point in time. + * The default {@link PersistenceService} is used. + * + * @param item the {@link Item} to get the variance for + * @param timestamp the point in time to which to compute the variance * @return the variance between now and then, or null if there is no default persistence service * available, or it is not a {@link QueryablePersistenceService}, or if there is no persisted state for the * given item at the given timestamp */ - public static @Nullable DecimalType varianceSince(Item item, ZonedDateTime timestamp) { - return varianceSince(item, timestamp, getDefaultServiceId()); + public static @Nullable State varianceTill(Item item, ZonedDateTime timestamp) { + return internalVarianceBetween(item, null, timestamp); } /** @@ -571,8 +1034,8 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * available, or it is not a {@link QueryablePersistenceService}, or if there is no persisted state for the * given item at the given timestamp */ - public static @Nullable DecimalType varianceBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return varianceBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable State varianceBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalVarianceBetween(item, begin, end); } /** @@ -582,12 +1045,27 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * @param item the {@link Item} to get the variance for * @param timestamp the point in time from which to compute the variance * @param serviceId the name of the {@link PersistenceService} to use + * @return the variance between then and now, or null if the persistence service given by + * serviceId is not available, or it is not a {@link QueryablePersistenceService}, or if there + * is no persisted state for the given item at the given timestamp + */ + public static @Nullable State varianceSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalVarianceBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the variance of the state of the given {@link Item} till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the {@link Item} to get the variance for + * @param timestamp the point in time to which to compute the variance + * @param serviceId the name of the {@link PersistenceService} to use * @return the variance between now and then, or null if the persistence service given by * serviceId is not available, or it is not a {@link QueryablePersistenceService}, or if there * is no persisted state for the given item at the given timestamp */ - public static @Nullable DecimalType varianceSince(Item item, ZonedDateTime timestamp, String serviceId) { - return internalVariance(item, timestamp, null, serviceId); + public static @Nullable State varianceTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalVarianceBetween(item, null, timestamp, serviceId); } /** @@ -602,22 +1080,30 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * serviceId is not available, or it is not a {@link QueryablePersistenceService}, or if there * is no persisted state for the given item at the given timestamp */ - public static @Nullable DecimalType varianceBetween(Item item, ZonedDateTime begin, ZonedDateTime end, - String serviceId) { - return internalVariance(item, begin, end, serviceId); + public static @Nullable State varianceBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { + return internalVarianceBetween(item, begin, end, serviceId); } - private static @Nullable DecimalType internalVariance(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, - String serviceId) { + private static @Nullable State internalVarianceBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalVarianceBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable State internalVarianceBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); - Iterator it = result.iterator(); - DecimalType averageSince = internalAverage(item, it, end); + if (result == null) { + return null; + } + State averageState = internalAverageBetween(item, begin, end, serviceId); - if (averageSince != null) { - BigDecimal average = averageSince.toBigDecimal(), sum = BigDecimal.ZERO; + if (averageState != null) { + DecimalType dt = averageState.as(DecimalType.class); + BigDecimal average = dt != null ? dt.toBigDecimal() : BigDecimal.ZERO, sum = BigDecimal.ZERO; int count = 0; - it = result.iterator(); + Iterator it = result.iterator(); while (it.hasNext()) { HistoricItem historicItem = it.next(); DecimalType value = historicItem.getState().as(DecimalType.class); @@ -630,7 +1116,14 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi // avoid division by zero if (count > 0) { - return new DecimalType(sum.divide(BigDecimal.valueOf(count), MathContext.DECIMAL64)); + BigDecimal variance = sum.divide(BigDecimal.valueOf(count), MathContext.DECIMAL64); + if (item instanceof NumberItem numberItem) { + Unit unit = numberItem.getUnit(); + if (unit != null) { + return new QuantityType<>(variance, unit.multiply(unit)); + } + } + return new DecimalType(variance); } } return null; @@ -645,12 +1138,29 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * * @param item the {@link Item} to get the standard deviation for * @param timestamp the point in time from which to compute the standard deviation + * @return the standard deviation between then and now, or null if there is no default persistence + * service available, or it is not a {@link QueryablePersistenceService}, or if there is no persisted state + * for the given item at the given timestamp + */ + public static @Nullable State deviationSince(Item item, ZonedDateTime timestamp) { + return internalDeviationBetween(item, timestamp, null); + } + + /** + * Gets the standard deviation of the state of the given {@link Item} till a certain point in time. + * The default {@link PersistenceService} is used. + * + * Note: If you need variance and standard deviation at the same time do not query both as it is a costly + * operation. Get the variance only, it is the squared deviation. + * + * @param item the {@link Item} to get the standard deviation for + * @param timestamp the point in time to which to compute the standard deviation * @return the standard deviation between now and then, or null if there is no default persistence * service available, or it is not a {@link QueryablePersistenceService}, or if there is no persisted state * for the given item at the given timestamp */ - public static @Nullable DecimalType deviationSince(Item item, ZonedDateTime timestamp) { - return deviationSince(item, timestamp, getDefaultServiceId()); + public static @Nullable State deviationTill(Item item, ZonedDateTime timestamp) { + return internalDeviationBetween(item, timestamp, null); } /** @@ -667,8 +1177,8 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * service available, or it is not a {@link QueryablePersistenceService}, or if there is no persisted state * for the given item in the given interval */ - public static @Nullable DecimalType deviationBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return deviationBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable State deviationBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalDeviationBetween(item, begin, end); } /** @@ -685,8 +1195,26 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * serviceId it is not available or is not a {@link QueryablePersistenceService}, or if there * is no persisted state for the given item at the given timestamp */ - public static @Nullable DecimalType deviationSince(Item item, ZonedDateTime timestamp, String serviceId) { - return internalDeviation(item, timestamp, null, serviceId); + public static @Nullable State deviationSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalDeviationBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the standard deviation of the state of the given {@link Item} till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * Note: If you need variance and standard deviation at the same time do not query both as it is a costly + * operation. Get the variance only, it is the squared deviation. + * + * @param item the {@link Item} to get the standard deviation for + * @param timestamp the point in time to which to compute the standard deviation + * @param serviceId the name of the {@link PersistenceService} to use + * @return the standard deviation between now and then, or null if the persistence service given by + * serviceId it is not available or is not a {@link QueryablePersistenceService}, or if there + * is no persisted state for the given item at the given timestamp + */ + public static @Nullable State deviationTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalDeviationBetween(item, null, timestamp, serviceId); } /** @@ -704,21 +1232,33 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * serviceId it is not available or is not a {@link QueryablePersistenceService}, or if there * is no persisted state for the given item in the given interval */ - public static @Nullable DecimalType deviationBetween(Item item, ZonedDateTime begin, ZonedDateTime end, + public static @Nullable State deviationBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - return internalDeviation(item, begin, end, serviceId); + return internalDeviationBetween(item, begin, end, serviceId); } - private static @Nullable DecimalType internalDeviation(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, - String serviceId) { - DecimalType variance = internalVariance(item, begin, end, serviceId); + private static @Nullable State internalDeviationBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalDeviationBetween(item, begin, end, serviceId) : null; + } - if (variance != null) { - BigDecimal bd = variance.toBigDecimal(); + private static @Nullable State internalDeviationBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + State variance = internalVarianceBetween(item, begin, end, serviceId); + if (variance != null) { + DecimalType dt = variance.as(DecimalType.class); // avoid ArithmeticException if variance is less than zero - if (BigDecimal.ZERO.compareTo(bd) <= 0) { - return new DecimalType(bd.sqrt(MathContext.DECIMAL64)); + if (dt != null && DecimalType.ZERO.compareTo(dt) <= 0) { + BigDecimal deviation = dt.toBigDecimal().sqrt(MathContext.DECIMAL64); + if (item instanceof NumberItem numberItem) { + Unit unit = numberItem.getUnit(); + if (unit != null) { + return new QuantityType<>(deviation, unit); + } + } + return new DecimalType(deviation); } } return null; @@ -734,8 +1274,22 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * previous states could be found or if the default persistence service does not refer to an available * {@link QueryablePersistenceService}. The current state is included in the calculation. */ - public static @Nullable DecimalType averageSince(Item item, ZonedDateTime timestamp) { - return averageSince(item, timestamp, getDefaultServiceId()); + public static @Nullable State averageSince(Item item, ZonedDateTime timestamp) { + return internalAverageBetween(item, timestamp, null); + } + + /** + * Gets the average value of the state of a given {@link Item} till a certain point in time. + * The default {@link PersistenceService} is used. + * + * @param item the {@link Item} to get the average value for + * @param timestamp the point in time to which to search for the average value + * @return the average value to timestamp or null if no + * previous states could be found or if the default persistence service does not refer to an available + * {@link QueryablePersistenceService}. The current state is included in the calculation. + */ + public static @Nullable State averageTill(Item item, ZonedDateTime timestamp) { + return internalAverageBetween(item, null, timestamp); } /** @@ -749,8 +1303,8 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * previous states could be found or if the default persistence service does not refer to an available * {@link QueryablePersistenceService}. */ - public static @Nullable DecimalType averageBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return averageBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable State averageBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalAverageBetween(item, begin, end); } /** @@ -765,8 +1319,24 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * refer to an available {@link QueryablePersistenceService}. The current state is included in the * calculation. */ - public static @Nullable DecimalType averageSince(Item item, ZonedDateTime timestamp, String serviceId) { - return averageBetween(item, timestamp, null, serviceId); + public static @Nullable State averageSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalAverageBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the average value of the state of a given {@link Item} till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the {@link Item} to get the average value for + * @param timestamp the point in time to which to search for the average value + * @param serviceId the name of the {@link PersistenceService} to use + * @return the average value to timestamp, or null if no + * previous states could be found or if the persistence service given by serviceId does not + * refer to an available {@link QueryablePersistenceService}. The current state is included in the + * calculation. + */ + public static @Nullable State averageTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalAverageBetween(item, null, timestamp, serviceId); } /** @@ -781,17 +1351,30 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * previous states could be found or if the persistence service given by serviceId does not * refer to an available {@link QueryablePersistenceService} */ - public static @Nullable DecimalType averageBetween(Item item, ZonedDateTime begin, ZonedDateTime end, - String serviceId) { + public static @Nullable State averageBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { + return internalAverageBetween(item, begin, end, serviceId); + } + + private static @Nullable State internalAverageBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalAverageBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable State internalAverageBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { Iterable result = getAllStatesBetweenWithBoundaries(item, begin, end, serviceId); + if (result == null) { + return null; + } Iterator it = result.iterator(); - return internalAverage(item, it, end); - } + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime beginTime = begin == null ? now : begin; + ZonedDateTime endTime = end == null ? now : end; - @SuppressWarnings("null") - private static @Nullable DecimalType internalAverage(Item item, Iterator it, ZonedDateTime endTime) { - if (endTime == null) { - endTime = ZonedDateTime.now(); + if (beginTime.isEqual(endTime)) { + HistoricItem historicItem = internalPersistedState(item, beginTime, serviceId); + return historicItem != null ? historicItem.getState() : null; } BigDecimal sum = BigDecimal.ZERO; @@ -799,13 +1382,23 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi HistoricItem lastItem = null; ZonedDateTime firstTimestamp = null; + // if (beginTime.equals(now)) { + // HistoricItem historicItem = internalPersistedState(item, now, serviceId); + // if (historicItem != null) { + // lastItem = new RetimedHistoricItem(historicItem, now); + // firstTimestamp = now; + // } + // } while (it.hasNext()) { HistoricItem thisItem = it.next(); - if (lastItem != null) { - BigDecimal value = lastItem.getState().as(DecimalType.class).toBigDecimal(); - BigDecimal weight = BigDecimal - .valueOf(Duration.between(lastItem.getTimestamp(), thisItem.getTimestamp()).toMillis()); - sum = sum.add(value.multiply(weight)); + if (lastItem != null && lastItem.getState() instanceof State state) { + DecimalType dtState = state.as(DecimalType.class); + if (dtState != null) { + BigDecimal value = dtState.toBigDecimal(); + BigDecimal weight = BigDecimal + .valueOf(Duration.between(lastItem.getTimestamp(), thisItem.getTimestamp()).toMillis()); + sum = sum.add(value.multiply(weight)); + } } if (firstTimestamp == null) { @@ -813,17 +1406,28 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi } lastItem = thisItem; } - - if (lastItem != null) { - BigDecimal value = lastItem.getState().as(DecimalType.class).toBigDecimal(); - BigDecimal weight = BigDecimal.valueOf(Duration.between(lastItem.getTimestamp(), endTime).toMillis()); - sum = sum.add(value.multiply(weight)); - } + // if (endTime.equals(now) && lastItem != null && lastItem.getState() instanceof State state) { + // DecimalType dtState = state.as(DecimalType.class); + // if (dtState != null) { + // BigDecimal value = dtState.toBigDecimal(); + // BigDecimal weight = BigDecimal.valueOf(Duration.between(lastItem.getTimestamp(), now).toMillis()); + // sum = sum.add(value.multiply(weight)); + // } + // } if (firstTimestamp != null) { BigDecimal totalDuration = BigDecimal.valueOf(Duration.between(firstTimestamp, endTime).toMillis()); - return totalDuration.signum() == 0 ? null - : new DecimalType(sum.divide(totalDuration, MathContext.DECIMAL64)); + if (totalDuration.signum() == 0) { + return null; + } + BigDecimal average = sum.divide(totalDuration, MathContext.DECIMAL64); + if (item instanceof NumberItem numberItem) { + Unit unit = numberItem.getUnit(); + if (unit != null) { + return new QuantityType<>(average, unit); + } + } + return new DecimalType(average); } return null; @@ -839,8 +1443,22 @@ public static boolean updatedBetween(Item item, ZonedDateTime begin, ZonedDateTi * states could be found or if the default persistence service does not refer to a * {@link QueryablePersistenceService} */ - public static DecimalType sumSince(Item item, ZonedDateTime timestamp) { - return sumSince(item, timestamp, getDefaultServiceId()); + public @Nullable static State sumSince(Item item, ZonedDateTime timestamp) { + return internalSumBetween(item, timestamp, null); + } + + /** + * Gets the sum of the state of a given item till a certain point in time. + * The default persistence service is used. + * + * @param item the item for which we will sum its persisted state values to timestamp + * @param timestamp the point in time to which to start the summation + * @return the sum of the state values to timestamp, or {@link DecimalType#ZERO} if no historic + * states could be found or if the default persistence service does not refer to a + * {@link QueryablePersistenceService} + */ + public @Nullable static State sumTill(Item item, ZonedDateTime timestamp) { + return internalSumBetween(item, null, timestamp); } /** @@ -855,8 +1473,8 @@ public static DecimalType sumSince(Item item, ZonedDateTime timestamp) { * states could be found or if the default persistence service does not refer to a * {@link QueryablePersistenceService} */ - public static DecimalType sumBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return sumBetween(item, begin, end, getDefaultServiceId()); + public @Nullable static State sumBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalSumBetween(item, begin, end); } /** @@ -870,8 +1488,23 @@ public static DecimalType sumBetween(Item item, ZonedDateTime begin, ZonedDateTi * states could be found for the item or if serviceId does not refer to a * {@link QueryablePersistenceService} */ - public static DecimalType sumSince(Item item, ZonedDateTime timestamp, String serviceId) { - return internalSum(item, timestamp, null, serviceId); + public @Nullable static State sumSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalSumBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the sum of the state of a given item till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item for which we will sum its persisted state values to timestamp + * @param timestamp the point in time to which to start the summation + * @param serviceId the name of the {@link PersistenceService} to use + * @return the sum of the state values to the given point in time, or {@link DecimalType#ZERO} if no historic + * states could be found for the item or if serviceId does not refer to a + * {@link QueryablePersistenceService} + */ + public @Nullable static State sumTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalSumBetween(item, null, timestamp, serviceId); } /** @@ -887,24 +1520,39 @@ public static DecimalType sumSince(Item item, ZonedDateTime timestamp, String se * states could be found for the item or if serviceId does not refer to a * {@link QueryablePersistenceService} */ - public static DecimalType sumBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - return internalSum(item, begin, end, serviceId); + public @Nullable static State sumBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { + return internalSumBetween(item, begin, end, serviceId); } - private static DecimalType internalSum(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, - String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); - Iterator it = result.iterator(); + private static @Nullable State internalSumBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalSumBetween(item, begin, end, serviceId) : null; + } - BigDecimal sum = BigDecimal.ZERO; - while (it.hasNext()) { - HistoricItem historicItem = it.next(); - DecimalType value = historicItem.getState().as(DecimalType.class); - if (value != null) { - sum = sum.add(value.toBigDecimal()); + private @Nullable static State internalSumBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + Iterable result = internalGetAllStatesBetween(item, begin, end, serviceId); + if (result != null) { + Iterator it = result.iterator(); + + BigDecimal sum = BigDecimal.ZERO; + while (it.hasNext()) { + HistoricItem historicItem = it.next(); + DecimalType value = historicItem.getState().as(DecimalType.class); + if (value != null) { + sum = sum.add(value.toBigDecimal()); + } } + if (item instanceof NumberItem numberItem) { + Unit unit = numberItem.getUnit(); + if (unit != null) { + return new QuantityType<>(sum, unit); + } + } + return new DecimalType(sum); } - return new DecimalType(sum); + return null; } /** @@ -918,8 +1566,23 @@ private static DecimalType internalSum(Item item, ZonedDateTime begin, @Nullable * there is no persisted state for the given item at the given timestamp available * in the default persistence service */ - public static @Nullable DecimalType deltaSince(Item item, ZonedDateTime timestamp) { - return deltaSince(item, timestamp, getDefaultServiceId()); + public static @Nullable State deltaSince(Item item, ZonedDateTime timestamp) { + return internalDeltaBetween(item, timestamp, null); + } + + /** + * Gets the difference value of the state of a given item till a certain point in time. + * The default persistence service is used. + * + * @param item the item to get the average state value for + * @param timestamp the point in time to which to compute the delta + * @return the difference between then and now, or null if there is no default persistence + * service available, the default persistence service is not a {@link QueryablePersistenceService}, or if + * there is no persisted state for the given item at the given timestamp available + * in the default persistence service + */ + public static @Nullable State deltaTill(Item item, ZonedDateTime timestamp) { + return internalDeltaBetween(item, null, timestamp); } /** @@ -933,8 +1596,8 @@ private static DecimalType internalSum(Item item, ZonedDateTime begin, @Nullable * refer to an available {@link QueryablePersistenceService}, or if there is no persisted state for the * given item for the given points in time */ - public static @Nullable DecimalType deltaBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { - return deltaBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable State deltaBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalDeltaBetween(item, begin, end); } /** @@ -949,16 +1612,24 @@ private static DecimalType internalSum(Item item, ZonedDateTime begin, @Nullable * item at the given timestamp using the persistence service named * serviceId */ - public static @Nullable DecimalType deltaSince(Item item, ZonedDateTime timestamp, String serviceId) { - HistoricItem itemThen = historicState(item, timestamp, serviceId); - if (itemThen != null) { - DecimalType valueThen = itemThen.getState().as(DecimalType.class); - DecimalType valueNow = item.getStateAs(DecimalType.class); - if (valueThen != null && valueNow != null) { - return new DecimalType(valueNow.toBigDecimal().subtract(valueThen.toBigDecimal())); - } - } - return null; + public static @Nullable State deltaSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalDeltaBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the difference value of the state of a given item till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the item to get the delta for + * @param timestamp the point in time to which to compute the delta + * @param serviceId the name of the {@link PersistenceService} to use + * @return the difference between then and now, or null if the given serviceId does not refer to an + * available {@link QueryablePersistenceService}, or if there is no persisted state for the given + * item at the given timestamp using the persistence service named + * serviceId + */ + public static @Nullable State deltaTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalDeltaBetween(item, null, timestamp, serviceId); } /** @@ -973,15 +1644,34 @@ private static DecimalType internalSum(Item item, ZonedDateTime begin, @Nullable * available {@link QueryablePersistenceService}, or if there is no persisted state for the given * item at the given points in time */ - public static @Nullable DecimalType deltaBetween(Item item, ZonedDateTime begin, ZonedDateTime end, - String serviceId) { - HistoricItem itemStart = historicState(item, begin, serviceId); - HistoricItem itemStop = historicState(item, end, serviceId); - if (itemStart != null && itemStop != null) { - DecimalType valueStart = itemStart.getState().as(DecimalType.class); - DecimalType valueStop = itemStop.getState().as(DecimalType.class); - if (valueStart != null && valueStop != null) { - return new DecimalType(valueStop.toBigDecimal().subtract(valueStart.toBigDecimal())); + public static @Nullable State deltaBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { + return internalDeltaBetween(item, begin, end, serviceId); + } + + private static @Nullable State internalDeltaBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalDeltaBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable State internalDeltaBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + HistoricItem itemStart = internalPersistedState(item, begin, serviceId); + HistoricItem itemStop = internalPersistedState(item, end, serviceId); + DecimalType valueStart = itemStart != null ? itemStart.getState().as(DecimalType.class) : null; + DecimalType valueStop = itemStop != null ? itemStop.getState().as(DecimalType.class) : null; + if (begin == null && end != null && end.isAfter(ZonedDateTime.now())) { + valueStart = getItemValue(item); + } + if (begin != null && end == null && begin.isBefore(ZonedDateTime.now())) { + valueStop = getItemValue(item); + } + + if (valueStart != null && valueStop != null) { + BigDecimal delta = valueStop.toBigDecimal().subtract(valueStart.toBigDecimal()); + if (item instanceof NumberItem numberItem) { + Unit unit = numberItem.getUnit(); + return (unit != null) ? new QuantityType<>(delta, unit) : new DecimalType(delta); } } return null; @@ -999,8 +1689,24 @@ private static DecimalType internalSum(Item item, ZonedDateTime begin, @Nullable * the given timestamp, or if there is a state but it is zero (which would cause a * divide-by-zero error) */ - public static DecimalType evolutionRate(Item item, ZonedDateTime timestamp) { - return evolutionRate(item, timestamp, getDefaultServiceId()); + public static @Nullable DecimalType evolutionRateSince(Item item, ZonedDateTime timestamp) { + return internalEvolutionRateBetween(item, timestamp, null); + } + + /** + * Gets the evolution rate of the state of a given {@link Item} till a certain point in time. + * The default {@link PersistenceService} is used. + * + * @param item the item to get the evolution rate value for + * @param timestamp the point in time to which to compute the evolution rate + * @return the evolution rate in percent (positive and negative) between then and now, or null if + * there is no default persistence service available, the default persistence service is not a + * {@link QueryablePersistenceService}, or if there is no persisted state for the given item at + * the given timestamp, or if there is a state but it is zero (which would cause a + * divide-by-zero error) + */ + public static @Nullable DecimalType evolutionRateTill(Item item, ZonedDateTime timestamp) { + return internalEvolutionRateBetween(item, null, timestamp); } /** @@ -1016,8 +1722,8 @@ public static DecimalType evolutionRate(Item item, ZonedDateTime timestamp) { * at the given interval, or if there is a state but it is zero (which would cause a * divide-by-zero error) */ - public static DecimalType evolutionRate(Item item, ZonedDateTime begin, ZonedDateTime end) { - return evolutionRate(item, begin, end, getDefaultServiceId()); + public static @Nullable DecimalType evolutionRateBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalEvolutionRateBetween(item, begin, end); } /** @@ -1034,18 +1740,26 @@ public static DecimalType evolutionRate(Item item, ZonedDateTime begin, ZonedDat * serviceId, or if there is a state but it is zero (which would cause a divide-by-zero * error) */ - public static @Nullable DecimalType evolutionRate(Item item, ZonedDateTime timestamp, String serviceId) { - HistoricItem itemThen = historicState(item, timestamp, serviceId); - if (itemThen != null) { - DecimalType valueThen = itemThen.getState().as(DecimalType.class); - DecimalType valueNow = item.getStateAs(DecimalType.class); - if (valueThen != null && valueThen.toBigDecimal().compareTo(BigDecimal.ZERO) != 0 && valueNow != null) { - // ((now - then) / then) * 100 - return new DecimalType(valueNow.toBigDecimal().subtract(valueThen.toBigDecimal()) - .divide(valueThen.toBigDecimal(), MathContext.DECIMAL64).movePointRight(2)); - } - } - return null; + public static @Nullable DecimalType evolutionRateSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalEvolutionRateBetween(item, timestamp, null, serviceId); + } + + /** + * Gets the evolution rate of the state of a given {@link Item} till a certain point in time. + * The {@link PersistenceService} identified by the serviceId is used. + * + * @param item the {@link Item} to get the evolution rate value for + * @param timestamp the point in time to which to compute the evolution rate + * @param serviceId the name of the {@link PersistenceService} to use + * @return the evolution rate in percent (positive and negative) between then and now, or null if + * the persistence service given by serviceId is not available or is not a + * {@link QueryablePersistenceService}, or if there is no persisted state for the given + * item at the given timestamp using the persistence service given by + * serviceId, or if there is a state but it is zero (which would cause a divide-by-zero + * error) + */ + public static @Nullable DecimalType evolutionRateTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalEvolutionRateBetween(item, null, timestamp, serviceId); } /** @@ -1063,19 +1777,33 @@ public static DecimalType evolutionRate(Item item, ZonedDateTime begin, ZonedDat * given by serviceId, or if there is a state but it is zero (which would cause a * divide-by-zero error) */ - public static @Nullable DecimalType evolutionRate(Item item, ZonedDateTime begin, ZonedDateTime end, + public static @Nullable DecimalType evolutionRateBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - HistoricItem itemBegin = historicState(item, begin, serviceId); - HistoricItem itemEnd = historicState(item, end, serviceId); - - if (itemBegin != null && itemEnd != null) { - DecimalType valueBegin = itemBegin.getState().as(DecimalType.class); - DecimalType valueEnd = itemEnd.getState().as(DecimalType.class); - if (valueBegin != null && valueBegin.toBigDecimal().compareTo(BigDecimal.ZERO) != 0 && valueEnd != null) { - // ((now - then) / then) * 100 - return new DecimalType(valueEnd.toBigDecimal().subtract(valueBegin.toBigDecimal()) - .divide(valueBegin.toBigDecimal(), MathContext.DECIMAL64).movePointRight(2)); - } + return internalEvolutionRateBetween(item, begin, end, serviceId); + } + + private static @Nullable DecimalType internalEvolutionRateBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalEvolutionRateBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable DecimalType internalEvolutionRateBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + HistoricItem itemStart = internalPersistedState(item, begin, serviceId); + HistoricItem itemStop = internalPersistedState(item, end, serviceId); + DecimalType valueStart = itemStart != null ? itemStart.getState().as(DecimalType.class) : null; + DecimalType valueStop = itemStop != null ? itemStop.getState().as(DecimalType.class) : null; + if (begin == null && end != null && end.isAfter(ZonedDateTime.now())) { + valueStart = getItemValue(item); + } + if (begin != null && end == null && begin.isBefore(ZonedDateTime.now())) { + valueStop = getItemValue(item); + } + + if (valueStart != null && valueStop != null) { + return new DecimalType(valueStop.toBigDecimal().subtract(valueStart.toBigDecimal()) + .divide(valueStart.toBigDecimal(), MathContext.DECIMAL64).movePointRight(2)); } return null; } @@ -1085,11 +1813,42 @@ public static DecimalType evolutionRate(Item item, ZonedDateTime begin, ZonedDat * The default {@link PersistenceService} is used. * * @param item the {@link Item} to query + * @param timestamp the beginning point in time + * @return the number of values persisted for this item, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable Long countSince(Item item, ZonedDateTime timestamp) { + return internalCountBetween(item, timestamp, null); + } + + /** + * Gets the number of available data points of a given {@link Item} from now to a point in time. + * The default {@link PersistenceService} is used. + * + * @param item the {@link Item} to query + * @param timestamp the ending point in time + * @return the number of values persisted for this item, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable Long countTill(Item item, ZonedDateTime timestamp) { + return internalCountBetween(item, null, timestamp); + } + + /** + * Gets the number of available data points of a given {@link Item} between two points in time. + * The default {@link PersistenceService} is used. + * + * @param item the {@link Item} to query * @param begin the beginning point in time - * @return the number of values persisted for this item + * @param end the end point in time + * @return the number of values persisted for this item, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countSince(Item item, ZonedDateTime begin) { - return countSince(item, begin, getDefaultServiceId()); + public static @Nullable Long countBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalCountBetween(item, begin, end); } /** @@ -1099,42 +1858,62 @@ public static long countSince(Item item, ZonedDateTime begin) { * @param item the {@link Item} to query * @param begin the beginning point in time * @param serviceId the name of the {@link PersistenceService} to use - * @return the number of values persisted for this item + * @return the number of values persisted for this item, null + * if the persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countSince(Item item, ZonedDateTime begin, String serviceId) { - return countBetween(item, begin, null, serviceId); + public static @Nullable Long countSince(Item item, ZonedDateTime begin, String serviceId) { + return internalCountBetween(item, begin, null, serviceId); } /** - * Gets the number of available historic data points of a given {@link Item} between two points in time. - * The default {@link PersistenceService} is used. + * Gets the number of available data points of a given {@link Item} from now to a point in time. + * The {@link PersistenceService} identified by the serviceId is used. * * @param item the {@link Item} to query - * @param begin the beginning point in time - * @param end the end point in time - * @return the number of values persisted for this item + * @param timestamp the ending point in time + * @param serviceId the name of the {@link PersistenceService} to use + * @return the number of values persisted for this item, null + * if the persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end) { - return countBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable Long countTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalCountBetween(item, null, timestamp, serviceId); } /** - * Gets the number of available historic data points of a given {@link Item} between two points in time. + * Gets the number of available data points of a given {@link Item} between two points in time. * The {@link PersistenceService} identified by the serviceId is used. * * @param item the {@link Item} to query * @param begin the beginning point in time * @param end the end point in time * @param serviceId the name of the {@link PersistenceService} to use - * @return the number of values persisted for this item + * @return the number of values persisted for this item, null + * if the persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { - Iterable historicItems = getAllStatesBetween(item, begin, end, serviceId); - if (historicItems instanceof Collection collection) { - return collection.size(); - } else { - return StreamSupport.stream(historicItems.spliterator(), false).count(); + public static @Nullable Long countBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { + return internalCountBetween(item, begin, end, serviceId); + } + + private static @Nullable Long internalCountBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalCountBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable Long internalCountBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + Iterable result = internalGetAllStatesBetween(item, begin, end, serviceId); + if (result != null) { + if (result instanceof Collection collection) { + return Long.valueOf(collection.size()); + } else { + return StreamSupport.stream(result.spliterator(), false).count(); + } } + return null; } /** @@ -1142,11 +1921,42 @@ public static long countBetween(Item item, ZonedDateTime begin, @Nullable ZonedD * The default {@link PersistenceService} is used. * * @param item the {@link Item} to query + * @param timestamp the beginning point in time + * @return the number of state changes for this item, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable Long countStateChangesSince(Item item, ZonedDateTime timestamp) { + return internalCountStateChangesBetween(item, timestamp, null); + } + + /** + * Gets the number of changes in data points of a given {@link Item} from now until a point in time. + * The default {@link PersistenceService} is used. + * + * @param item the {@link Item} to query + * @param timestamp the ending point in time + * @return the number of state changes for this item, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} + */ + public static @Nullable Long countStateChangesTill(Item item, ZonedDateTime timestamp) { + return internalCountStateChangesBetween(item, null, timestamp); + } + + /** + * Gets the number of changes in data points of a given {@link Item} between two points in time. + * The default {@link PersistenceService} is used. + * + * @param item the {@link Item} to query * @param begin the beginning point in time - * @return the number of state changes for this item + * @param end the end point in time + * @return the number of state changes for this item, null + * if the default persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countStateChangesSince(Item item, ZonedDateTime begin) { - return countStateChangesSince(item, begin, getDefaultServiceId()); + public static @Nullable Long countStateChangesBetween(Item item, ZonedDateTime begin, ZonedDateTime end) { + return internalCountStateChangesBetween(item, begin, end); } /** @@ -1154,78 +1964,75 @@ public static long countStateChangesSince(Item item, ZonedDateTime begin) { * The {@link PersistenceService} identified by the serviceId is used. * * @param item the {@link Item} to query - * @param begin the beginning point in time + * @param timestamp the beginning point in time * @param serviceId the name of the {@link PersistenceService} to use - * @return the number of state changes for this item + * @return the number of state changes for this item, null + * if the persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countStateChangesSince(Item item, ZonedDateTime begin, String serviceId) { - return countStateChangesBetween(item, begin, null, serviceId); + public static @Nullable Long countStateChangesSince(Item item, ZonedDateTime timestamp, String serviceId) { + return internalCountStateChangesBetween(item, timestamp, null, serviceId); } /** - * Gets the number of changes in historic data points of a given {@link Item} between two points in time. - * The default {@link PersistenceService} is used. + * Gets the number of changes in data points of a given {@link Item} from now until a point in time. + * The {@link PersistenceService} identified by the serviceId is used. * * @param item the {@link Item} to query - * @param begin the beginning point in time - * @param end the end point in time - * @return the number of state changes for this item + * @param timestamp the ending point in time + * @param serviceId the name of the {@link PersistenceService} to use + * @return the number of state changes for this item, null + * if the persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countStateChangesBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end) { - return countStateChangesBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable Long countStateChangesTill(Item item, ZonedDateTime timestamp, String serviceId) { + return internalCountStateChangesBetween(item, null, timestamp, serviceId); } /** - * Gets the number of changes in historic data points of a given {@link Item} between two points in time. + * Gets the number of changes in data points of a given {@link Item} between two points in time. * The {@link PersistenceService} identified by the serviceId is used. * * @param item the {@link Item} to query * @param begin the beginning point in time * @param end the end point in time * @param serviceId the name of the {@link PersistenceService} to use - * @return the number of state changes for this item + * @return the number of state changes for this item, null + * if the persistence service is not available or does not refer to a + * {@link QueryablePersistenceService} */ - public static long countStateChangesBetween(Item item, ZonedDateTime begin, @Nullable ZonedDateTime end, + public static @Nullable Long countStateChangesBetween(Item item, ZonedDateTime begin, ZonedDateTime end, String serviceId) { - Iterable result = getAllStatesBetween(item, begin, end, serviceId); - Iterator it = result.iterator(); - - if (!it.hasNext()) { - return 0; - } - - long count = 0; - State previousState = it.next().getState(); - while (it.hasNext()) { - HistoricItem historicItem = it.next(); - State state = historicItem.getState(); - if (!state.equals(previousState)) { - previousState = state; - count++; - } - } - return count; + return internalCountStateChangesBetween(item, begin, end, serviceId); } - private static @Nullable PersistenceService getService(String serviceId) { - if (registry != null) { - return serviceId != null ? registry.get(serviceId) : registry.getDefault(); - } - return null; + private static @Nullable Long internalCountStateChangesBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalCountStateChangesBetween(item, begin, end, serviceId) : null; } - private static @Nullable String getDefaultServiceId() { - if (registry != null) { - String id = registry.getDefaultId(); - if (id != null) { - return id; - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("There is no default persistence service configured!"); + private static @Nullable Long internalCountStateChangesBetween(Item item, @Nullable ZonedDateTime begin, + @Nullable ZonedDateTime end, String serviceId) { + Iterable result = internalGetAllStatesBetween(item, begin, end, serviceId); + if (result != null) { + Iterator it = result.iterator(); + + if (!it.hasNext()) { + return Long.valueOf(0); } - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("PersistenceServiceRegistryImpl is not available!"); + + long count = 0; + State previousState = it.next().getState(); + while (it.hasNext()) { + HistoricItem historicItem = it.next(); + State state = historicItem.getState(); + if (!state.equals(previousState)) { + previousState = state; + count++; + } + } + return count; } return null; } @@ -1239,8 +2046,36 @@ public static long countStateChangesBetween(Item item, ZonedDateTime begin, @Nul * @return the historic items since the given point in time, or null if no historic items could be * found. */ - public static Iterable getAllStatesSince(Item item, ZonedDateTime timestamp) { - return getAllStatesBetween(item, timestamp, null); + public static @Nullable Iterable getAllStatesSince(Item item, ZonedDateTime timestamp) { + return internalGetAllStatesBetween(item, timestamp, null); + } + + /** + * Retrieves the future items for a given item till a certain point in time. + * The default persistence service is used. + * + * @param item the item for which to retrieve the future item + * @param timestamp the point in time to which to retrieve the states + * @return the future items to the given point in time, or null if no future items could be + * found. + */ + public static @Nullable Iterable getAllStatesTill(Item item, ZonedDateTime timestamp) { + return internalGetAllStatesBetween(item, null, timestamp); + } + + /** + * Retrieves the historic items for a given item between two certain points in time. + * The default persistence service is used. + * + * @param item the item for which to retrieve the historic item + * @param begin the point in time from which to retrieve the states + * @param end the point in time to which to retrieve the states + * @return the historic items between the given points in time, or null if no historic items could be + * found. + */ + public static @Nullable Iterable getAllStatesBetween(Item item, ZonedDateTime begin, + ZonedDateTime end) { + return internalGetAllStatesBetween(item, begin, end); } /** @@ -1250,27 +2085,29 @@ public static Iterable getAllStatesSince(Item item, ZonedDateTime * @param item the item for which to retrieve the historic item * @param timestamp the point in time from which to retrieve the states * @param serviceId the name of the {@link PersistenceService} to use - * @return the historic items since the given point in time, or null if no historic items could be + * @return the future items to the given point in time, or null if no historic items could be * found or if the provided serviceId does not refer to an available * {@link QueryablePersistenceService} */ - public static Iterable getAllStatesSince(Item item, ZonedDateTime timestamp, String serviceId) { - return getAllStatesBetween(item, timestamp, null, serviceId); + public static @Nullable Iterable getAllStatesSince(Item item, ZonedDateTime timestamp, + String serviceId) { + return internalGetAllStatesBetween(item, timestamp, null, serviceId); } /** - * Retrieves the historic items for a given item beetween two certain points in time. - * The default persistence service is used. + * Retrieves the future items for a given item till a certain point in time + * through a {@link PersistenceService} identified by the serviceId. * - * @param item the item for which to retrieve the historic item - * @param begin the point in time from which to retrieve the states - * @param end the point in time to which to retrieve the states - * @return the historic items between the given points in time, or null if no historic items could be - * found. + * @param item the item for which to retrieve the future item + * @param timestamp the point in time to which to retrieve the states + * @param serviceId the name of the {@link PersistenceService} to use + * @return the historic items since the given point in time, or null if no historic items could be + * found or if the provided serviceId does not refer to an available + * {@link QueryablePersistenceService} */ - public static Iterable getAllStatesBetween(Item item, ZonedDateTime begin, - @Nullable ZonedDateTime end) { - return getAllStatesBetween(item, begin, end, getDefaultServiceId()); + public static @Nullable Iterable getAllStatesTill(Item item, ZonedDateTime timestamp, + String serviceId) { + return internalGetAllStatesBetween(item, null, timestamp, serviceId); } /** @@ -1285,64 +2122,134 @@ public static Iterable getAllStatesBetween(Item item, ZonedDateTim * found or if the provided serviceId does not refer to an available * {@link QueryablePersistenceService} */ - public static Iterable getAllStatesBetween(Item item, ZonedDateTime begin, - @Nullable ZonedDateTime end, String serviceId) { + public static @Nullable Iterable getAllStatesBetween(Item item, ZonedDateTime begin, + ZonedDateTime end, String serviceId) { + return internalGetAllStatesBetween(item, begin, end, serviceId); + } + + private static @Nullable Iterable internalGetAllStatesBetween(Item item, + @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end) { + String serviceId = getDefaultServiceId(); + return serviceId != null ? internalGetAllStatesBetween(item, begin, end, serviceId) : null; + } + + private static @Nullable Iterable internalGetAllStatesBetween(Item item, + @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { PersistenceService service = getService(serviceId); - if (service instanceof QueryablePersistenceService qService) { + if (service != null && service instanceof QueryablePersistenceService qService) { FilterCriteria filter = new FilterCriteria(); - filter.setBeginDate(begin); + ZonedDateTime now = ZonedDateTime.now(); + if ((begin == null && end == null) || (begin != null && end == null && begin.isAfter(now)) + || (begin == null && end != null && end.isBefore(now))) { + LoggerFactory.getLogger(PersistenceExtensions.class).warn( + "Querying persistence service with open begin and/or end not allowed: begin {}, end {}, now {}", + begin, end, now); + return null; + } + if (begin != null) { + filter.setBeginDate(begin); + } else { + filter.setBeginDate(ZonedDateTime.now()); + } if (end != null) { filter.setEndDate(end); + } else { + filter.setEndDate(ZonedDateTime.now()); } filter.setItemName(item.getName()); filter.setOrdering(Ordering.ASCENDING); return qService.query(filter); - } else { - LoggerFactory.getLogger(PersistenceExtensions.class) - .warn("There is no queryable persistence service registered with the id '{}'", serviceId); - return List.of(); } + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no queryable persistence service registered with the id '{}'", serviceId); + return null; } - private static Iterable getAllStatesBetweenWithBoundaries(Item item, ZonedDateTime begin, - @Nullable ZonedDateTime end, String serviceId) { - Iterable betweenItems = getAllStatesBetween(item, begin, end, serviceId); + private static @Nullable Iterable getAllStatesBetweenWithBoundaries(Item item, + @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end, String serviceId) { + Iterable betweenItems = internalGetAllStatesBetween(item, begin, end, serviceId); + + ZonedDateTime now = ZonedDateTime.now(); + if ((begin == null && end == null) || (begin != null && end == null && begin.isAfter(now)) + || (begin == null && end != null && end.isBefore(now)) + || (begin != null && end != null && end.isBefore(begin))) { + return null; + } + + ZonedDateTime beginTime = (begin == null) ? now : begin; + ZonedDateTime endTime = (end == null) ? now : end; List betweenItemsList = new ArrayList<>(); - for (HistoricItem historicItem : betweenItems) { - betweenItemsList.add(historicItem); + if (betweenItems != null) { + for (HistoricItem historicItem : betweenItems) { + betweenItemsList.add(historicItem); + } } // add HistoricItem at begin if (betweenItemsList.isEmpty() || !betweenItemsList.get(0).getTimestamp().equals(begin)) { - if (!begin.isAfter(ZonedDateTime.now())) { - HistoricItem first = historicState(item, begin, serviceId); - - if (first != null) { - betweenItemsList.add(0, new RetimedHistoricItem(first, begin)); - } + HistoricItem first = beginTime.equals(now) ? historicItemOrCurrentState(item, null) + : internalPersistedState(item, beginTime, serviceId); + if (first != null) { + first = new RetimedHistoricItem(first, beginTime); + } + if (first != null) { + betweenItemsList.add(0, first); } } // add HistoricItem at end - if (end != null && !end.isAfter(ZonedDateTime.now())) { - if (betweenItemsList.isEmpty() - || !betweenItemsList.get(betweenItemsList.size() - 1).getTimestamp().equals(end)) { - HistoricItem last = historicState(item, end, serviceId); + if (betweenItemsList.isEmpty() + || !betweenItemsList.get(betweenItemsList.size() - 1).getTimestamp().equals(end)) { + HistoricItem last = endTime.equals(now) ? historicItemOrCurrentState(item, null) + : internalPersistedState(item, endTime, serviceId); + if (last != null) { + last = new RetimedHistoricItem(last, endTime); + } + if (last != null) { + betweenItemsList.add(last); + } + } + return !betweenItemsList.isEmpty() ? betweenItemsList : null; + } - if (last != null) { - betweenItemsList.add(new RetimedHistoricItem(last, end)); - } + private static @Nullable PersistenceService getService(String serviceId) { + return registry != null ? registry.get(serviceId) : null; + } + + private static @Nullable String getDefaultServiceId() { + if (registry != null) { + String id = registry.getDefaultId(); + if (id != null) { + return id; + } else { + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("There is no default persistence service configured!"); } + } else { + LoggerFactory.getLogger(PersistenceExtensions.class) + .warn("PersistenceServiceRegistryImpl is not available!"); } + return null; + } - return betweenItemsList; + private static @Nullable DecimalType getItemValue(Item item) { + if (item instanceof NumberItem numberItem) { + Unit unit = numberItem.getUnit(); + if (unit != null) { + QuantityType qt = item.getStateAs(QuantityType.class); + qt = (qt != null) ? qt.toUnit(unit) : qt; + if (qt != null) { + return new DecimalType(qt.toBigDecimal()); + } + } + } + return item.getStateAs(DecimalType.class); } - private static @Nullable HistoricItem historicItemOrCurrentState(Item item, HistoricItem historicItem, - DecimalType value) { - if (historicItem == null && value != null) { + private static @Nullable HistoricItem historicItemOrCurrentState(Item item, @Nullable HistoricItem historicItem) { + if (historicItem == null) { // there are no historic states we couldn't determine a value, construct a HistoricItem from the current // state return new HistoricItem() { diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java index 0819f3f0fac..d2c8a7d7116 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/PersistenceExtensionsTest.java @@ -12,21 +12,13 @@ */ package org.openhab.core.persistence.extensions; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.closeTo; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; +import static org.openhab.core.persistence.extensions.TestPersistenceService.*; -import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -45,24 +37,28 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.i18n.UnitProvider; import org.openhab.core.items.GenericItem; import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.ItemUtil; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; import org.openhab.core.persistence.HistoricItem; import org.openhab.core.persistence.PersistenceService; import org.openhab.core.persistence.PersistenceServiceRegistry; +import org.openhab.core.types.State; /** * @author Kai Kreuzer - Initial contribution * @author Chris Jackson - Initial contribution * @author Jan N. Klug - Fix averageSince calculation * @author Jan N. Klug - Interval method tests and refactoring + * @author Mark Herwege - Changed return types to State for some interval methods to also return unit + * @author Mark Herwege - Extended for future dates */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -75,6 +71,7 @@ public class PersistenceExtensionsTest { private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock; private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock; + private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock; private @NonNullByDefault({}) GenericItem numberItem, quantityItem, switchItem; @@ -88,10 +85,16 @@ public void setUp() { TEST_QUANTITY_NUMBER); switchItem = itemFactory.createItem(CoreItemFactory.SWITCH, TEST_SWITCH); + numberItem.setState(STATE); + quantityItem.setState(new QuantityType(STATE, SIUnits.CELSIUS)); + switchItem.setState(SWITCH_STATE); + when(itemRegistryMock.get(TEST_NUMBER)).thenReturn(numberItem); when(itemRegistryMock.get(TEST_QUANTITY_NUMBER)).thenReturn(quantityItem); when(itemRegistryMock.get(TEST_SWITCH)).thenReturn(switchItem); + when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.systemDefault()); + new PersistenceExtensions(new PersistenceServiceRegistry() { private final PersistenceService testPersistenceService = new TestPersistenceService(itemRegistryMock); @@ -114,431 +117,1171 @@ public Set getAll() { @Override public @Nullable PersistenceService get(@Nullable String serviceId) { - return TestPersistenceService.ID.equals(serviceId) ? testPersistenceService : null; + return TestPersistenceService.SERVICE_ID.equals(serviceId) ? testPersistenceService : null; } - }); + }, timeZoneProviderMock); } @Test - public void testHistoricStateDecimalType() { - HistoricItem historicItem = PersistenceExtensions.historicState(numberItem, - ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + public void testPersistedStateDecimalType() { + HistoricItem historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); - assertEquals("2012", historicItem.getState().toString()); + assertEquals(value(HISTORIC_END), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 12, 31, 0, 0, 0, 0, ZoneId.systemDefault()), + SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(HISTORIC_INTERMEDIATE_VALUE_1), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(HISTORIC_INTERMEDIATE_VALUE_1), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(numberItem, ZonedDateTime.now(), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(TestPersistenceService.HISTORIC_END), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_NOVALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(HISTORIC_END), historicItem.getState()); - historicItem = PersistenceExtensions.historicState(numberItem, - ZonedDateTime.of(2011, 12, 31, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2011", historicItem.getState().toString()); + assertEquals(value(FUTURE_START), historicItem.getState()); - historicItem = PersistenceExtensions.historicState(numberItem, - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2011", historicItem.getState().toString()); + assertEquals(value(FUTURE_INTERMEDIATE_VALUE_3), historicItem.getState()); - historicItem = PersistenceExtensions.historicState(numberItem, - ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(FUTURE_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2000", historicItem.getState().toString()); + assertEquals(value(FUTURE_END), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(AFTER_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(FUTURE_END), historicItem.getState()); // default persistence service - historicItem = PersistenceExtensions.historicState(numberItem, - ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + historicItem = PersistenceExtensions.persistedState(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); assertNull(historicItem); } @Test - public void testHistoricStateQuantityType() { - HistoricItem historicItem = PersistenceExtensions.historicState(quantityItem, - ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + public void testPersistedStateQuantityType() { + HistoricItem historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); - assertEquals("2012 °C", historicItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), historicItem.getState()); - historicItem = PersistenceExtensions.historicState(quantityItem, - ZonedDateTime.of(2011, 12, 31, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 12, 31, 0, 0, 0, 0, ZoneId.systemDefault()), + SERVICE_ID); assertNotNull(historicItem); - assertEquals("2011 °C", historicItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_INTERMEDIATE_VALUE_1), SIUnits.CELSIUS), + historicItem.getState()); - historicItem = PersistenceExtensions.historicState(quantityItem, - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2011 °C", historicItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_INTERMEDIATE_VALUE_1), SIUnits.CELSIUS), + historicItem.getState()); - historicItem = PersistenceExtensions.historicState(quantityItem, - ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(quantityItem, ZonedDateTime.now(), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2000 °C", historicItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_NOVALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(new QuantityType<>(value(FUTURE_START), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(new QuantityType<>(value(FUTURE_INTERMEDIATE_VALUE_3), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(FUTURE_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(new QuantityType<>(value(FUTURE_END), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(AFTER_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(new QuantityType<>(value(FUTURE_END), SIUnits.CELSIUS), historicItem.getState()); // default persistence service - historicItem = PersistenceExtensions.historicState(quantityItem, - ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + historicItem = PersistenceExtensions.persistedState(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); assertNull(historicItem); } @Test - public void testHistoricSwitchState() { + public void testPersistedStateOnOffType() { ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS).minusMinutes(1); - HistoricItem historicItem = PersistenceExtensions.historicState(switchItem, now.minusHours(15), - TestPersistenceService.ID); + HistoricItem historicItem = PersistenceExtensions.persistedState(switchItem, now.plusHours(SWITCH_START), + SERVICE_ID); assertNull(historicItem); - historicItem = PersistenceExtensions.historicState(switchItem, now.minusHours(14), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_1), + SERVICE_ID); + assertNotNull(historicItem); + assertEquals(switchValue(SWITCH_ON_INTERMEDIATE_1), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(switchItem, now.plusHours(SWITCH_OFF_INTERMEDIATE_1), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_OFF_INTERMEDIATE_1), historicItem.getState()); - historicItem = PersistenceExtensions.historicState(switchItem, now.minusHours(4), TestPersistenceService.ID); + historicItem = PersistenceExtensions.persistedState(switchItem, now.plusHours(SWITCH_OFF_INTERMEDIATE_2), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.OFF, historicItem.getState()); + assertEquals(switchValue(SWITCH_OFF_INTERMEDIATE_2), historicItem.getState()); + + historicItem = PersistenceExtensions.persistedState(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_3), + SERVICE_ID); + assertNotNull(historicItem); + assertEquals(switchValue(SWITCH_ON_INTERMEDIATE_3), historicItem.getState()); + } @Test public void testMaximumSinceDecimalType() { - numberItem.setState(new DecimalType(1)); HistoricItem historicItem = PersistenceExtensions.maximumSince(numberItem, - ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); - assertEquals("2012", historicItem.getState().toString()); + assertEquals(value(HISTORIC_END), historicItem.getState()); historicItem = PersistenceExtensions.maximumSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2012", historicItem.getState().toString()); - assertEquals(ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), historicItem.getTimestamp()); + assertEquals(value(HISTORIC_END), historicItem.getState()); + assertEquals(ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + historicItem.getTimestamp()); // default persistence service historicItem = PersistenceExtensions.maximumSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); + } + + @Test + public void testMaximumTillDecimalType() { + HistoricItem historicItem = PersistenceExtensions.maximumTill(numberItem, + ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("1", historicItem.getState().toString()); + assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); + assertEquals(value(FUTURE_START), historicItem.getState()); + + historicItem = PersistenceExtensions.maximumTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(FUTURE_INTERMEDIATE_VALUE_3), historicItem.getState()); + assertEquals(ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + historicItem.getTimestamp()); + + // default persistence service + historicItem = PersistenceExtensions.maximumTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test public void testMaximumBetweenDecimalType() { HistoricItem historicItem = PersistenceExtensions.maximumBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(historicItem, is(notNullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); + assertThat(historicItem.getState(), is(value(HISTORIC_INTERMEDIATE_VALUE_2))); + + historicItem = PersistenceExtensions.maximumBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); + assertThat(historicItem.getState(), is(value(FUTURE_INTERMEDIATE_VALUE_4))); + + historicItem = PersistenceExtensions.maximumBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); - assertThat(historicItem.getState().toString(), is("2011")); + assertThat(historicItem.getState(), is(value(FUTURE_INTERMEDIATE_VALUE_4))); + // default persistence service historicItem = PersistenceExtensions.maximumBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(historicItem, is(nullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test public void testMaximumSinceQuantityType() { - quantityItem.setState(QuantityType.valueOf(1, SIUnits.CELSIUS)); HistoricItem historicItem = PersistenceExtensions.maximumSince(quantityItem, - ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(historicItem, is(notNullValue())); + ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); - assertThat(historicItem.getState().toString(), is("2012 °C")); + assertThat(historicItem.getState(), is(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS))); historicItem = PersistenceExtensions.maximumSince(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(historicItem, is(notNullValue())); - assertThat(historicItem.getState().toString(), is("2012 °C")); - assertThat(historicItem.getTimestamp(), is(ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()))); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS))); + assertThat(historicItem.getTimestamp(), + is(ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()))); // default persistence service historicItem = PersistenceExtensions.maximumSince(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(historicItem, is(notNullValue())); - assertThat(historicItem.getState().toString(), is("1 °C")); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); + + // test with alternative unit + quantityItem.setState(QuantityType.valueOf(5000, Units.KELVIN)); + historicItem = PersistenceExtensions.maximumSince(quantityItem, + ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertThat(historicItem.getState(), is(new QuantityType<>(4726.85, SIUnits.CELSIUS))); + } + + @Test + public void testMaximumTillQuantityType() { + HistoricItem historicItem = PersistenceExtensions.maximumTill(quantityItem, + ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertEquals(new QuantityType<>(value(FUTURE_START), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.maximumTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(new QuantityType<>(value(FUTURE_INTERMEDIATE_VALUE_3), SIUnits.CELSIUS), historicItem.getState()); + assertEquals(ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + historicItem.getTimestamp()); + + // default persistence service + historicItem = PersistenceExtensions.maximumTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test public void testMaximumBetweenQuantityType() { HistoricItem historicItem = PersistenceExtensions.maximumBetween(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(historicItem, is(notNullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertThat(historicItem.getState(), + is(new QuantityType<>(value(HISTORIC_INTERMEDIATE_VALUE_2), SIUnits.CELSIUS))); + + historicItem = PersistenceExtensions.maximumBetween(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertThat(historicItem.getState(), + is(new QuantityType<>(value(FUTURE_INTERMEDIATE_VALUE_4), SIUnits.CELSIUS))); + + historicItem = PersistenceExtensions.maximumBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); - assertThat(historicItem.getState().toString(), is("2011 °C")); + assertThat(historicItem.getState(), + is(new QuantityType<>(value(FUTURE_INTERMEDIATE_VALUE_4), SIUnits.CELSIUS))); + // default persistence service historicItem = PersistenceExtensions.maximumBetween(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(historicItem, is(nullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test - public void testMaximumSinceSwitch() { - switchItem.setState(OnOffType.OFF); - + public void testMaximumSinceOnOffType() { ZonedDateTime now = ZonedDateTime.now(); - HistoricItem historicItem = PersistenceExtensions.maximumSince(switchItem, now.minusHours(15), - TestPersistenceService.ID); + HistoricItem historicItem = PersistenceExtensions.maximumSince(switchItem, now.plusHours(SWITCH_START), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_1), historicItem.getState()); - historicItem = PersistenceExtensions.maximumSince(switchItem, now.minusHours(6), TestPersistenceService.ID); + historicItem = PersistenceExtensions.maximumSince(switchItem, now.plusHours(SWITCH_OFF_INTERMEDIATE_1), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_2), historicItem.getState()); - historicItem = PersistenceExtensions.maximumSince(switchItem, now.minusHours(1), TestPersistenceService.ID); + historicItem = PersistenceExtensions.maximumSince(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_21), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_2), historicItem.getState()); - historicItem = PersistenceExtensions.maximumSince(switchItem, now, TestPersistenceService.ID); + historicItem = PersistenceExtensions.maximumSince(switchItem, now, SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_2), historicItem.getState()); + + historicItem = PersistenceExtensions.maximumSince(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_22), + SERVICE_ID); + assertNull(historicItem); + } - historicItem = PersistenceExtensions.maximumSince(switchItem, now.plusHours(1), TestPersistenceService.ID); + @Test + public void testMaximumTillOnOffType() { + ZonedDateTime now = ZonedDateTime.now(); + HistoricItem historicItem = PersistenceExtensions.maximumTill(switchItem, + now.plusHours(SWITCH_OFF_INTERMEDIATE_2), SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.OFF, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_2), historicItem.getState()); + + historicItem = PersistenceExtensions.maximumTill(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_3), + SERVICE_ID); + assertNotNull(historicItem); + assertEquals(switchValue(SWITCH_ON_3), historicItem.getState()); + + historicItem = PersistenceExtensions.maximumTill(switchItem, now.plusHours(SWITCH_END), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(switchValue(SWITCH_ON_3), historicItem.getState()); + + historicItem = PersistenceExtensions.maximumTill(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_21), + SERVICE_ID); + assertNull(historicItem); } @Test public void testMinimumSinceDecimalType() { - numberItem.setState(new DecimalType(5000)); HistoricItem historicItem = PersistenceExtensions.minimumSince(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); - assertEquals("5000", historicItem.getState().toString()); + assertEquals(value(HISTORIC_START), historicItem.getState()); historicItem = PersistenceExtensions.minimumSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2005", historicItem.getState().toString()); - assertEquals(ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), historicItem.getTimestamp()); + assertEquals(value(HISTORIC_INTERMEDIATE_VALUE_1), historicItem.getState()); + assertEquals(ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + historicItem.getTimestamp()); // default persistence service historicItem = PersistenceExtensions.minimumSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); + } + + @Test + public void testMinimumTillDecimalType() { + HistoricItem historicItem = PersistenceExtensions.minimumTill(numberItem, + ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("5000", historicItem.getState().toString()); + assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); + assertEquals(value(HISTORIC_END), historicItem.getState()); + + historicItem = PersistenceExtensions.minimumTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertEquals(value(HISTORIC_END), historicItem.getState()); + + // default persistence service + historicItem = PersistenceExtensions.minimumTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test public void testMinimumBetweenDecimalType() { HistoricItem historicItem = PersistenceExtensions.minimumBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(historicItem, is(notNullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); + assertThat(historicItem.getState(), is(value(HISTORIC_INTERMEDIATE_VALUE_1))); + + historicItem = PersistenceExtensions.minimumBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); - assertThat(historicItem.getState().toString(), is("2005")); + assertThat(historicItem.getState(), is(value(FUTURE_INTERMEDIATE_VALUE_3))); historicItem = PersistenceExtensions.minimumBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(historicItem, is(nullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(DecimalType.class))); + assertThat(historicItem.getState(), is(value(HISTORIC_INTERMEDIATE_VALUE_1))); + + // default persistence service + historicItem = PersistenceExtensions.minimumBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test public void testMinimumSinceQuantityType() { - quantityItem.setState(QuantityType.valueOf(5000, SIUnits.CELSIUS)); HistoricItem historicItem = PersistenceExtensions.minimumSince(quantityItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); - assertEquals("5000 °C", historicItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_START), SIUnits.CELSIUS), historicItem.getState()); historicItem = PersistenceExtensions.minimumSince(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("2005 °C", historicItem.getState().toString()); - assertEquals(ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), historicItem.getTimestamp()); + assertEquals(new QuantityType<>(value(HISTORIC_INTERMEDIATE_VALUE_1), SIUnits.CELSIUS), + historicItem.getState()); + assertEquals(ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + historicItem.getTimestamp()); // default persistence service historicItem = PersistenceExtensions.minimumSince(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); + + // test with alternative unit + quantityItem.setState(QuantityType.valueOf(273.15, Units.KELVIN)); + historicItem = PersistenceExtensions.minimumSince(quantityItem, + ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertThat(historicItem.getState(), is(new QuantityType<>(0, SIUnits.CELSIUS))); + } + + @Test + public void testMinimumTillQuantityType() { + HistoricItem historicItem = PersistenceExtensions.minimumTill(quantityItem, + ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), historicItem.getState()); + + historicItem = PersistenceExtensions.minimumTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(historicItem); - assertEquals("5000 °C", historicItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), historicItem.getState()); + + // default persistence service + historicItem = PersistenceExtensions.minimumTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test public void testMinimumBetweenQuantityType() { HistoricItem historicItem = PersistenceExtensions.minimumBetween(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(historicItem, is(notNullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertThat(historicItem.getState(), + is(new QuantityType<>(value(HISTORIC_INTERMEDIATE_VALUE_1), SIUnits.CELSIUS))); + + historicItem = PersistenceExtensions.minimumBetween(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); - assertThat(historicItem.getState().toString(), is("2005 °C")); + assertThat(historicItem.getState(), + is(new QuantityType<>(value(FUTURE_INTERMEDIATE_VALUE_3), SIUnits.CELSIUS))); historicItem = PersistenceExtensions.minimumBetween(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(historicItem, is(nullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(historicItem); + assertThat(historicItem.getState(), is(instanceOf(QuantityType.class))); + assertThat(historicItem.getState(), + is(new QuantityType<>(value(HISTORIC_INTERMEDIATE_VALUE_1), SIUnits.CELSIUS))); + + // default persistence service + historicItem = PersistenceExtensions.minimumBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(historicItem); } @Test - public void testMinimumSinceSwitch() { - switchItem.setState(OnOffType.ON); - + public void testMinimumSinceOnOffType() { ZonedDateTime now = ZonedDateTime.now(); - HistoricItem historicItem = PersistenceExtensions.minimumSince(switchItem, now.minusHours(15), - TestPersistenceService.ID); + HistoricItem historicItem = PersistenceExtensions.minimumSince(switchItem, now.plusHours(SWITCH_START), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.OFF, historicItem.getState()); + assertEquals(switchValue(SWITCH_OFF_1), historicItem.getState()); - historicItem = PersistenceExtensions.minimumSince(switchItem, now.minusHours(6), TestPersistenceService.ID); + historicItem = PersistenceExtensions.minimumSince(switchItem, now.plusHours(SWITCH_OFF_INTERMEDIATE_1), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.OFF, historicItem.getState()); + assertEquals(switchValue(SWITCH_OFF_INTERMEDIATE_1), historicItem.getState()); - historicItem = PersistenceExtensions.minimumSince(switchItem, now.minusHours(1), TestPersistenceService.ID); + historicItem = PersistenceExtensions.minimumSince(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_21), + SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_INTERMEDIATE_21), historicItem.getState()); - historicItem = PersistenceExtensions.minimumSince(switchItem, now, TestPersistenceService.ID); + historicItem = PersistenceExtensions.minimumSince(switchItem, now, SERVICE_ID); assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + assertEquals(switchValue(SWITCH_ON_INTERMEDIATE_22), historicItem.getState()); - historicItem = PersistenceExtensions.minimumSince(switchItem, now.plusHours(1), TestPersistenceService.ID); - assertNotNull(historicItem); - assertEquals(OnOffType.ON, historicItem.getState()); + historicItem = PersistenceExtensions.minimumSince(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_22), + SERVICE_ID); + assertNull(historicItem); } @Test - public void testVarianceSince() { - numberItem.setState(new DecimalType(3025)); - - ZonedDateTime startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + public void testVarianceSinceDecimalType() { + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(HISTORIC_INTERMEDIATE_VALUE_1, null); + + double expected = DoubleStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END) + .mapToDouble(i -> Double.valueOf(i)), DoubleStream.of(STATE.doubleValue())) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (HISTORIC_END + 1 - HISTORIC_INTERMEDIATE_VALUE_1 + 1); + State variance = PersistenceExtensions.varianceSince(numberItem, startStored, SERVICE_ID); + assertNotNull(variance); + DecimalType dt = variance.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); - long storedInterval = Duration.between(startStored, endStored).toDays(); - long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); - double expectedAverage = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) - / (storedInterval + recentInterval); + // default persistence service + variance = PersistenceExtensions.varianceSince(numberItem, startStored); + assertNull(variance); + } - double expected = IntStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) - .mapToDouble(i -> Double.parseDouble(Integer.toString(i))).map(d -> Math.pow(d - expectedAverage, 2)) - .sum() / 10d; - DecimalType variance = PersistenceExtensions.varianceSince(numberItem, startStored, TestPersistenceService.ID); + @Test + public void testVarianceTillDecimalType() { + ZonedDateTime endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(null, FUTURE_INTERMEDIATE_VALUE_3); + + double expected = DoubleStream + .concat(DoubleStream.of(STATE.doubleValue()), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3) + .mapToDouble(i -> Double.valueOf(i))) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (1 + FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1); + State variance = PersistenceExtensions.varianceTill(numberItem, endStored, SERVICE_ID); assertNotNull(variance); - assertEquals(expected, variance.doubleValue(), 0.01); + DecimalType dt = variance.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); // default persistence service - variance = PersistenceExtensions.varianceSince(numberItem, startStored); + variance = PersistenceExtensions.varianceTill(numberItem, endStored); assertNull(variance); } @Test - public void testVarianceBetween() { - ZonedDateTime startStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + public void testVarianceBetweenDecimalType() { + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + ZonedDateTime endStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage1 = average(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2); + + double expected = IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage1, 2)).sum() + / (HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1 + 1); + + State variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored, SERVICE_ID); + assertNotNull(variance); + DecimalType dt = variance.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + startStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage2 = average(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4); + + expected = IntStream.rangeClosed(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage2, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3 + 1); + + variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored, SERVICE_ID); + assertNotNull(variance); + dt = variance.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage3 = average(HISTORIC_INTERMEDIATE_VALUE_1, FUTURE_INTERMEDIATE_VALUE_3); - double expected = DoubleStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011) - .map(d -> Math.pow(d - (2005.0 + 2010.0) / 2.0, 2)).sum() / 7d; + expected = IntStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3)) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage3, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1 + HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1); - DecimalType variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored, - TestPersistenceService.ID); - assertThat(variance, is(notNullValue())); - assertThat(variance.doubleValue(), is(closeTo(expected, 0.01))); + variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored, SERVICE_ID); + assertNotNull(variance); + dt = variance.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); // default persistence service variance = PersistenceExtensions.varianceBetween(numberItem, startStored, endStored); - assertThat(variance, is(nullValue())); + assertNull(variance); + } + + @Test + public void testVarianceSinceQuantityType() { + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(HISTORIC_INTERMEDIATE_VALUE_1, null); + + double expected = DoubleStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END) + .mapToDouble(i -> Double.valueOf(i)), DoubleStream.of(STATE.doubleValue())) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (HISTORIC_END + 1 - HISTORIC_INTERMEDIATE_VALUE_1 + 1); + State variance = PersistenceExtensions.varianceSince(quantityItem, startStored, SERVICE_ID); + assertNotNull(variance); + QuantityType qt = variance.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS.multiply(SIUnits.CELSIUS), qt.getUnit()); + + // default persistence service + variance = PersistenceExtensions.varianceSince(quantityItem, startStored); + assertNull(variance); + } + + @Test + public void testVarianceTillQuantityType() { + ZonedDateTime endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(null, FUTURE_INTERMEDIATE_VALUE_3); + + double expected = DoubleStream + .concat(DoubleStream.of(STATE.doubleValue()), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3) + .mapToDouble(i -> Double.valueOf(i))) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (1 + FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1); + State variance = PersistenceExtensions.varianceTill(quantityItem, endStored, SERVICE_ID); + assertNotNull(variance); + QuantityType qt = variance.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS.multiply(SIUnits.CELSIUS), qt.getUnit()); + + // default persistence service + variance = PersistenceExtensions.varianceTill(quantityItem, endStored); + assertNull(variance); + } + + @Test + public void testVarianceBetweenQuantityType() { + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + ZonedDateTime endStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage1 = average(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2); + + double expected = IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage1, 2)).sum() + / (HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1 + 1); + + State variance = PersistenceExtensions.varianceBetween(quantityItem, startStored, endStored, SERVICE_ID); + assertNotNull(variance); + QuantityType qt = variance.as(QuantityType.class); + assertNotNull(qt); + assertThat(qt.doubleValue(), is(closeTo(expected, 0.01))); + assertEquals(SIUnits.CELSIUS.multiply(SIUnits.CELSIUS), qt.getUnit()); + + startStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage2 = average(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4); + + expected = IntStream.rangeClosed(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage2, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3 + 1); + + variance = PersistenceExtensions.varianceBetween(quantityItem, startStored, endStored, SERVICE_ID); + assertNotNull(variance); + qt = variance.as(QuantityType.class); + assertNotNull(qt); + assertThat(qt.doubleValue(), is(closeTo(expected, 0.01))); + assertEquals(SIUnits.CELSIUS.multiply(SIUnits.CELSIUS), qt.getUnit()); + + startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage3 = average(HISTORIC_INTERMEDIATE_VALUE_1, FUTURE_INTERMEDIATE_VALUE_3); + + expected = IntStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3)) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage3, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1 + HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1); + + variance = PersistenceExtensions.varianceBetween(quantityItem, startStored, endStored, SERVICE_ID); + assertNotNull(variance); + qt = variance.as(QuantityType.class); + assertNotNull(qt); + assertThat(qt.doubleValue(), is(closeTo(expected, 0.01))); + assertEquals(SIUnits.CELSIUS.multiply(SIUnits.CELSIUS), qt.getUnit()); + + // default persistence service + variance = PersistenceExtensions.varianceBetween(quantityItem, startStored, endStored); + assertNull(variance); } @Test public void testDeviationSinceDecimalType() { - ZonedDateTime startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - - long storedInterval = Duration.between(startStored, endStored).toDays(); - long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); - double expectedAverage = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) - / (storedInterval + recentInterval); - - double expected = Math.sqrt(DoubleStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) - .map(d -> Math.pow(d - expectedAverage, 2)).sum() / 10d); - DecimalType deviation = PersistenceExtensions.deviationSince(numberItem, startStored, - TestPersistenceService.ID); + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(HISTORIC_INTERMEDIATE_VALUE_1, null); + + double expected = Math.sqrt(DoubleStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END) + .mapToDouble(i -> Double.valueOf(i)), DoubleStream.of(STATE.doubleValue())) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (HISTORIC_END + 1 - HISTORIC_INTERMEDIATE_VALUE_1 + 1)); + State deviation = PersistenceExtensions.deviationSince(numberItem, startStored, SERVICE_ID); assertNotNull(deviation); - assertEquals(expected, deviation.doubleValue(), 0.01); + DecimalType dt = deviation.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); // default persistence service deviation = PersistenceExtensions.deviationSince(numberItem, startStored); assertNull(deviation); } + @Test + public void testDeviationTillDecimalType() { + ZonedDateTime endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(null, FUTURE_INTERMEDIATE_VALUE_3); + + double expected = Math.sqrt(DoubleStream + .concat(DoubleStream.of(STATE.doubleValue()), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3) + .mapToDouble(i -> Double.valueOf(i))) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (1 + FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1)); + State deviation = PersistenceExtensions.deviationTill(numberItem, endStored, SERVICE_ID); + assertNotNull(deviation); + DecimalType dt = deviation.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); + + // default persistence service + deviation = PersistenceExtensions.deviationTill(numberItem, endStored); + assertNull(deviation); + } + @Test public void testDeviationBetweenDecimalType() { - ZonedDateTime startStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + ZonedDateTime endStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2); + + double expected = Math.sqrt(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2) + .mapToDouble(i -> Double.parseDouble(Integer.toString(i))).map(d -> Math.pow(d - expectedAverage, 2)) + .sum() / (HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1 + 1)); + State deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored, SERVICE_ID); + assertNotNull(deviation); + DecimalType dt = deviation.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + startStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage2 = average(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4); + + expected = Math.sqrt(IntStream.rangeClosed(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage2, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3 + 1)); + + deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored, SERVICE_ID); + assertNotNull(deviation); + dt = deviation.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage3 = average(HISTORIC_INTERMEDIATE_VALUE_1, FUTURE_INTERMEDIATE_VALUE_3); + + expected = Math.sqrt(IntStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3)) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage3, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1 + HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1)); + + deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored, SERVICE_ID); + assertNotNull(deviation); + dt = deviation.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + // default persistence service + deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored); + assertNull(deviation); + } + + @Test + public void testDeviationSinceQuantityType() { + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(HISTORIC_INTERMEDIATE_VALUE_1, null); + + double expected = Math.sqrt(DoubleStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END) + .mapToDouble(i -> Double.valueOf(i)), DoubleStream.of(STATE.doubleValue())) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (HISTORIC_END + 1 - HISTORIC_INTERMEDIATE_VALUE_1 + 1)); + State deviation = PersistenceExtensions.deviationSince(quantityItem, startStored, SERVICE_ID); + assertNotNull(deviation); + QuantityType qt = deviation.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + // default persistence service + deviation = PersistenceExtensions.deviationSince(quantityItem, startStored); + assertNull(deviation); + } + + @Test + public void testDeviationTillQuantityType() { + ZonedDateTime endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(null, FUTURE_INTERMEDIATE_VALUE_3); + + double expected = Math.sqrt(DoubleStream + .concat(DoubleStream.of(STATE.doubleValue()), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3) + .mapToDouble(i -> Double.valueOf(i))) + .map(d -> Math.pow(d - expectedAverage, 2)).sum() + / (1 + FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1)); + State deviation = PersistenceExtensions.deviationTill(quantityItem, endStored, SERVICE_ID); + assertNotNull(deviation); + QuantityType qt = deviation.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + // default persistence service + deviation = PersistenceExtensions.deviationTill(quantityItem, endStored); + assertNull(deviation); + } + + @Test + public void testDeviationBetweenQuantityType() { + ZonedDateTime startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + ZonedDateTime endStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expectedAverage = average(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2); + + double expected = Math.sqrt(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2) + .mapToDouble(i -> Double.parseDouble(Integer.toString(i))).map(d -> Math.pow(d - expectedAverage, 2)) + .sum() / (HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1 + 1)); + State deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored, SERVICE_ID); + assertNotNull(deviation); + QuantityType qt = deviation.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + startStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage2 = average(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4); + + expected = Math.sqrt(IntStream.rangeClosed(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage2, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3 + 1)); + + deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored, SERVICE_ID); + assertNotNull(deviation); + qt = deviation.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + startStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expectedAverage3 = average(HISTORIC_INTERMEDIATE_VALUE_1, FUTURE_INTERMEDIATE_VALUE_3); + + expected = Math.sqrt(IntStream + .concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3)) + .mapToDouble(i -> Double.valueOf(i)).map(d -> Math.pow(d - expectedAverage3, 2)).sum() + / (FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1 + HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1)); + + deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored, SERVICE_ID); + assertNotNull(deviation); + qt = deviation.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + // default persistence service + deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored); + assertNull(deviation); + } + + @Test + public void testAverageSinceDecimalType() { + ZonedDateTime start = ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expected = average(BEFORE_START, null); + State average = PersistenceExtensions.averageSince(numberItem, start, SERVICE_ID); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); + + start = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + expected = average(HISTORIC_INTERMEDIATE_VALUE_1, null); + average = PersistenceExtensions.averageSince(numberItem, start, SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); + + // default persistence service + average = PersistenceExtensions.averageSince(numberItem, start); + assertNull(average); + } + + @Test + public void testAverageTillDecimalType() { + ZonedDateTime end = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expected = average(null, FUTURE_INTERMEDIATE_VALUE_3); + State average = PersistenceExtensions.averageTill(numberItem, end, SERVICE_ID); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); + + // default persistence service + average = PersistenceExtensions.averageTill(numberItem, end); + assertNull(average); + } + + @Test + public void testAverageBetweenDecimalType() { + ZonedDateTime beginStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + ZonedDateTime endStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + + double expected = average(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2); + State average = PersistenceExtensions.averageBetween(numberItem, beginStored, endStored, SERVICE_ID); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertEquals(expected, dt.doubleValue(), 0.01); + + beginStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + expected = average(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4); + + average = PersistenceExtensions.averageBetween(numberItem, beginStored, endStored, SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + beginStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + expected = average(HISTORIC_INTERMEDIATE_VALUE_1, FUTURE_INTERMEDIATE_VALUE_3); + + average = PersistenceExtensions.averageBetween(numberItem, beginStored, endStored, SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(expected, 0.01))); + + // default persistence service + average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored); + assertNull(average); + } + + @Test + public void testAverageSinceQuantityType() { + ZonedDateTime start = ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expected = average(BEFORE_START, null); + State average = PersistenceExtensions.averageSince(quantityItem, start, SERVICE_ID); + assertNotNull(average); + QuantityType qt = average.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + start = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + expected = average(HISTORIC_INTERMEDIATE_VALUE_1, null); + average = PersistenceExtensions.averageSince(quantityItem, start, SERVICE_ID); + assertNotNull(average); + qt = average.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + // default persistence service + average = PersistenceExtensions.averageSince(quantityItem, start); + assertNull(average); + } + + @Test + public void testAverageTillQuantityType() { + ZonedDateTime end = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + double expected = average(null, FUTURE_INTERMEDIATE_VALUE_3); + State average = PersistenceExtensions.averageTill(quantityItem, end, SERVICE_ID); + assertNotNull(average); + QuantityType qt = average.as(QuantityType.class); + assertNotNull(qt); + assertEquals(expected, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + // default persistence service + average = PersistenceExtensions.averageTill(quantityItem, end); + assertNull(average); + } + + @Test + public void testAverageBetweenQuantityType() { + ZonedDateTime beginStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + ZonedDateTime endStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()); + double expected = average(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2); + State average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored, SERVICE_ID); + + assertNotNull(average); + QuantityType qt = average.as(QuantityType.class); + assertNotNull(qt); + assertThat(qt.doubleValue(), is(closeTo(expected, 0.01))); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + beginStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + expected = average(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4); - double expected = Math.sqrt(DoubleStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011) - .map(d -> Math.pow(d - (2005.0 + 2010.0) / 2.0, 2)).sum() / 7d); + average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored, SERVICE_ID); + assertNotNull(average); + qt = average.as(QuantityType.class); + assertNotNull(qt); + assertThat(qt.doubleValue(), is(closeTo(expected, 0.01))); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + beginStored = ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + endStored = ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + expected = average(HISTORIC_INTERMEDIATE_VALUE_1, FUTURE_INTERMEDIATE_VALUE_3); - DecimalType deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored, - TestPersistenceService.ID); - assertThat(deviation, is(notNullValue())); - assertThat(deviation.doubleValue(), is(closeTo(expected, 0.01))); + average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored, SERVICE_ID); + assertNotNull(average); + qt = average.as(QuantityType.class); + assertNotNull(qt); + assertThat(qt.doubleValue(), is(closeTo(expected, 0.01))); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); // default persistence service - deviation = PersistenceExtensions.deviationBetween(numberItem, startStored, endStored); - assertThat(deviation, is(nullValue())); + average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored); + assertNull(average); } @Test - public void testDeviationSinceQuantityType() { - quantityItem.setState(QuantityType.valueOf(3025, SIUnits.CELSIUS)); + public void testAverageSinceOnOffType() { + // switch is 5h ON, 5h OFF, and 5h ON (until now) - ZonedDateTime startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES); + State average = PersistenceExtensions.averageSince(switchItem, now.plusHours(SWITCH_START), SERVICE_ID); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), + is(closeTo((SWITCH_OFF_1 - SWITCH_ON_1 - SWITCH_ON_2) / (-1.0 * SWITCH_START), 0.01))); - long storedInterval = Duration.between(startStored, endStored).toDays(); - long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); - double expectedAverage = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) - / (storedInterval + recentInterval); + average = PersistenceExtensions.averageSince(switchItem, now.plusHours(SWITCH_OFF_INTERMEDIATE_1), SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(-SWITCH_ON_2 / (-1.0 * SWITCH_OFF_INTERMEDIATE_1), 0.01))); - double expected = Math.sqrt(DoubleStream.of(2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012) - .map(d -> Math.pow(d - expectedAverage, 2)).sum() / 10d); + average = PersistenceExtensions.averageSince(switchItem, now.plusHours(SWITCH_ON_2), SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(-SWITCH_ON_2 / (-1.0 * SWITCH_ON_2), 0.01))); - DecimalType deviation = PersistenceExtensions.deviationSince(quantityItem, startStored, - TestPersistenceService.ID); - assertNotNull(deviation); - assertEquals(expected, deviation.doubleValue(), 0.01); + average = PersistenceExtensions.averageSince(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_21), SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), + is(closeTo(-SWITCH_ON_INTERMEDIATE_21 / (-1.0 * SWITCH_ON_INTERMEDIATE_21), 0.01))); - // default persistence service - deviation = PersistenceExtensions.deviationSince(quantityItem, startStored); - assertNull(deviation); + average = PersistenceExtensions.averageSince(switchItem, now, SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(1d, 0.01))); + + average = PersistenceExtensions.averageSince(switchItem, now.plusHours(1), SERVICE_ID); + assertNull(average); } @Test - public void testDeviationBetweenQuantityType() { - ZonedDateTime startStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - - double expected = Math.sqrt(DoubleStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011) - .map(d -> Math.pow(d - (2005.0 + 2010.0) / 2.0, 2)).sum() / 7d); + public void testAverageTillOnOffType() { + // switch is 5h ON, 5h OFF, and 5h ON (from now) - DecimalType deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored, - TestPersistenceService.ID); - assertThat(deviation, is(notNullValue())); - assertThat(deviation.doubleValue(), is(closeTo(expected, 0.01))); + ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES); + State average = PersistenceExtensions.averageTill(switchItem, now.plusHours(SWITCH_END), SERVICE_ID); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), + is(closeTo((SWITCH_OFF_3 - SWITCH_ON_3 + SWITCH_OFF_2) / (1.0 * SWITCH_END), 0.01))); - // default persistence service - deviation = PersistenceExtensions.deviationBetween(quantityItem, startStored, endStored); - assertThat(deviation, is(nullValue())); - } + average = PersistenceExtensions.averageTill(switchItem, now.plusHours(SWITCH_OFF_INTERMEDIATE_2), SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(SWITCH_OFF_2 / (1.0 * SWITCH_OFF_INTERMEDIATE_2), 0.01))); - @Test - public void testAverageSinceDecimalType() { - ZonedDateTime startStored = ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - DecimalType average = PersistenceExtensions.averageSince(numberItem, startStored, TestPersistenceService.ID); - assertNull(average); + average = PersistenceExtensions.averageTill(switchItem, now.plusHours(SWITCH_ON_3), SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(SWITCH_OFF_2 / (1.0 * SWITCH_ON_3), 0.01))); - startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - long storedInterval = Duration.between(startStored, endStored).toDays(); - long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); - double expected = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) - / (storedInterval + recentInterval); + average = PersistenceExtensions.averageTill(switchItem, now.plusHours(SWITCH_ON_INTERMEDIATE_22), SERVICE_ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(SWITCH_ON_INTERMEDIATE_22 / (1.0 * SWITCH_ON_INTERMEDIATE_22), 0.01))); - average = PersistenceExtensions.averageSince(numberItem, startStored, TestPersistenceService.ID); + average = PersistenceExtensions.averageTill(switchItem, now.plusMinutes(1), SERVICE_ID); assertNotNull(average); - assertEquals(expected, average.doubleValue(), 0.01); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(1d, 0.01))); - // default persistence service - average = PersistenceExtensions.averageSince(numberItem, startStored); + average = PersistenceExtensions.averageTill(switchItem, now.minusHours(1), SERVICE_ID); assertNull(average); } @@ -568,7 +1311,7 @@ public Set getAll() { public @Nullable PersistenceService get(@Nullable String serviceId) { return TestCachedValuesPersistenceService.ID.equals(serviceId) ? persistenceService : null; } - }); + }, timeZoneProviderMock); ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime beginStored = now.minusHours(27); @@ -578,294 +1321,532 @@ public Set getAll() { persistenceService.addHistoricItem(beginStored.plusHours(2), new DecimalType(0), TEST_NUMBER); persistenceService.addHistoricItem(beginStored.plusHours(25), new DecimalType(50), TEST_NUMBER); persistenceService.addHistoricItem(beginStored.plusHours(26), new DecimalType(0), TEST_NUMBER); + numberItem.setState(new DecimalType(0)); - DecimalType average = PersistenceExtensions.averageSince(numberItem, beginStored, + State average = PersistenceExtensions.averageSince(numberItem, beginStored, TestCachedValuesPersistenceService.ID); - assertThat(average.doubleValue(), is(closeTo((100.0 + 50.0) / 27.0, 0.01))); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo((100.0 + 50.0) / 27.0, 0.01))); average = PersistenceExtensions.averageSince(numberItem, beginStored.plusHours(3), TestCachedValuesPersistenceService.ID); - assertThat(average.doubleValue(), is(closeTo(50.0 / 24.0, 0.01))); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(50.0 / 24.0, 0.01))); average = PersistenceExtensions.averageSince(numberItem, now.minusMinutes(30), TestCachedValuesPersistenceService.ID); - assertThat(average.doubleValue(), is(closeTo(0, 0.01))); - } - - @Test - public void testAverageBetweenDecimalType() { - ZonedDateTime beginStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - DecimalType average = PersistenceExtensions.averageBetween(numberItem, beginStored, endStored, - TestPersistenceService.ID); - - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo((2005.0 + 2010.0) / 2.0, 0.01))); - - // default persistence service - average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored); - assertThat(average, is(nullValue())); - } - - @Test - public void testAverageSinceQuantityType() { - quantityItem.setState(QuantityType.valueOf(3025, SIUnits.CELSIUS)); - - ZonedDateTime startStored = ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - DecimalType average = PersistenceExtensions.averageSince(quantityItem, startStored, TestPersistenceService.ID); - assertNull(average); - - startStored = ZonedDateTime.of(2003, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - long storedInterval = Duration.between(startStored, endStored).toDays(); - long recentInterval = Duration.between(endStored, ZonedDateTime.now()).toDays(); - double expected = ((2003.0 + 2011.0) / 2.0 * storedInterval + 2012.0 * recentInterval) - / (storedInterval + recentInterval); - - average = PersistenceExtensions.averageSince(quantityItem, startStored, TestPersistenceService.ID); assertNotNull(average); - assertEquals(expected, average.doubleValue(), 0.01); - - // default persistence service - average = PersistenceExtensions.averageSince(quantityItem, startStored); - assertNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(0, 0.01))); } @Test - public void testAverageBetweenQuantityType() { - ZonedDateTime beginStored = ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - ZonedDateTime endStored = ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); - DecimalType average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored, - TestPersistenceService.ID); - - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo((2005.0 + 2010.0) / 2, 0.01))); + public void testAverageTillDecimalTypeIrregularTimespans() { + TestCachedValuesPersistenceService persistenceService = new TestCachedValuesPersistenceService(); + new PersistenceExtensions(new PersistenceServiceRegistry() { - // default persistence service - average = PersistenceExtensions.averageBetween(quantityItem, beginStored, endStored); - assertThat(average, is(nullValue())); - } + @Override + public @Nullable String getDefaultId() { + // not available + return null; + } - @Test - public void testAverageSinceSwitch() { - // switch is 5h ON, 6h OFF, and 4h ON (until now) + @Override + public @Nullable PersistenceService getDefault() { + // not available + return null; + } - ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES); - DecimalType average = PersistenceExtensions.averageSince(switchItem, now.minusHours(15), - TestPersistenceService.ID); - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(9.0 / 15.0, 0.01))); + @Override + public Set getAll() { + return Set.of(persistenceService); + } - average = PersistenceExtensions.averageSince(switchItem, now.minusHours(7), TestPersistenceService.ID); - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(4.0 / 7.0, 0.01))); + @Override + public @Nullable PersistenceService get(@Nullable String serviceId) { + return TestCachedValuesPersistenceService.ID.equals(serviceId) ? persistenceService : null; + } + }, timeZoneProviderMock); - average = PersistenceExtensions.averageSince(switchItem, now.minusHours(6), TestPersistenceService.ID); - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(0.833, 0.2))); + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime beginStored = now.plusHours(1); - average = PersistenceExtensions.averageSince(switchItem, now.minusHours(5), TestPersistenceService.ID); - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(1d, 0.2))); + persistenceService.addHistoricItem(beginStored, new DecimalType(0), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(1), new DecimalType(0), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(2), new DecimalType(50), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(3), new DecimalType(0), TEST_NUMBER); + persistenceService.addHistoricItem(beginStored.plusHours(25), new DecimalType(100), TEST_NUMBER); + numberItem.setState(new DecimalType(0)); - average = PersistenceExtensions.averageSince(switchItem, now.minusHours(1), TestPersistenceService.ID); - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(1d, 0.001))); + State average = PersistenceExtensions.averageTill(numberItem, beginStored.plusHours(26), + TestCachedValuesPersistenceService.ID); + assertNotNull(average); + DecimalType dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo((100.0 + 50.0) / 27.0, 0.01))); - average = PersistenceExtensions.averageSince(switchItem, now, TestPersistenceService.ID); - assertThat(average, is(notNullValue())); - assertThat(average.doubleValue(), is(closeTo(1d, 0.001))); + average = PersistenceExtensions.averageTill(numberItem, beginStored.plusHours(24), + TestCachedValuesPersistenceService.ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(50.0 / 25.0, 0.01))); - average = PersistenceExtensions.averageSince(switchItem, now.plusHours(1), TestPersistenceService.ID); - assertThat(average, is(nullValue())); + average = PersistenceExtensions.averageTill(numberItem, now.plusMinutes(30), + TestCachedValuesPersistenceService.ID); + assertNotNull(average); + dt = average.as(DecimalType.class); + assertNotNull(dt); + assertThat(dt.doubleValue(), is(closeTo(0, 0.01))); } @Test public void testAverageBetweenZeroDuration() { ZonedDateTime now = ZonedDateTime.now(); - assertDoesNotThrow( - () -> PersistenceExtensions.averageBetween(quantityItem, now, now, TestPersistenceService.ID)); - assertThat(PersistenceExtensions.averageBetween(quantityItem, now, now, TestPersistenceService.ID), - is(nullValue())); + State state = PersistenceExtensions.averageBetween(quantityItem, now, now, SERVICE_ID); + assertNotNull(state); + QuantityType qt = state.as(QuantityType.class); + assertNotNull(qt); + assertEquals(HISTORIC_END, qt.doubleValue(), 0.01); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); } @Test public void testSumSinceDecimalType() { - DecimalType sum = PersistenceExtensions.sumSince(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + State sum = PersistenceExtensions.sumSince(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(sum); - assertEquals(0.0, sum.doubleValue(), 0.001); + DecimalType dt = sum.as(DecimalType.class); + assertNotNull(dt); + assertEquals(IntStream.rangeClosed(HISTORIC_START, HISTORIC_END).sum(), dt.doubleValue(), 0.001); sum = PersistenceExtensions.sumSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(sum); - assertEquals(IntStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012).sum(), sum.doubleValue(), 0.001); + dt = sum.as(DecimalType.class); + assertNotNull(dt); + assertEquals(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END).sum(), dt.doubleValue(), 0.001); // default persistence service sum = PersistenceExtensions.sumSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(sum); + } + + @Test + public void testSumTillDecimalType() { + State sum = PersistenceExtensions.sumTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(sum); - assertEquals(0.0, sum.doubleValue(), 0.001); + DecimalType dt = sum.as(DecimalType.class); + assertNotNull(dt); + assertEquals(IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3).sum(), dt.doubleValue(), 0.001); + + // default persistence service + sum = PersistenceExtensions.sumTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(sum); } @Test public void testSumBetweenDecimalType() { - DecimalType sum = PersistenceExtensions.sumBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(sum, is(notNullValue())); - assertThat(sum.doubleValue(), is(closeTo(14056.0, 0.1))); + State sum = PersistenceExtensions.sumBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(sum); + DecimalType dt = sum.as(DecimalType.class); + assertNotNull(dt); + assertEquals(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2).sum(), + dt.doubleValue(), 0.001); + + sum = PersistenceExtensions.sumBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(sum); + dt = sum.as(DecimalType.class); + assertNotNull(dt); + assertEquals(IntStream.rangeClosed(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4).sum(), + dt.doubleValue(), 0.001); sum = PersistenceExtensions.sumBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(sum); + dt = sum.as(DecimalType.class); + assertNotNull(dt); + assertEquals( + IntStream.concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3)).sum(), + dt.doubleValue(), 0.001); - assertThat(sum, is(notNullValue())); - assertThat(sum.doubleValue(), is(closeTo(0.0, 0.1))); + // default persistence service + sum = PersistenceExtensions.sumBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(sum); } @Test public void testSumSinceQuantityType() { - DecimalType sum = PersistenceExtensions.sumSince(quantityItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + State sum = PersistenceExtensions.sumSince(quantityItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(sum); + QuantityType qt = sum.as(QuantityType.class); + assertNotNull(qt); + assertEquals(IntStream.rangeClosed(HISTORIC_START, HISTORIC_END).sum(), qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + sum = PersistenceExtensions.sumSince(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(sum); - assertEquals(0.0, sum.doubleValue(), 0.001); + qt = sum.as(QuantityType.class); + assertNotNull(qt); + assertEquals(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END).sum(), qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + // default persistence service sum = PersistenceExtensions.sumSince(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(sum); + } + + @Test + public void testSumTillQuantityType() { + State sum = PersistenceExtensions.sumTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(sum); - assertEquals(IntStream.of(2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012).sum(), sum.doubleValue(), 0.001); + QuantityType qt = sum.as(QuantityType.class); + assertNotNull(qt); + assertEquals(IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3).sum(), qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); // default persistence service sum = PersistenceExtensions.sumSince(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(sum); + } + + @Test + public void testSumBetweenQuantityType() { + State sum = PersistenceExtensions.sumBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(sum); + QuantityType qt = sum.as(QuantityType.class); + assertNotNull(qt); + assertEquals(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_INTERMEDIATE_VALUE_2).sum(), + qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + sum = PersistenceExtensions.sumBetween(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(sum); - assertEquals(0.0, sum.doubleValue(), 0.001); + qt = sum.as(QuantityType.class); + assertNotNull(qt); + assertEquals(IntStream.rangeClosed(FUTURE_INTERMEDIATE_VALUE_3, FUTURE_INTERMEDIATE_VALUE_4).sum(), + qt.doubleValue(), 0.001); + + sum = PersistenceExtensions.sumBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(sum); + qt = sum.as(QuantityType.class); + assertNotNull(qt); + assertEquals( + IntStream.concat(IntStream.rangeClosed(HISTORIC_INTERMEDIATE_VALUE_1, HISTORIC_END), + IntStream.rangeClosed(FUTURE_START, FUTURE_INTERMEDIATE_VALUE_3)).sum(), + qt.doubleValue(), 0.001); + + // default persistence service + sum = PersistenceExtensions.sumBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + + assertNull(sum); } @Test public void testLastUpdate() { - numberItem.setState(new DecimalType(2005)); - ZonedDateTime lastUpdate = PersistenceExtensions.lastUpdate(numberItem, TestPersistenceService.ID); + ZonedDateTime lastUpdate = PersistenceExtensions.lastUpdate(numberItem, SERVICE_ID); assertNotNull(lastUpdate); - assertEquals(ZonedDateTime.of(2012, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), lastUpdate); + assertEquals(ZonedDateTime.of(HISTORIC_END, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), lastUpdate); // default persistence service lastUpdate = PersistenceExtensions.lastUpdate(numberItem); assertNull(lastUpdate); } + @Test + public void testNextUpdate() { + ZonedDateTime nextUpdate = PersistenceExtensions.nextUpdate(numberItem, SERVICE_ID); + assertNotNull(nextUpdate); + assertEquals(ZonedDateTime.of(FUTURE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), nextUpdate); + + // default persistence service + nextUpdate = PersistenceExtensions.lastUpdate(numberItem); + assertNull(nextUpdate); + } + @Test public void testDeltaSince() { - numberItem.setState(new DecimalType(2012)); - DecimalType delta = PersistenceExtensions.deltaSince(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + State delta = PersistenceExtensions.deltaSince(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNull(delta); delta = PersistenceExtensions.deltaSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + DecimalType dt = delta.as(DecimalType.class); + assertNotNull(dt); + DecimalType dtState = numberItem.getState().as(DecimalType.class); + assertNotNull(dtState); + assertEquals(dtState.doubleValue() - HISTORIC_INTERMEDIATE_VALUE_1, dt.doubleValue(), 0.001); + + delta = PersistenceExtensions.deltaSince(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(delta); - assertEquals(7, delta.doubleValue(), 0.001); + QuantityType qt = delta.as(QuantityType.class); + assertNotNull(qt); + QuantityType qtState = quantityItem.getState().as(QuantityType.class); + assertNotNull(qtState); + assertEquals(qtState.doubleValue() - HISTORIC_INTERMEDIATE_VALUE_1, qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); - numberItem.setState(new QuantityType<>(2012, SIUnits.CELSIUS)); + // default persistence service delta = PersistenceExtensions.deltaSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(delta); + } + + @Test + public void testDeltaTill() { + State delta = PersistenceExtensions.deltaTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + DecimalType dt = delta.as(DecimalType.class); + assertNotNull(dt); + DecimalType dtState = numberItem.getState().as(DecimalType.class); + assertNotNull(dtState); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - dtState.doubleValue(), dt.doubleValue(), 0.001); + + delta = PersistenceExtensions.deltaTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertNotNull(delta); - assertEquals(7, delta.doubleValue(), 0.001); + QuantityType qt = delta.as(QuantityType.class); + assertNotNull(qt); + QuantityType qtState = quantityItem.getState().as(QuantityType.class); + assertNotNull(qtState); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - dtState.doubleValue(), qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); // default persistence service - delta = PersistenceExtensions.deltaSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + delta = PersistenceExtensions.deltaTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); assertNull(delta); } @Test public void testDeltaBetween() { - DecimalType delta = PersistenceExtensions.deltaBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(delta, is(notNullValue())); - assertThat(delta.doubleValue(), is(closeTo(6, 0.001))); + State delta = PersistenceExtensions.deltaBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + DecimalType dt = delta.as(DecimalType.class); + assertNotNull(dt); + assertEquals(HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1, dt.doubleValue(), 0.001); + + delta = PersistenceExtensions.deltaBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + QuantityType qt = delta.as(QuantityType.class); + assertNotNull(qt); + assertEquals(HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1, qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + delta = PersistenceExtensions.deltaBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + dt = delta.as(DecimalType.class); + assertNotNull(dt); + assertEquals(FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3, dt.doubleValue(), 0.001); + + delta = PersistenceExtensions.deltaBetween(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + qt = delta.as(QuantityType.class); + assertNotNull(qt); + assertEquals(FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3, qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); + + delta = PersistenceExtensions.deltaBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + dt = delta.as(DecimalType.class); + assertNotNull(dt); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - HISTORIC_INTERMEDIATE_VALUE_1, dt.doubleValue(), 0.001); delta = PersistenceExtensions.deltaBetween(quantityItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(delta, is(notNullValue())); - assertThat(delta.doubleValue(), is(closeTo(6, 0.001))); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(delta); + qt = delta.as(QuantityType.class); + assertNotNull(qt); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - HISTORIC_INTERMEDIATE_VALUE_1, qt.doubleValue(), 0.001); + assertEquals(SIUnits.CELSIUS, qt.getUnit()); // default persistence service delta = PersistenceExtensions.deltaBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(delta, is(nullValue())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(delta); } @Test - public void testEvolutionRate() { - numberItem.setState(new DecimalType(2012)); - DecimalType rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + public void testEvolutionRateSince() { + DecimalType rate = PersistenceExtensions.evolutionRateSince(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertThat(rate, is(nullValue())); - rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(rate, is(notNullValue())); + rate = PersistenceExtensions.evolutionRateSince(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); // ((now - then) / then) * 100 - assertThat(rate.doubleValue(), is(closeTo(0.349, 0.001))); - - rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(rate, is(notNullValue())); + assertThat(rate.doubleValue(), + is(closeTo( + 100.0 * (STATE.doubleValue() - HISTORIC_INTERMEDIATE_VALUE_1) / HISTORIC_INTERMEDIATE_VALUE_1, + 0.001))); + + rate = PersistenceExtensions.evolutionRateSince(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); // ((now - then) / then) * 100 - assertThat(rate.doubleValue(), is(closeTo(0.299, 0.001))); + assertThat(rate.doubleValue(), + is(closeTo( + 100.0 * (STATE.doubleValue() - HISTORIC_INTERMEDIATE_VALUE_1) / HISTORIC_INTERMEDIATE_VALUE_1, + 0.001))); - numberItem.setState(new QuantityType<>(2012, SIUnits.CELSIUS)); - rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(rate, is(notNullValue())); - // ((now - then) / then) * 100 - assertThat(rate.doubleValue(), is(closeTo(0.349, 0.001))); + // default persistence service + rate = PersistenceExtensions.evolutionRateSince(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(rate); + } - rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertThat(rate, is(notNullValue())); - // ((now - then) / then) * 100 - assertThat(rate.doubleValue(), is(closeTo(0.299, 0.001))); + @Test + public void testEvolutionRateTill() { + DecimalType rate = PersistenceExtensions.evolutionRateTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((then - now) / now) * 100 + assertThat(rate.doubleValue(), + is(closeTo(100.0 * (FUTURE_INTERMEDIATE_VALUE_3 - STATE.doubleValue()) / STATE.doubleValue(), 0.001))); + + rate = PersistenceExtensions.evolutionRateTill(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((then - now) / now) * 100 + assertThat(rate.doubleValue(), + is(closeTo(100.0 * (FUTURE_INTERMEDIATE_VALUE_3 - STATE.doubleValue()) / STATE.doubleValue(), 0.001))); // default persistence service - rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(rate, is(nullValue())); + rate = PersistenceExtensions.evolutionRateTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(rate); + } - rate = PersistenceExtensions.evolutionRate(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertThat(rate, is(nullValue())); + @Test + public void testEvolutionRateBetween() { + DecimalType rate = PersistenceExtensions.evolutionRateBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((now - then) / then) * 100 + assertThat(rate.doubleValue(), is(closeTo( + 100.0 * (HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1) / HISTORIC_INTERMEDIATE_VALUE_1, + 0.001))); + + rate = PersistenceExtensions.evolutionRateBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((now - then) / then) * 100 + assertThat(rate.doubleValue(), is(closeTo( + 100.0 * (HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1) / HISTORIC_INTERMEDIATE_VALUE_1, + 0.001))); + + rate = PersistenceExtensions.evolutionRateBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((now - then) / then) * 100 + assertThat(rate.doubleValue(), is(closeTo( + 100.0 * (FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3) / FUTURE_INTERMEDIATE_VALUE_3, + 0.001))); + + rate = PersistenceExtensions.evolutionRateBetween(quantityItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((now - then) / then) * 100 + assertThat(rate.doubleValue(), is(closeTo( + 100.0 * (FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3) / FUTURE_INTERMEDIATE_VALUE_3, + 0.001))); + + rate = PersistenceExtensions.evolutionRateBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((now - then) / then) * 100 + assertThat(rate.doubleValue(), is(closeTo( + 100.0 * (FUTURE_INTERMEDIATE_VALUE_3 - HISTORIC_INTERMEDIATE_VALUE_1) / HISTORIC_INTERMEDIATE_VALUE_1, + 0.001))); + + rate = PersistenceExtensions.evolutionRateBetween(quantityItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertNotNull(rate); + // ((now - then) / then) * 100 + assertThat(rate.doubleValue(), is(closeTo( + 100.0 * (FUTURE_INTERMEDIATE_VALUE_3 - HISTORIC_INTERMEDIATE_VALUE_1) / HISTORIC_INTERMEDIATE_VALUE_1, + 0.001))); + + // default persistence service + rate = PersistenceExtensions.evolutionRateBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(rate); } @Test public void testPreviousStateDecimalTypeNoSkip() { - HistoricItem prevStateItem = PersistenceExtensions.previousState(numberItem, false, TestPersistenceService.ID); + HistoricItem prevStateItem = PersistenceExtensions.previousState(numberItem, false, SERVICE_ID); assertNotNull(prevStateItem); assertThat(prevStateItem.getState(), is(instanceOf(DecimalType.class))); - assertEquals("2012", prevStateItem.getState().toString()); + assertEquals(value(HISTORIC_END), prevStateItem.getState()); numberItem.setState(new DecimalType(4321)); - prevStateItem = PersistenceExtensions.previousState(numberItem, false, TestPersistenceService.ID); + prevStateItem = PersistenceExtensions.previousState(numberItem, false, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2012", prevStateItem.getState().toString()); + assertEquals(value(HISTORIC_END), prevStateItem.getState()); - numberItem.setState(new DecimalType(2012)); - prevStateItem = PersistenceExtensions.previousState(numberItem, false, TestPersistenceService.ID); + numberItem.setState(new DecimalType(HISTORIC_END)); + prevStateItem = PersistenceExtensions.previousState(numberItem, false, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2012", prevStateItem.getState().toString()); + assertEquals(value(HISTORIC_END), prevStateItem.getState()); numberItem.setState(new DecimalType(3025)); - prevStateItem = PersistenceExtensions.previousState(numberItem, false, TestPersistenceService.ID); + prevStateItem = PersistenceExtensions.previousState(numberItem, false, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2012", prevStateItem.getState().toString()); + assertEquals(value(HISTORIC_END), prevStateItem.getState()); // default persistence service prevStateItem = PersistenceExtensions.previousState(numberItem, false); @@ -874,26 +1855,25 @@ public void testPreviousStateDecimalTypeNoSkip() { @Test public void testPreviousStateQuantityTypeNoSkip() { - HistoricItem prevStateItem = PersistenceExtensions.previousState(quantityItem, false, - TestPersistenceService.ID); + HistoricItem prevStateItem = PersistenceExtensions.previousState(quantityItem, false, SERVICE_ID); assertNotNull(prevStateItem); assertThat(prevStateItem.getState(), is(instanceOf(QuantityType.class))); - assertEquals("2012 °C", prevStateItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), prevStateItem.getState()); quantityItem.setState(QuantityType.valueOf(4321, SIUnits.CELSIUS)); - prevStateItem = PersistenceExtensions.previousState(quantityItem, false, TestPersistenceService.ID); + prevStateItem = PersistenceExtensions.previousState(quantityItem, false, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2012 °C", prevStateItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), prevStateItem.getState()); - quantityItem.setState(QuantityType.valueOf(2012, SIUnits.CELSIUS)); - prevStateItem = PersistenceExtensions.previousState(quantityItem, false, TestPersistenceService.ID); + quantityItem.setState(QuantityType.valueOf(HISTORIC_END, SIUnits.CELSIUS)); + prevStateItem = PersistenceExtensions.previousState(quantityItem, false, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2012 °C", prevStateItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), prevStateItem.getState()); quantityItem.setState(QuantityType.valueOf(3025, SIUnits.CELSIUS)); - prevStateItem = PersistenceExtensions.previousState(quantityItem, false, TestPersistenceService.ID); + prevStateItem = PersistenceExtensions.previousState(quantityItem, false, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2012 °C", prevStateItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END), SIUnits.CELSIUS), prevStateItem.getState()); // default persistence service prevStateItem = PersistenceExtensions.previousState(quantityItem, false); @@ -902,10 +1882,10 @@ public void testPreviousStateQuantityTypeNoSkip() { @Test public void testPreviousStateDecimalTypeSkip() { - numberItem.setState(new DecimalType(2012)); - HistoricItem prevStateItem = PersistenceExtensions.previousState(numberItem, true, TestPersistenceService.ID); + numberItem.setState(new DecimalType(HISTORIC_END)); + HistoricItem prevStateItem = PersistenceExtensions.previousState(numberItem, true, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2011", prevStateItem.getState().toString()); + assertEquals(value(HISTORIC_END - 1), prevStateItem.getState()); // default persistence service prevStateItem = PersistenceExtensions.previousState(numberItem, true); @@ -914,162 +1894,353 @@ public void testPreviousStateDecimalTypeSkip() { @Test public void testPreviousStateQuantityTypeSkip() { - quantityItem.setState(QuantityType.valueOf(2012, SIUnits.CELSIUS)); - HistoricItem prevStateItem = PersistenceExtensions.previousState(quantityItem, true, TestPersistenceService.ID); + quantityItem.setState(QuantityType.valueOf(HISTORIC_END, SIUnits.CELSIUS)); + HistoricItem prevStateItem = PersistenceExtensions.previousState(quantityItem, true, SERVICE_ID); assertNotNull(prevStateItem); - assertEquals("2011 °C", prevStateItem.getState().toString()); + assertEquals(new QuantityType<>(value(HISTORIC_END - 1), SIUnits.CELSIUS), prevStateItem.getState()); // default persistence service prevStateItem = PersistenceExtensions.previousState(quantityItem, true); assertNull(prevStateItem); } + @Test + public void testNextStateDecimalTypeNoSkip() { + HistoricItem nextStateItem = PersistenceExtensions.nextState(numberItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertThat(nextStateItem.getState(), is(instanceOf(DecimalType.class))); + assertEquals(value(FUTURE_START), nextStateItem.getState()); + + numberItem.setState(new DecimalType(4321)); + nextStateItem = PersistenceExtensions.nextState(numberItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(value(FUTURE_START), nextStateItem.getState()); + + numberItem.setState(new DecimalType(FUTURE_START)); + nextStateItem = PersistenceExtensions.nextState(numberItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(value(FUTURE_START), nextStateItem.getState()); + + numberItem.setState(new DecimalType(3025)); + nextStateItem = PersistenceExtensions.nextState(numberItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(value(FUTURE_START), nextStateItem.getState()); + + // default persistence service + nextStateItem = PersistenceExtensions.nextState(numberItem, false); + assertNull(nextStateItem); + } + + @Test + public void testNextStateQuantityTypeNoSkip() { + HistoricItem nextStateItem = PersistenceExtensions.nextState(quantityItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertThat(nextStateItem.getState(), is(instanceOf(QuantityType.class))); + assertEquals(new QuantityType<>(value(FUTURE_START), SIUnits.CELSIUS), nextStateItem.getState()); + + quantityItem.setState(QuantityType.valueOf(4321, SIUnits.CELSIUS)); + nextStateItem = PersistenceExtensions.nextState(quantityItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(new QuantityType<>(value(FUTURE_START), SIUnits.CELSIUS), nextStateItem.getState()); + + quantityItem.setState(QuantityType.valueOf(FUTURE_START, SIUnits.CELSIUS)); + nextStateItem = PersistenceExtensions.nextState(quantityItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(new QuantityType<>(value(FUTURE_START), SIUnits.CELSIUS), nextStateItem.getState()); + + quantityItem.setState(QuantityType.valueOf(3025, SIUnits.CELSIUS)); + nextStateItem = PersistenceExtensions.nextState(quantityItem, false, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(new QuantityType<>(value(FUTURE_START), SIUnits.CELSIUS), nextStateItem.getState()); + + // default persistence service + nextStateItem = PersistenceExtensions.nextState(quantityItem, false); + assertNull(nextStateItem); + } + + @Test + public void testNextStateDecimalTypeSkip() { + numberItem.setState(new DecimalType(FUTURE_START)); + HistoricItem nextStateItem = PersistenceExtensions.nextState(numberItem, true, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(value(FUTURE_START + 1), nextStateItem.getState()); + + // default persistence service + nextStateItem = PersistenceExtensions.nextState(numberItem, true); + assertNull(nextStateItem); + } + + @Test + public void testNextStateQuantityTypeSkip() { + quantityItem.setState(QuantityType.valueOf(FUTURE_START, SIUnits.CELSIUS)); + HistoricItem nextStateItem = PersistenceExtensions.nextState(quantityItem, true, SERVICE_ID); + assertNotNull(nextStateItem); + assertEquals(new QuantityType<>(value(FUTURE_START + 1), SIUnits.CELSIUS), nextStateItem.getState()); + + // default persistence service + nextStateItem = PersistenceExtensions.nextState(quantityItem, true); + assertNull(nextStateItem); + } + @Test public void testChangedSince() { - boolean changed = PersistenceExtensions.changedSince(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertFalse(changed); + Boolean changed = PersistenceExtensions.changedSince(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, true); changed = PersistenceExtensions.changedSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertTrue(changed); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, true); // default persistence service changed = PersistenceExtensions.changedSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertFalse(changed); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(changed); + } + + @Test + public void testChangedTill() { + Boolean changed = PersistenceExtensions.changedTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, true); + + // default persistence service + changed = PersistenceExtensions.changedTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(changed); } @Test public void testChangedBetween() { - boolean changed = PersistenceExtensions.changedBetween(numberItem, - ZonedDateTime.of(2019, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2019, 5, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertFalse(changed); + Boolean changed = PersistenceExtensions.changedBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 5, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, false); + + changed = PersistenceExtensions.changedBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, true); + + changed = PersistenceExtensions.changedBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 5, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, false); changed = PersistenceExtensions.changedBetween(numberItem, - ZonedDateTime.of(2006, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2008, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertTrue(changed); + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(changed, true); // default persistence service changed = PersistenceExtensions.changedBetween(numberItem, - ZonedDateTime.of(2004, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertFalse(changed); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(changed); } @Test public void testUpdatedSince() { - boolean updated = PersistenceExtensions.updatedSince(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertFalse(updated); + Boolean updated = PersistenceExtensions.updatedSince(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, true); updated = PersistenceExtensions.updatedSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertTrue(updated); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, true); // default persistence service updated = PersistenceExtensions.updatedSince(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertFalse(updated); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(updated); + } + + @Test + public void testUpdatedTill() { + Boolean updated = PersistenceExtensions.updatedTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, true); + + // default persistence service + updated = PersistenceExtensions.updatedTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(updated); } @Test public void testUpdatedBetween() { - boolean updated = PersistenceExtensions.updatedBetween(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertFalse(updated); + Boolean updated = PersistenceExtensions.updatedBetween(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, true); updated = PersistenceExtensions.updatedBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertTrue(updated); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, true); updated = PersistenceExtensions.updatedBetween(numberItem, - ZonedDateTime.of(2019, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2021, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertTrue(updated); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_NOVALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_NOVALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + SERVICE_ID); + assertEquals(updated, false); - // default persistence service updated = PersistenceExtensions.updatedBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertFalse(updated); - } - - @Test - public void testCountBetween() { - long counts = PersistenceExtensions.countBetween(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(0, counts); + ZonedDateTime.of(FUTURE_INTERMEDIATE_NOVALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_NOVALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, false); - counts = PersistenceExtensions.countBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(7, counts); + updated = PersistenceExtensions.updatedBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(updated, true); - counts = PersistenceExtensions.countBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertEquals(0, counts); + // default persistence service + updated = PersistenceExtensions.updatedBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(updated); } @Test public void testCountSince() { - long counts = PersistenceExtensions.countSince(numberItem, - ZonedDateTime.of(1980, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(33, counts); + Long counts = PersistenceExtensions.countSince(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1, counts); counts = PersistenceExtensions.countSince(numberItem, - ZonedDateTime.of(2007, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(6, counts); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_2 + 1, counts); counts = PersistenceExtensions.countSince(numberItem, - ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_NOVALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + SERVICE_ID); assertEquals(0, counts); + // default persistence service counts = PersistenceExtensions.countSince(numberItem, - ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(counts); + } + + @Test + public void testCountTill() { + Long counts = PersistenceExtensions.countTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_NOVALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertEquals(0, counts); + + counts = PersistenceExtensions.countTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1, counts); + + counts = PersistenceExtensions.countTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_START + 1, counts); + + // default persistence service + counts = PersistenceExtensions.countTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(counts); + } + + @Test + public void testCountBetween() { + Long counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_INTERMEDIATE_VALUE_1 - HISTORIC_START + 1, counts); + + counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1 + 1, counts); + + counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3 + 1, counts); + + counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1 + HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1, + counts); + + // default persistence service + counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(counts); } @Test public void testCountStateChangesSince() { - long counts = PersistenceExtensions.countStateChangesSince(numberItem, - ZonedDateTime.of(1980, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(32, counts); + Long counts = PersistenceExtensions.countStateChangesSince(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1, counts); counts = PersistenceExtensions.countStateChangesSince(numberItem, - ZonedDateTime.of(2007, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(5, counts); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_2, counts); counts = PersistenceExtensions.countStateChangesSince(numberItem, - ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_NOVALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + SERVICE_ID); assertEquals(0, counts); + // default persistence service counts = PersistenceExtensions.countStateChangesSince(numberItem, - ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(counts); + } + + @Test + public void testCountStateChangesTill() { + Long counts = PersistenceExtensions.countStateChangesTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_NOVALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); assertEquals(0, counts); + + counts = PersistenceExtensions.countStateChangesTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START, counts); + + counts = PersistenceExtensions.countStateChangesTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_START, counts); + + // default persistence service + counts = PersistenceExtensions.countStateChangesTill(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(counts); } @Test public void testCountStateChangesBetween() { - long counts = PersistenceExtensions.countStateChangesBetween(numberItem, - ZonedDateTime.of(1940, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(0, counts); + Long counts = PersistenceExtensions.countStateChangesBetween(numberItem, + ZonedDateTime.of(BEFORE_START, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_INTERMEDIATE_VALUE_1 - HISTORIC_START, counts); counts = PersistenceExtensions.countStateChangesBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), TestPersistenceService.ID); - assertEquals(6, counts); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(HISTORIC_INTERMEDIATE_VALUE_2 - HISTORIC_INTERMEDIATE_VALUE_1, counts); + + counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_4, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_4 - FUTURE_INTERMEDIATE_VALUE_3 + 1, counts); + counts = PersistenceExtensions.countBetween(numberItem, + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(FUTURE_INTERMEDIATE_VALUE_3, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), SERVICE_ID); + assertEquals(FUTURE_INTERMEDIATE_VALUE_3 - FUTURE_START + 1 + HISTORIC_END - HISTORIC_INTERMEDIATE_VALUE_1 + 1, + counts); + + // default persistence service counts = PersistenceExtensions.countStateChangesBetween(numberItem, - ZonedDateTime.of(2005, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), - ZonedDateTime.of(2011, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); - assertEquals(0, counts); + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(HISTORIC_INTERMEDIATE_VALUE_2, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())); + assertNull(counts); } } diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java index 6c6f2f3fd41..250d42604be 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestCachedValuesPersistenceService.java @@ -25,8 +25,8 @@ import org.openhab.core.persistence.FilterCriteria; import org.openhab.core.persistence.FilterCriteria.Ordering; import org.openhab.core.persistence.HistoricItem; +import org.openhab.core.persistence.ModifiablePersistenceService; import org.openhab.core.persistence.PersistenceItemInfo; -import org.openhab.core.persistence.QueryablePersistenceService; import org.openhab.core.persistence.strategy.PersistenceStrategy; import org.openhab.core.types.State; @@ -36,7 +36,7 @@ * @author Florian Binder - Initial contribution */ @NonNullByDefault -public class TestCachedValuesPersistenceService implements QueryablePersistenceService { +public class TestCachedValuesPersistenceService implements ModifiablePersistenceService { public static final String ID = "testCachedHistoricItems"; @@ -62,6 +62,19 @@ public void store(Item item) { public void store(Item item, @Nullable String alias) { } + @Override + public void store(Item item, ZonedDateTime date, State state) { + } + + @Override + public void store(Item item, ZonedDateTime date, State state, @Nullable String alias) { + } + + @Override + public boolean remove(FilterCriteria filter) throws IllegalArgumentException { + return true; + } + @Override public Iterable query(FilterCriteria filter) { Stream stream = historicItems.stream(); @@ -70,16 +83,19 @@ public Iterable query(FilterCriteria filter) { throw new UnsupportedOperationException("state filtering is not supported yet"); } - if (filter.getItemName() != null) { - stream = stream.filter(hi -> filter.getItemName().equals(hi.getName())); + String itemName = filter.getItemName(); + if (itemName != null) { + stream = stream.filter(hi -> itemName.equals(hi.getName())); } - if (filter.getBeginDate() != null) { - stream = stream.filter(hi -> !filter.getBeginDate().isAfter(hi.getTimestamp())); + ZonedDateTime beginDate = filter.getBeginDate(); + if (beginDate != null) { + stream = stream.filter(hi -> !beginDate.isAfter(hi.getTimestamp())); } - if (filter.getEndDate() != null) { - stream = stream.filter(hi -> !filter.getEndDate().isBefore(hi.getTimestamp())); + ZonedDateTime endDate = filter.getEndDate(); + if (endDate != null) { + stream = stream.filter(hi -> !endDate.isBefore(hi.getTimestamp())); } if (filter.getOrdering() == Ordering.ASCENDING) { diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java index 316d2547cf8..b17abef20d4 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/extensions/TestPersistenceService.java @@ -12,6 +12,7 @@ */ package org.openhab.core.persistence.extensions; +import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -21,6 +22,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; +import java.util.stream.LongStream; import javax.measure.Unit; @@ -44,11 +46,44 @@ * A simple persistence service used for unit tests * * @author Kai Kreuzer - Initial contribution + * @author Mark Herwege - Allow future values */ @NonNullByDefault public class TestPersistenceService implements QueryablePersistenceService { - public static final String ID = "test"; + public static final String SERVICE_ID = "test"; + + static final int SWITCH_START = -15; + static final int SWITCH_ON_1 = -15; + static final int SWITCH_ON_INTERMEDIATE_1 = -12; + static final int SWITCH_OFF_1 = -10; + static final int SWITCH_OFF_INTERMEDIATE_1 = -6; + static final int SWITCH_ON_2 = -5; + static final int SWITCH_ON_INTERMEDIATE_21 = -1; + static final int SWITCH_ON_INTERMEDIATE_22 = +1; + static final int SWITCH_OFF_2 = +5; + static final int SWITCH_OFF_INTERMEDIATE_2 = +7; + static final int SWITCH_ON_3 = +10; + static final int SWITCH_ON_INTERMEDIATE_3 = +12; + static final int SWITCH_OFF_3 = +15; + static final int SWITCH_END = +15; + static final OnOffType SWITCH_STATE = OnOffType.ON; + + static final int BEFORE_START = 1940; + static final int HISTORIC_START = 1950; + static final int HISTORIC_INTERMEDIATE_VALUE_1 = 2005; + static final int HISTORIC_INTERMEDIATE_VALUE_2 = 2011; + static final int HISTORIC_END = 2012; + static final int HISTORIC_INTERMEDIATE_NOVALUE_3 = 2019; + static final int HISTORIC_INTERMEDIATE_NOVALUE_4 = 2021; + static final int FUTURE_INTERMEDIATE_NOVALUE_1 = 2051; + static final int FUTURE_INTERMEDIATE_NOVALUE_2 = 2056; + static final int FUTURE_START = 2060; + static final int FUTURE_INTERMEDIATE_VALUE_3 = 2070; + static final int FUTURE_INTERMEDIATE_VALUE_4 = 2077; + static final int FUTURE_END = 2100; + static final int AFTER_END = 2110; + static final DecimalType STATE = new DecimalType(HISTORIC_END); private final ItemRegistry itemRegistry; @@ -58,7 +93,7 @@ public TestPersistenceService(ItemRegistry itemRegistry) { @Override public String getId() { - return ID; + return SERVICE_ID; } @Override @@ -72,18 +107,17 @@ public void store(Item item, @Nullable String alias) { @Override public Iterable query(FilterCriteria filter) { if (PersistenceExtensionsTest.TEST_SWITCH.equals(filter.getItemName())) { - ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES), - nowMinusFifteenHours = now.minusHours(15), - beginDate = filter.getBeginDate() != null ? filter.getBeginDate() : nowMinusFifteenHours, - endDate = filter.getEndDate() != null ? filter.getEndDate() : now; - if (endDate.isBefore(beginDate)) { - return List.of(); - } - - List results = new ArrayList<>(16); - for (int i = 0; i <= 15; i++) { - final int hours = i; - final ZonedDateTime theDate = nowMinusFifteenHours.plusHours(hours); + ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.MINUTES); + ZonedDateTime nowMinusHours = now.plusHours(SWITCH_START); + ZonedDateTime endDate = filter.getEndDate(); + endDate = endDate != null ? endDate : now; + ZonedDateTime beginDate = filter.getBeginDate(); + beginDate = beginDate != null ? beginDate : endDate.isAfter(now) ? now : endDate.minusHours(1); + + List results = new ArrayList<>(31); + for (int i = SWITCH_START; i <= SWITCH_END; i++) { + final int hour = i; + final ZonedDateTime theDate = nowMinusHours.plusHours(i - SWITCH_START); if (!theDate.isBefore(beginDate) && !theDate.isAfter(endDate)) { results.add(new HistoricItem() { @Override @@ -93,7 +127,8 @@ public ZonedDateTime getTimestamp() { @Override public State getState() { - return OnOffType.from(hours < 5 || hours > 10); + return OnOffType.from(hour < SWITCH_OFF_1 || (hour >= SWITCH_ON_2 && hour < SWITCH_OFF_2) + || hour >= SWITCH_ON_3); } @Override @@ -108,22 +143,27 @@ public String getName() { } return results; } else { - int startValue = 1950; - int endValue = 2012; + int startValue = HISTORIC_START; + int endValue = FUTURE_END; - if (filter.getBeginDate() != null) { - startValue = filter.getBeginDate().getYear(); + ZonedDateTime beginDate = filter.getBeginDate(); + if (beginDate != null && beginDate.getYear() >= startValue) { + startValue = beginDate.getYear(); } - if (filter.getEndDate() != null) { - endValue = filter.getEndDate().getYear(); + ZonedDateTime endDate = filter.getEndDate(); + if (endDate != null && endDate.getYear() <= endValue) { + endValue = endDate.getYear(); } - if (endValue <= startValue || startValue < 1950) { + if (endValue <= startValue) { return List.of(); } List results = new ArrayList<>(endValue - startValue); for (int i = startValue; i <= endValue; i++) { + if (i > HISTORIC_END && i < FUTURE_START) { + continue; + } final int year = i; results.add(new HistoricItem() { @Override @@ -165,4 +205,46 @@ public String getLabel(@Nullable Locale locale) { public List getDefaultStrategies() { return List.of(); } + + static OnOffType switchValue(int hour) { + return (hour >= SWITCH_ON_1 && hour < SWITCH_OFF_1) || (hour >= SWITCH_ON_2 && hour < SWITCH_OFF_2) + || (hour >= SWITCH_ON_3 && hour < SWITCH_OFF_3) ? OnOffType.ON : OnOffType.OFF; + } + + static DecimalType value(long year) { + if (year < HISTORIC_START) { + return DecimalType.ZERO; + } else if (year <= HISTORIC_END) { + return new DecimalType(year); + } else if (year < FUTURE_START) { + return new DecimalType(HISTORIC_END); + } else if (year <= FUTURE_END) { + return new DecimalType(year); + } else { + return new DecimalType(FUTURE_END); + } + } + + static double average(@Nullable Integer beginYear, @Nullable Integer endYear) { + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime beginDate = beginYear != null + ? ZonedDateTime.of(beginYear >= HISTORIC_START ? beginYear : HISTORIC_START, 1, 1, 0, 0, 0, 0, + ZoneId.systemDefault()) + : now; + ZonedDateTime endDate = endYear != null ? ZonedDateTime.of(endYear, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()) + : now; + int begin = beginYear != null ? beginYear : now.getYear() + 1; + int end = endYear != null ? endYear : now.getYear(); + long sum = LongStream.range(begin, end).map(y -> value(y).longValue() * Duration + .between(ZonedDateTime.of(Long.valueOf(y).intValue(), 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(Long.valueOf(y + 1).intValue(), 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())) + .toMillis()).sum(); + sum += beginYear == null ? value(now.getYear()).longValue() * Duration + .between(now, ZonedDateTime.of(now.getYear() + 1, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())).toMillis() + : 0; + sum += endYear == null ? value(now.getYear()).longValue() * Duration + .between(ZonedDateTime.of(now.getYear(), 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), now).toMillis() : 0; + long duration = Duration.between(beginDate, endDate).toMillis(); + return 1.0 * sum / duration; + } }