Skip to content

Commit baaaf7f

Browse files
[awattar] Refactor and add test coverage (openhab#17752)
* [aWATTar] push test coverage and improve code readability Signed-off-by: Thomas Leber <[email protected]>
1 parent 968cc56 commit baaaf7f

14 files changed

+362
-120
lines changed

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/AwattarBestPriceResult.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
*/
1313
package org.openhab.binding.awattar.internal;
1414

15+
import java.time.Instant;
16+
1517
import org.eclipse.jdt.annotation.NonNullByDefault;
1618

1719
/**
@@ -24,7 +26,7 @@ public abstract class AwattarBestPriceResult {
2426
private long start;
2527
private long end;
2628

27-
public AwattarBestPriceResult() {
29+
protected AwattarBestPriceResult() {
2830
}
2931

3032
public long getStart() {
@@ -47,7 +49,18 @@ public void updateEnd(long end) {
4749
}
4850
}
4951

50-
public abstract boolean isActive();
52+
/**
53+
* Returns true if the best price is active.
54+
*
55+
* @param pointInTime the current time
56+
* @return true if the best price is active, false otherwise
57+
*/
58+
public abstract boolean isActive(Instant pointInTime);
5159

60+
/**
61+
* Returns the hours of the best price.
62+
*
63+
* @return the hours of the best price as a string
64+
*/
5265
public abstract String getHours();
5366
}

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/AwattarConsecutiveBestPriceResult.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, int length,
7676
}
7777

