From e6698bfad0774d30f7e14a3fc52565298f6fc9a1 Mon Sep 17 00:00:00 2001 From: Erik van Velzen Date: Tue, 17 Feb 2026 14:25:19 +0100 Subject: [PATCH] First typed time series: electricity --- src/main/kotlin/DateTimeUtil.kt | 12 +++++++ src/main/kotlin/TimeSeriesBuilder.kt | 4 +++ .../kotlin/typed/ElectricityKwhTimeSeries.kt | 20 ++++++++++++ .../kotlin/typed/ElectricityTimeSeries.kt | 3 ++ src/main/kotlin/typed/EnergyTimeSeries.kt | 7 +++++ src/main/kotlin/typed/PowerTimeSeries.kt | 7 +++++ src/main/kotlin/untyped/ArrayTimeSeries.kt | 2 +- .../typed/ElectricityTimeSeriesTest.java | 31 +++++++++++++++++++ .../timeseries/untyped/WraparoundTest.java | 1 - 9 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/DateTimeUtil.kt create mode 100644 src/main/kotlin/typed/ElectricityKwhTimeSeries.kt create mode 100644 src/main/kotlin/typed/ElectricityTimeSeries.kt create mode 100644 src/main/kotlin/typed/EnergyTimeSeries.kt create mode 100644 src/main/kotlin/typed/PowerTimeSeries.kt create mode 100644 src/test/java/com/zenmo/timeseries/typed/ElectricityTimeSeriesTest.java diff --git a/src/main/kotlin/DateTimeUtil.kt b/src/main/kotlin/DateTimeUtil.kt new file mode 100644 index 0000000..c37f1fa --- /dev/null +++ b/src/main/kotlin/DateTimeUtil.kt @@ -0,0 +1,12 @@ +package com.zenmo.timeseries + +import java.time.Duration +import java.time.temporal.TemporalAmount + +private const val SECONDS_IN_HOUR = 3600.0 + +internal fun TemporalAmount.hours(): Double = Duration.from(this).toSeconds() / SECONDS_IN_HOUR + +internal fun TemporalAmount.multiplicateInverseHours(): Double { + return 1 / (this.hours()) +} diff --git a/src/main/kotlin/TimeSeriesBuilder.kt b/src/main/kotlin/TimeSeriesBuilder.kt index 1874603..c1a2b98 100644 --- a/src/main/kotlin/TimeSeriesBuilder.kt +++ b/src/main/kotlin/TimeSeriesBuilder.kt @@ -1,5 +1,7 @@ package com.zenmo.timeseries +import com.zenmo.timeseries.typed.ElectricityKwhTimeSeries +import com.zenmo.timeseries.typed.ElectricityTimeSeries import com.zenmo.timeseries.untyped.ArrayTimeSeries import com.zenmo.timeseries.untyped.TimeSeries import com.zenmo.timeseries.untyped.TimeSeriesAccessor @@ -34,6 +36,8 @@ class TimeSeriesBuilder { } } + fun buildElectricityKwhTimeSeries(): ElectricityTimeSeries = ElectricityKwhTimeSeries(build()) + /** * Start of the first interval of the [values]. * Must support arithmetic using the [step]. diff --git a/src/main/kotlin/typed/ElectricityKwhTimeSeries.kt b/src/main/kotlin/typed/ElectricityKwhTimeSeries.kt new file mode 100644 index 0000000..be1b735 --- /dev/null +++ b/src/main/kotlin/typed/ElectricityKwhTimeSeries.kt @@ -0,0 +1,20 @@ +package com.zenmo.timeseries.typed + +import com.zenmo.timeseries.multiplicateInverseHours +import com.zenmo.timeseries.untyped.TimeSeries +import java.time.temporal.Temporal + +/** + * A time series of electric energy backed by a raw time series of kWh. + * Can directly take the quarter-hourly values given by an electricity supplier. + */ +internal class ElectricityKwhTimeSeries( + internal val kwhTimeSeries: TimeSeries, +) : ElectricityTimeSeries { + override fun getKwh(intervalStart: Temporal): Double = kwhTimeSeries[intervalStart] + + private val multiplicateInverseHours: Double = kwhTimeSeries.step.multiplicateInverseHours() + + override fun getKw(intervalStart: Temporal): Double = + getKwh(intervalStart) * multiplicateInverseHours +} diff --git a/src/main/kotlin/typed/ElectricityTimeSeries.kt b/src/main/kotlin/typed/ElectricityTimeSeries.kt new file mode 100644 index 0000000..db2ae1f --- /dev/null +++ b/src/main/kotlin/typed/ElectricityTimeSeries.kt @@ -0,0 +1,3 @@ +package com.zenmo.timeseries.typed + +interface ElectricityTimeSeries: EnergyTimeSeries, PowerTimeSeries diff --git a/src/main/kotlin/typed/EnergyTimeSeries.kt b/src/main/kotlin/typed/EnergyTimeSeries.kt new file mode 100644 index 0000000..5721616 --- /dev/null +++ b/src/main/kotlin/typed/EnergyTimeSeries.kt @@ -0,0 +1,7 @@ +package com.zenmo.timeseries.typed + +import java.time.temporal.Temporal + +interface EnergyTimeSeries { + fun getKwh(intervalStart: Temporal): Double +} diff --git a/src/main/kotlin/typed/PowerTimeSeries.kt b/src/main/kotlin/typed/PowerTimeSeries.kt new file mode 100644 index 0000000..212c39e --- /dev/null +++ b/src/main/kotlin/typed/PowerTimeSeries.kt @@ -0,0 +1,7 @@ +package com.zenmo.timeseries.typed + +import java.time.temporal.Temporal + +interface PowerTimeSeries { + fun getKw(intervalStart: Temporal): Double +} diff --git a/src/main/kotlin/untyped/ArrayTimeSeries.kt b/src/main/kotlin/untyped/ArrayTimeSeries.kt index 537e0cb..bcc0eb3 100644 --- a/src/main/kotlin/untyped/ArrayTimeSeries.kt +++ b/src/main/kotlin/untyped/ArrayTimeSeries.kt @@ -104,7 +104,7 @@ internal open class ArrayTimeSeries( return result } - fun size() = values.size + internal fun size() = values.size fun toBuilder() = TimeSeriesBuilder().start(start).step(step).values(values) } diff --git a/src/test/java/com/zenmo/timeseries/typed/ElectricityTimeSeriesTest.java b/src/test/java/com/zenmo/timeseries/typed/ElectricityTimeSeriesTest.java new file mode 100644 index 0000000..e36d015 --- /dev/null +++ b/src/test/java/com/zenmo/timeseries/typed/ElectricityTimeSeriesTest.java @@ -0,0 +1,31 @@ +package com.zenmo.timeseries.typed; + +import com.zenmo.timeseries.untyped.TimeSeries; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ElectricityTimeSeriesTest { + @Test + void test() { + var start = Instant.parse("2025-01-01T00:00:00Z"); + var electricityTimeSeries = TimeSeries.builder() + .values(new double[]{2.0, 3.0, 4.0}) + .start(start) + .step(Duration.ofMinutes(15)) + .buildElectricityKwhTimeSeries(); + + assertEquals(2.0, electricityTimeSeries.getKwh(start)); + assertEquals(8.0, electricityTimeSeries.getKw(start)); + assertEquals(3.0, electricityTimeSeries.getKwh(start.plus(Duration.ofMinutes(15)))); + assertEquals(12.0, electricityTimeSeries.getKw(start.plus(Duration.ofMinutes(15)))); + assertEquals(4.0, electricityTimeSeries.getKwh(start.plus(Duration.ofMinutes(30)))); + assertEquals(16.0, electricityTimeSeries.getKw(start.plus(Duration.ofMinutes(30)))); + assertThrows(IndexOutOfBoundsException.class, () -> electricityTimeSeries.getKwh(start.plus(Duration.ofMinutes(45)))); + assertThrows(IndexOutOfBoundsException.class, () -> electricityTimeSeries.getKw(start.plus(Duration.ofMinutes(45)))); + } +} diff --git a/src/test/java/com/zenmo/timeseries/untyped/WraparoundTest.java b/src/test/java/com/zenmo/timeseries/untyped/WraparoundTest.java index 645525c..f4196c6 100644 --- a/src/test/java/com/zenmo/timeseries/untyped/WraparoundTest.java +++ b/src/test/java/com/zenmo/timeseries/untyped/WraparoundTest.java @@ -7,7 +7,6 @@ import java.time.Instant; import java.time.Period; import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import java.util.stream.DoubleStream;