7878
@Override
79-
public boolean isActive() {
80-
return contains(Instant.now().toEpochMilli());
79+
public boolean isActive(Instant pointInTime) {
80+
return contains(pointInTime.toEpochMilli());
8181
}
8282

8383
public boolean contains(long timestamp) {

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/AwattarNonConsecutiveBestPriceResult.java

+2-11
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult {
3333
private final List<AwattarPrice> members;
3434
private final ZoneId zoneId;
35-
private boolean sorted = true;
3635

3736
public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, boolean inverted,
3837
ZoneId zoneId) {
@@ -57,32 +56,24 @@ public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int lengt
5756
}
5857

5958
private void addMember(AwattarPrice member) {
60-
sorted = false;
6159
members.add(member);
6260
updateStart(member.timerange().start());
6361
updateEnd(member.timerange().end());
6462
}
6563

6664
@Override
67-
public boolean isActive() {
68-
return members.stream().anyMatch(x -> x.timerange().contains(Instant.now().toEpochMilli()));
65+
public boolean isActive(Instant pointInTime) {
66+
return members.stream().anyMatch(x -> x.timerange().contains(pointInTime.toEpochMilli()));
6967
}
7068

7169
@Override
7270
public String toString() {
7371
return String.format("NonConsecutiveBestpriceResult with %s", members.toString());
7472
}
7573

76-
private void sort() {
77-
if (!sorted) {
78-
members.sort(Comparator.comparingLong(p -> p.timerange().start()));
79-
}
80-
}
81-
8274
@Override
8375
public String getHours() {
8476
boolean second = false;
85-
sort();
8677
StringBuilder res = new StringBuilder();
8778

8879
for (AwattarPrice price : members) {

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/AwattarUtil.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import javax.measure.quantity.Time;
2121

2222
import org.eclipse.jdt.annotation.NonNullByDefault;
23-
import org.openhab.core.i18n.TimeZoneProvider;
2423
import org.openhab.core.library.types.QuantityType;
2524
import org.openhab.core.library.unit.Units;
2625

@@ -32,9 +31,9 @@
3231
@NonNullByDefault
3332
public class AwattarUtil {
3433

35-
public static long getMillisToNextMinute(int mod, TimeZoneProvider timeZoneProvider) {
34+
public static long getMillisToNextMinute(int mod, ZoneId zoneId) {
3635
long now = Instant.now().toEpochMilli();
37-
ZonedDateTime dt = ZonedDateTime.now(timeZoneProvider.getTimeZone()).truncatedTo(ChronoUnit.MINUTES);
36+
ZonedDateTime dt = ZonedDateTime.now(zoneId).truncatedTo(ChronoUnit.MINUTES);
3837
int min = dt.getMinute();
3938
int offset = min % mod;
4039
offset = offset == 0 ? mod : offset;

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/api/AwattarApi.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
import static org.eclipse.jetty.http.HttpMethod.GET;
1616
import static org.eclipse.jetty.http.HttpStatus.OK_200;
1717

18-
import java.time.LocalDate;
19-
import java.time.ZoneId;
2018
import java.time.ZonedDateTime;
19+
import java.time.temporal.ChronoUnit;
2120
import java.util.Comparator;
2221
import java.util.SortedSet;
2322
import java.util.TreeSet;
@@ -31,6 +30,7 @@
3130
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
3231
import org.openhab.binding.awattar.internal.AwattarPrice;
3332
import org.openhab.binding.awattar.internal.dto.AwattarApiData;
33+
import org.openhab.binding.awattar.internal.dto.AwattarTimeProvider;
3434
import org.openhab.binding.awattar.internal.dto.Datum;
3535
import org.openhab.binding.awattar.internal.handler.TimeRange;
3636
import org.slf4j.Logger;
@@ -58,7 +58,7 @@ public class AwattarApi {
5858
private double vatFactor;
5959
private double basePrice;
6060

61-
private ZoneId zone;
61+
private AwattarTimeProvider timeProvider;
6262

6363
private Gson gson;
6464

@@ -79,8 +79,8 @@ public AwattarApiException(String message) {
7979
* @param httpClient the HTTP client to use
8080
* @param zone the time zone to use
8181
*/
82-
public AwattarApi(HttpClient httpClient, ZoneId zone, AwattarBridgeConfiguration config) {
83-
this.zone = zone;
82+
public AwattarApi(HttpClient httpClient, AwattarTimeProvider timeProvider, AwattarBridgeConfiguration config) {
83+
this.timeProvider = timeProvider;
8484
this.httpClient = httpClient;
8585

8686
this.gson = new Gson();
@@ -112,7 +112,7 @@ public AwattarApi(HttpClient httpClient, ZoneId zone, AwattarBridgeConfiguration
112112
public SortedSet<AwattarPrice> getData() throws AwattarApiException {
113113
try {
114114
// we start one day in the past to cover ranges that already started yesterday
115-
ZonedDateTime zdt = LocalDate.now(zone).atStartOfDay(zone).minusDays(1);
115+
ZonedDateTime zdt = timeProvider.getZonedDateTimeNow().truncatedTo(ChronoUnit.DAYS).minusDays(1);
116116
long start = zdt.toInstant().toEpochMilli();
117117
// Starting from midnight yesterday we add three days so that the range covers
118118
// the whole next day.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2010-2025 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.awattar.internal.dto;
14+
15+
import java.time.Instant;
16+
import java.time.ZoneId;
17+
import java.time.ZonedDateTime;
18+
19+
import org.eclipse.jdt.annotation.NonNullByDefault;
20+
import org.openhab.core.i18n.TimeZoneProvider;
21+
22+
/**
23+
* The {@link AwattarTimeProvider} provides a time provider for aWATTar
24+
*
25+
* @author Thomas Leber - Initial contribution
26+
*/
27+
@NonNullByDefault
28+
public class AwattarTimeProvider {
29+
30+
private TimeZoneProvider timeZoneProvider;
31+
32+
public AwattarTimeProvider(TimeZoneProvider timeZoneProvider) {
33+
this.timeZoneProvider = timeZoneProvider;
34+
}
35+
36+
/**
37+
* Get the current zone id.
38+
*
39+
* @return the current zone id
40+
*/
41+
public ZoneId getZoneId() {
42+
return timeZoneProvider.getTimeZone();
43+
}
44+
45+
/**
46+
* Get the current instant.
47+
*
48+
* @return the current instant
49+
*/
50+
public Instant getInstantNow() {
51+
return Instant.now();
52+
}
53+
54+
/**
55+
* Get the current zoned date time.
56+
*
57+
* @return the current zoned date time
58+
*/
59+
public ZonedDateTime getZonedDateTimeNow() {
60+
return Instant.now().atZone(getZoneId());
61+
}
62+
}

bundles/org.openhab.binding.awattar/src/main/java/org/openhab/binding/awattar/internal/handler/AwattarBestPriceHandler.java

+38-20
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import org.openhab.binding.awattar.internal.AwattarConsecutiveBestPriceResult;
4040
import org.openhab.binding.awattar.internal.AwattarNonConsecutiveBestPriceResult;
4141
import org.openhab.binding.awattar.internal.AwattarPrice;
42-
import org.openhab.core.i18n.TimeZoneProvider;
42+
import org.openhab.binding.awattar.internal.dto.AwattarTimeProvider;
4343
import org.openhab.core.library.types.DateTimeType;
4444
import org.openhab.core.library.types.OnOffType;
4545
import org.openhab.core.library.types.QuantityType;
@@ -70,14 +70,13 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
7070
private static final int THING_REFRESH_INTERVAL = 60;
7171

7272
private final Logger logger = LoggerFactory.getLogger(AwattarBestPriceHandler.class);
73+
private final AwattarTimeProvider timeProvider;
7374

7475
private @Nullable ScheduledFuture<?> thingRefresher;
7576

76-
private final TimeZoneProvider timeZoneProvider;
77-
78-
public AwattarBestPriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
77+
public AwattarBestPriceHandler(Thing thing, AwattarTimeProvider timeProvider) {
7978
super(thing);
80-
this.timeZoneProvider = timeZoneProvider;
79+
this.timeProvider = timeProvider;
8180
}
8281

8382
@Override
@@ -97,7 +96,7 @@ public void initialize() {
9796
* here
9897
*/
9998
thingRefresher = scheduler.scheduleAtFixedRate(this::refreshChannels,
100-
getMillisToNextMinute(1, timeZoneProvider), THING_REFRESH_INTERVAL * 1000L,
99+
getMillisToNextMinute(1, timeProvider.getZoneId()), THING_REFRESH_INTERVAL * 1000L,
101100
TimeUnit.MILLISECONDS);
102101
}
103102
}
@@ -141,7 +140,7 @@ public void refreshChannel(ChannelUID channelUID) {
141140
return;
142141
}
143142

144-
ZoneId zoneId = bridgeHandler.getTimeZone();
143+
ZoneId zoneId = timeProvider.getZoneId();
145144

146145
AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
147146
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, zoneId);
@@ -163,7 +162,7 @@ public void refreshChannel(ChannelUID channelUID) {
163162
long diff;
164163
switch (channelId) {
165164
case CHANNEL_ACTIVE:
166-
state = OnOffType.from(result.isActive());
165+
state = OnOffType.from(result.isActive(timeProvider.getInstantNow()));
167166
break;
168167
case CHANNEL_START:
169168
state = new DateTimeType(Instant.ofEpochMilli(result.getStart()));
@@ -172,16 +171,16 @@ public void refreshChannel(ChannelUID channelUID) {
172171
state = new DateTimeType(Instant.ofEpochMilli(result.getEnd()));
173172
break;
174173
case CHANNEL_COUNTDOWN:
175-
diff = result.getStart() - Instant.now().toEpochMilli();
174+
diff = result.getStart() - timeProvider.getInstantNow().toEpochMilli();
176175
if (diff >= 0) {
177176
state = getDuration(diff);
178177
} else {
179178
state = QuantityType.valueOf(0, Units.MINUTE);
180179
}
181180
break;
182181
case CHANNEL_REMAINING:
183-
if (result.isActive()) {
184-
diff = result.getEnd() - Instant.now().toEpochMilli();
182+
if (result.isActive(timeProvider.getInstantNow())) {
183+
diff = result.getEnd() - timeProvider.getInstantNow().toEpochMilli();
185184
state = getDuration(diff);
186185
} else {
187186
state = QuantityType.valueOf(0, Units.MINUTE);
@@ -216,20 +215,39 @@ private List<AwattarPrice> getPriceRange(AwattarBridgeHandler bridgeHandler, Tim
216215
return result;
217216
}
218217

218+
/**
219+
* Returns the time range for the given start hour and duration.
220+
*
221+
* @param start the start hour (0-23)
222+
* @param duration the duration in hours
223+
* @param zoneId the time zone to use
224+
* @return the range
225+
*/
219226
protected TimeRange getRange(int start, int duration, ZoneId zoneId) {
220-
ZonedDateTime startCal = getCalendarForHour(start, zoneId);
221-
ZonedDateTime endCal = startCal.plusHours(duration);
222-
ZonedDateTime now = ZonedDateTime.now(zoneId);
227+
ZonedDateTime startTime = getStartTime(start, zoneId);
228+
ZonedDateTime endTime = startTime.plusHours(duration);
229+
ZonedDateTime now = timeProvider.getZonedDateTimeNow();
223230
if (now.getHour() < start) {
224231
// we are before the range, so we might be still within the last range
225-
startCal = startCal.minusDays(1);
226-
endCal = endCal.minusDays(1);
232+
startTime = startTime.minusDays(1);
233+
endTime = endTime.minusDays(1);
227234
}
228-
if (endCal.toInstant().toEpochMilli() < Instant.now().toEpochMilli()) {
235+
if (endTime.isBefore(now)) {
229236
// span is in the past, add one day
230-
startCal = startCal.plusDays(1);
231-
endCal = endCal.plusDays(1);
237+
startTime = startTime.plusDays(1);
238+
endTime = endTime.plusDays(1);
232239
}
233-
return new TimeRange(startCal.toInstant().toEpochMilli(), endCal.toInstant().toEpochMilli());
240+
return new TimeRange(startTime.toInstant().toEpochMilli(), endTime.toInstant().toEpochMilli());
241+
}
242+
243+
/**
244+
* Returns the start time for the given hour.
245+
*
246+
* @param start the hour. Must be between 0 and 23.
247+
* @param zoneId the time zone
248+
* @return the start time
249+
*/
250+
protected ZonedDateTime getStartTime(int start, ZoneId zoneId) {
251+
return getCalendarForHour(start, zoneId);
234252
}
235253
}

0 commit comments

Comments
 (0)