Fun of Programing


Timeseries

Data structure to manage timeseries data

Timeseries

Overview

Timeseries is a simple kotlin based library that provides a data structure to manage timeseries data.

This library aims to provide a simple but efficient in-memory data structure for handling and manipulating timeseries data. Sure same can be achieved by using a simple NavigableMap but then one will have to code a lot of methods for timeseries handling. This library does that by providing implementations that use NavigableMap internally.

Central interface of this library is Timeseries

Usage

Maven dependency

<dependency>
    <groupId>io.github.funofprograming</groupId>
    <artifactId>timeseries</artifactId>
    <version>LATEST</version>
</dependency>

Versions

Check for latest versions here: Releases

Code

This library provides 2 interfaces namely Timeseries which is immutable. And then a mutable version as MutableTimeseries.

All entries in and out of these interfaces are done via TimeseriesEntry class.

Following Junit classes show how to use these 3 interfaces.

TimeseriesEntry

package io.github.funofprograming.timeseries

import io.github.funofprograming.timeseries.impl.TimeseriesImpl
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import java.time.Instant
import java.util.UUID

class TestTimeseriesEntry {

    @Test
    fun testInitializeTimeseriesEntry_nullEventId() {
        val instant = Instant.now()
        val event = 1
        val timeseriesEntry: TimeseriesEntry<Int> = timeseriesEntryOf<Int>(instant, event)
        assertNotNull(timeseriesEntry)
        assertEquals(instant, timeseriesEntry.eventInstant)
        assertEquals(event, timeseriesEntry.event)
        assertNull(timeseriesEntry.eventId)
    }

    @Test
    fun testInitializeTimeseriesEntry_nonNullEventId() {
        val instant = Instant.now()
        val event = 1
        val eventId = UUID.randomUUID()
        val timeseriesEntry: TimeseriesEntry<Int> = timeseriesEntryOf<Int>(instant, event, eventId)
        assertNotNull(timeseriesEntry)
        assertEquals(instant, timeseriesEntry.eventInstant)
        assertEquals(event, timeseriesEntry.event)
        assertEquals(eventId, timeseriesEntry.eventId)
    }
}

package io.github.funofprograming.timeseries;

import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;
import static io.github.funofprograming.timeseries.TimeseriesBuildersKt.*;

class TestJavaTimeseriesEntry {

    @Test
    void testInitializeTimeseriesEntry_nullEventId() {
        Instant instant = Instant.now();
        Integer event = 1;
        TimeseriesEntry<Integer> timeseriesEntry = timeseriesEntryOf(instant, event, null);
        assertNotNull(timeseriesEntry);
        assertEquals(instant, timeseriesEntry.getEventInstant());
        assertEquals(event, timeseriesEntry.getEvent());
        assertNull(timeseriesEntry.getEventId());
    }

    @Test
    void testInitializeTimeseriesEntry_nonNullEventId() {
        Instant instant = Instant.now();
        Integer event = 1;
        UUID eventId = UUID.randomUUID();
        TimeseriesEntry<Integer> timeseriesEntry = timeseriesEntryOf(instant, event, eventId);
        assertNotNull(timeseriesEntry);
        assertEquals(instant, timeseriesEntry.getEventInstant());
        assertEquals(event, timeseriesEntry.getEvent());
        assertEquals(eventId, timeseriesEntry.getEventId());
    }
}

Timeseries

package io.github.funofprograming.timeseries

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import java.time.Instant
import java.util.NavigableMap
import java.util.TreeMap
import kotlin.collections.containsKey

class TestTimeseries {

    @Test
    fun testInitializeTimeseries_empty() {
        val timeseries = timeseriesOf<Int>()
        assertNotNull(timeseries)
    }

    @Test
    fun testInitializeTimeseries_oneEntry() {
        val timeseries = timeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_entryCollection_differentTime() {
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(Instant.now(), 1)
                , timeseriesEntryOf(Instant.now(), 2)
                , timeseriesEntryOf(Instant.now(), 3)
        ))
        assertNotNull(timeseries)
        assertEquals(3, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_entryCollection_sameTime() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 1)
                , timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
            ))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_entryMap() {
        val instant = Instant.now()
        val timeseriesEntrySet = setOf(
            timeseriesEntryOf(instant, 1)
            , timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
        )
        val timeseriesEntryMap = TreeMap<Instant, Collection<TimeseriesEntry<Int>>>()
        timeseriesEntryMap[instant] = timeseriesEntrySet
        val timeseries = timeseriesOf<Int>(timeseriesEntryMap)

        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_copy() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 1)
                , timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
            ))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())

        val timeseriesCopy = timeseriesOf(timeseries)
        assertNotNull(timeseriesCopy)
        assertFalse { timeseriesCopy === timeseries } //different objects
        assertTrue { timeseriesCopy == timeseries } //same entries
        assertEquals(1, timeseriesCopy.countInstants())
        assertEquals(3, timeseriesCopy.countEvents())
    }

    @Test
    fun testPlus_singleEntry_overwriteFalse() {
        val timeseries = timeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val timeseries2 = timeseries.plus((timeseriesEntryOf(Instant.now(), 2)), false)
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertFalse { timeseries2 == timeseries } //different objects
        assertEquals(2, timeseries2.countInstants())
        assertEquals(2, timeseries2.countEvents())
    }

    @Test
    fun testPlus_singleEntry_overwriteTrue() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(timeseriesEntryOf(instant, 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val timeseries2 = timeseries.plus((timeseriesEntryOf(instant, 1)))
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertTrue { timeseries2 == timeseries } //same entries because overwrite=true
        assertEquals(1, timeseries2.countInstants())
        assertEquals(1, timeseries2.countEvents())
    }

    @Test
    fun testPlus_collectionEntry_overwriteFalse() {
        val timeseries = timeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val timeseries2 = timeseries.plus(setOf(
                                                            timeseriesEntryOf(Instant.now(), 2)
                                                            , timeseriesEntryOf(Instant.now(), 3)
                                                            , timeseriesEntryOf(Instant.now(), 4))
                                , false)
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertFalse { timeseries2 == timeseries } //different objects
        assertEquals(4, timeseries2.countInstants())
        assertEquals(4, timeseries2.countEvents())
    }

    @Test
    fun testPlus_collectionEntry_overwriteTrue() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
                                                            timeseriesEntryOf(instant, 2)
                                                            , timeseriesEntryOf(instant, 3)
                                                            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.plus(setOf(
                                                            timeseriesEntryOf(instant, 2)
                                                            , timeseriesEntryOf(instant, 5)
                                                            , timeseriesEntryOf(instant, 6)))
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertFalse { timeseries2 == timeseries } //different objects
        assertEquals(1, timeseries2.countInstants())
        assertEquals(5, timeseries2.countEvents()) //one entry is overwritten so total 5 events instead of 6
    }

    @Test
    fun testGet() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val instantEvents = timeseries.get(instant)
        assertNotNull(instantEvents)
        assertTrue { instantEvents.isNotEmpty() }
        assertTrue { instantEvents.size == 3 }
        assertTrue { instantEvents.contains(timeseriesEntryOf(instant, 2)) }
        assertTrue { instantEvents.contains(timeseriesEntryOf(instant, 3)) }
        assertTrue { instantEvents.contains(timeseriesEntryOf(instant, 4)) }
        assertFalse { instantEvents.contains(timeseriesEntryOf(instant, 5)) }

        val instantEvents2 = timeseries.get(Instant.now())
        assertNotNull(instantEvents2)
        assertTrue { instantEvents2.isEmpty() }
    }

    @Test
    fun testGetAll() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val instantEvents = timeseries.getAll()
        assertNotNull(instantEvents)
        assertTrue { instantEvents.isNotEmpty() }
        assertTrue { instantEvents.size == 1 }
        assertTrue { instantEvents.keys.contains(instant) }
        assertTrue { instantEvents[instant]?.contains(timeseriesEntryOf(instant, 2)) ?: false }
        assertTrue { instantEvents[instant]?.contains(timeseriesEntryOf(instant, 3)) ?: false }
        assertTrue { instantEvents[instant]?.contains(timeseriesEntryOf(instant, 4)) ?: false }
    }

    @Test
    fun getAllInstants() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val instants = timeseries.getAllInstants()
        assertNotNull(instants)
        assertTrue { instants.isNotEmpty() }
        assertTrue { instants.size == 1 }
        assertTrue { instants.contains(instant) }
    }

    @Test
    fun testMinus_singleEntry() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.minus((timeseriesEntryOf(instant, 1)))
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertTrue { timeseries2 == timeseries } //same entries because minus entry does not exist
        assertEquals(1, timeseries2.countInstants())
        assertEquals(3, timeseries2.countEvents())
        val timeseries3 = timeseries.minus((timeseriesEntryOf(instant, 2)))
        assertFalse { timeseries3 === timeseries } //different objects
        assertFalse { timeseries3 == timeseries } //different objects
        assertEquals(1, timeseries3.countInstants())
        assertEquals(2, timeseries3.countEvents())
    }

    @Test
    fun testMinus_collectionEntry() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.minus(setOf(
            timeseriesEntryOf(instant, 1)
            , timeseriesEntryOf(instant, 7)))
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertTrue { timeseries2 == timeseries } //same entries because minus entry does not exist
        assertEquals(1, timeseries2.countInstants())
        assertEquals(3, timeseries2.countEvents())
        val timeseries3 = timeseries.minus(setOf(timeseriesEntryOf(instant, 2)
                                                            , timeseriesEntryOf(instant, 3)))
        assertFalse { timeseries3 === timeseries } //different objects
        assertFalse { timeseries3 == timeseries } //different objects
        assertEquals(1, timeseries3.countInstants())
        assertEquals(1, timeseries3.countEvents())
    }

    @Test
    fun testMinus_instant() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.minus(Instant.EPOCH)
        assertNotNull(timeseries2)
        assertFalse { timeseries2 === timeseries } //different objects
        assertTrue { timeseries2 == timeseries } //same entries because minus entry does not exist
        assertEquals(1, timeseries2.countInstants())
        assertEquals(3, timeseries2.countEvents())
        val timeseries3 = timeseries.minus(instant)
        assertFalse { timeseries3 === timeseries } //different objects
        assertFalse { timeseries3 == timeseries } //different objects
        assertEquals(0, timeseries3.countInstants())
        assertEquals(0, timeseries3.countEvents())
    }

    @Test
    fun testContains_instant() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        assertTrue { timeseries.contains(instant) }
        assertFalse { timeseries.contains(Instant.EPOCH) }
    }

    @Test
    fun testContains_event() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        assertTrue { timeseries.contains(2) }
        assertFalse { timeseries.contains(5) }
    }

    @Test
    fun testCountInstants() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testCountEvents() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testIsEmpty() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertFalse { timeseries.isEmpty() }
        val timeseries2 = timeseriesOf<Int>()
        assertNotNull(timeseries2)
        assertTrue { timeseries2.isEmpty() }
    }

    @Test
    fun testIsNotEmpty() {
        val instant = Instant.now()
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertTrue { timeseries.isNotEmpty() }
        val timeseries2 = timeseriesOf<Int>()
        assertNotNull(timeseries2)
        assertFalse { timeseries2.isNotEmpty() }
    }

    @Test
    fun testGetEntriesSubMap_exclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val subMap = timeseries.getEntriesSubMap(instant1, instant3)
        assertNotNull(subMap)
        assertEquals(1, subMap.size)
        assertTrue { subMap.containsKey(instant2) }
        assertFalse { subMap.containsKey(instant1) }
        assertFalse { subMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesSubMap_inclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val subMap = timeseries.getEntriesSubMap(instant1, true, instant3, true)
        assertNotNull(subMap)
        assertEquals(3, subMap.size)
        assertTrue { subMap.containsKey(instant1) }
        assertTrue { subMap.containsKey(instant2) }
        assertTrue { subMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesHeadMap_exclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val headMap = timeseries.getEntriesHeadMap(instant3)
        assertNotNull(headMap)
        assertEquals(2, headMap.size)
        assertTrue { headMap.containsKey(instant1) }
        assertTrue { headMap.containsKey(instant2) }
        assertFalse { headMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesHeadMap_inclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val headMap = timeseries.getEntriesHeadMap(instant3, true)
        assertNotNull(headMap)
        assertEquals(3, headMap.size)
        assertTrue { headMap.containsKey(instant1) }
        assertTrue { headMap.containsKey(instant2) }
        assertTrue { headMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesTailMap_exclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val tailMap = timeseries.getEntriesTailMap(instant1)
        assertNotNull(tailMap)
        assertEquals(2, tailMap.size)
        assertFalse { tailMap.containsKey(instant1) }
        assertTrue { tailMap.containsKey(instant2) }
        assertTrue { tailMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesTailMap_inclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val tailMap = timeseries.getEntriesTailMap(instant1, true)
        assertNotNull(tailMap)
        assertEquals(3, tailMap.size)
        assertTrue { tailMap.containsKey(instant1) }
        assertTrue { tailMap.containsKey(instant2) }
        assertTrue { tailMap.containsKey(instant3) }
    }

    @Test
    fun testStart() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant3, 3),
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2)
        ))

        val startEntries = timeseries.start()
        assertNotNull(startEntries)
        assertTrue { startEntries?.isNotEmpty() ?: false }
        assertTrue { startEntries?.contains(timeseriesEntryOf(instant1, 1)) ?: false }
    }

    @Test
    fun testStart_empty() {
        val timeseries = timeseriesOf<Int>()
        val startEntries = timeseries.start()
        assertNull(startEntries)
    }

    @Test
    fun testEnd() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant3, 3),
            timeseriesEntryOf(instant2, 2)
        ))

        val endEntries = timeseries.end()
        assertNotNull(endEntries)
        assertTrue { endEntries?.isNotEmpty() ?: false }
        assertTrue { endEntries?.contains(timeseriesEntryOf(instant3, 3)) ?: false }
    }

    @Test
    fun testEnd_empty() {
        val timeseries = timeseriesOf<Int>()
        val endEntries = timeseries.end()
        assertNull(endEntries)
    }

    @Test
    fun testIterator_ascending() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant3, 3),
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2)
        ))

        val iterator = timeseries.iterator()
        assertNotNull(iterator)
        assertTrue { iterator.hasNext() }

        val first = iterator.next()
        assertTrue { first.contains(timeseriesEntryOf(instant1, 1)) }

        val second = iterator.next()
        assertTrue { second.contains(timeseriesEntryOf(instant2, 2)) }

        val third = iterator.next()
        assertTrue { third.contains(timeseriesEntryOf(instant3, 3)) }

        assertFalse { iterator.hasNext() }
    }

    @Test
    fun testDescendingIterator() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = timeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant1, 1),
                timeseriesEntryOf(instant3, 3),
                timeseriesEntryOf(instant2, 2)
            )
        )

        val iterator = timeseries.descendingIterator()
        assertNotNull(iterator)
        assertTrue { iterator.hasNext() }

        val first = iterator.next()
        assertTrue { first.contains(timeseriesEntryOf(instant3, 3)) }

        val second = iterator.next()
        assertTrue { second.contains(timeseriesEntryOf(instant2, 2)) }

        val third = iterator.next()
        assertTrue { third.contains(timeseriesEntryOf(instant1, 1)) }

        assertFalse { iterator.hasNext() }
    }
}

package io.github.funofprograming.timeseries;

import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static io.github.funofprograming.timeseries.TimeseriesBuildersKt.*;

class TestJavaTimeseries {

    @Test
    void testInitializeTimeseries_empty() {
        Timeseries<Integer> timeseries = timeseriesOf();
        assertNotNull(timeseries);
    }

    @Test
    void testInitializeTimeseries_oneEntry() {
        Timeseries<Integer> timeseries = timeseriesOf(
                timeseriesEntryOf(Instant.now(), 1, null)
        );
        assertNotNull(timeseries);
        assertEquals(1, timeseries.countInstants());
        assertEquals(1, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_entryCollection_differentTime() {
        Timeseries<Integer> timeseries = timeseriesOf(
                Set.of(
                        timeseriesEntryOf(Instant.now(), 1, null),
                        timeseriesEntryOf(Instant.now().plusSeconds(1), 2, null),
                        timeseriesEntryOf(Instant.now().plusSeconds(2), 3, null)
                )
        );
        assertNotNull(timeseries);
        assertEquals(3, timeseries.countInstants());
        assertEquals(3, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_entryCollection_sameTime() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(
                Set.of(
                        timeseriesEntryOf(instant, 1, null),
                        timeseriesEntryOf(instant, 2, null),
                        timeseriesEntryOf(instant, 3, null)
                )
        );
        assertNotNull(timeseries);
        assertEquals(1, timeseries.countInstants());
        assertEquals(3, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_entryMap() {
        Instant instant = Instant.now();
        Collection<TimeseriesEntry<Integer>> timeseriesEntrySet = Set.of(
                timeseriesEntryOf(instant, 1, null),
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null)
        );

        NavigableMap<Instant, Collection<TimeseriesEntry<Integer>>> timeseriesEntryMap = new TreeMap<>();
        timeseriesEntryMap.put(instant, timeseriesEntrySet);

        Timeseries<Integer> timeseries = timeseriesOf(timeseriesEntryMap);

        assertNotNull(timeseries);
        assertEquals(1, timeseries.countInstants());
        assertEquals(3, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_copy() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(
                Set.of(
                        timeseriesEntryOf(instant, 1, null),
                        timeseriesEntryOf(instant, 2, null),
                        timeseriesEntryOf(instant, 3, null)
                )
        );

        Timeseries<Integer> timeseriesCopy = timeseriesOf(timeseries);
        assertNotNull(timeseriesCopy);
        assertNotSame(timeseries, timeseriesCopy); // different objects
        assertEquals(timeseries, timeseriesCopy);  // same entries
        assertEquals(1, timeseriesCopy.countInstants());
        assertEquals(3, timeseriesCopy.countEvents());
    }

    @Test
    void testPlus_singleEntry_overwriteFalse() {
        Timeseries<Integer> timeseries = timeseriesOf(timeseriesEntryOf(Instant.now(), 1, null));

        Timeseries<Integer> timeseries2 = timeseries.plus(timeseriesEntryOf(Instant.now(), 2, null), false);

        assertNotNull(timeseries2);
        assertNotSame(timeseries, timeseries2);
        assertNotEquals(timeseries, timeseries2);
        assertEquals(2, timeseries2.countInstants());
        assertEquals(2, timeseries2.countEvents());
    }

    @Test
    void testPlus_singleEntry_overwriteTrue() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(timeseriesEntryOf(instant, 1, null));

        Timeseries<Integer> timeseries2 = timeseries.plus(timeseriesEntryOf(instant, 1, null), true);

        assertNotNull(timeseries2);
        assertNotSame(timeseries, timeseries2);
        assertEquals(timeseries, timeseries2); // same entries because overwrite=true
        assertEquals(1, timeseries2.countInstants());
        assertEquals(1, timeseries2.countEvents());
    }

    @Test
    void testPlus_collectionEntry_overwriteFalse() {
        Timeseries<Integer> timeseries = timeseriesOf(timeseriesEntryOf(Instant.now(), 1, null));

        Timeseries<Integer> timeseries2 = timeseries.plus(Set.of(
                timeseriesEntryOf(Instant.now(), 2, null),
                timeseriesEntryOf(Instant.now(), 3, null),
                timeseriesEntryOf(Instant.now(), 4, null)
        ), false);

        assertNotNull(timeseries2);
        assertEquals(4, timeseries2.countInstants());
        assertEquals(4, timeseries2.countEvents());
    }

    @Test
    void testPlus_collectionEntry_overwriteTrue() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null),
                timeseriesEntryOf(instant, 4, null)
        ));

        Timeseries<Integer> timeseries2 = timeseries.plus(Set.of(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 5, null),
                timeseriesEntryOf(instant, 6, null)
        ), true);

        assertNotNull(timeseries2);
        assertEquals(1, timeseries2.countInstants());
        assertEquals(5, timeseries2.countEvents()); // one entry is overwritten, total 5
    }

    @Test
    void testGet() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null),
                timeseriesEntryOf(instant, 4, null)
        ));

        Collection<TimeseriesEntry<Integer>> instantEvents = timeseries.get(instant);
        assertNotNull(instantEvents);
        assertFalse(instantEvents.isEmpty());
        assertEquals(3, instantEvents.size());
        assertTrue(instantEvents.contains(timeseriesEntryOf(instant, 2, null)));
        assertFalse(instantEvents.contains(timeseriesEntryOf(instant, 5, null)));

        Collection<TimeseriesEntry<Integer>> instantEvents2 = timeseries.get(Instant.now());
        assertTrue(instantEvents2.isEmpty());
    }

    @Test
    void testGetAll() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null)
        ));

        Map<Instant, Collection<TimeseriesEntry<Integer>>> allEvents = timeseries.getAll();
        assertEquals(1, allEvents.size());
        assertTrue(allEvents.containsKey(instant));
    }

    @Test
    void testMinus_singleEntry() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null)
        ));

        Timeseries<Integer> timeseries2 = timeseries.minus(timeseriesEntryOf(instant, 1, null));
        assertEquals(2, timeseries2.countEvents()); // Not found, no change

        Timeseries<Integer> timeseries3 = timeseries.minus(timeseriesEntryOf(instant, 2, null));
        assertEquals(1, timeseries3.countEvents());
    }

    @Test
    void testMinus_instant() {
        Instant instant = Instant.now();
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(instant, 2, null)
        ));

        Timeseries<Integer> timeseries2 = timeseries.minus(instant);
        assertEquals(0, timeseries2.countInstants());
    }

    @Test
    void testGetEntriesSubMap_exclusive() {
        Instant i1 = Instant.parse("2024-01-01T00:00:00Z");
        Instant i2 = Instant.parse("2024-01-02T00:00:00Z");
        Instant i3 = Instant.parse("2024-01-03T00:00:00Z");

        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(i1, 1, null),
                timeseriesEntryOf(i2, 2, null),
                timeseriesEntryOf(i3, 3, null)
        ));

        NavigableMap<Instant, Collection<TimeseriesEntry<Integer>>> subMap = timeseries.getEntriesSubMap(i1, i3);
        assertEquals(1, subMap.size());
        assertTrue(subMap.containsKey(i2));
    }

    @Test
    void testStart() {
        Instant i1 = Instant.parse("2024-01-01T00:00:00Z");
        Instant i2 = Instant.parse("2024-01-02T00:00:00Z");
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(i2, 2, null),
                timeseriesEntryOf(i1, 1, null)
        ));

        Collection<TimeseriesEntry<Integer>> startEntries = timeseries.start();
        assertNotNull(startEntries);
        assertTrue(startEntries.contains(timeseriesEntryOf(i1, 1, null)));
    }

    @Test
    void testIterator_ascending() {
        Instant i1 = Instant.parse("2024-01-01T00:00:00Z");
        Instant i2 = Instant.parse("2024-01-02T00:00:00Z");
        Timeseries<Integer> timeseries = timeseriesOf(Set.of(
                timeseriesEntryOf(i2, 2, null),
                timeseriesEntryOf(i1, 1, null)
        ));

        Iterator<Collection<TimeseriesEntry<Integer>>> iterator = timeseries.iterator();
        assertTrue(iterator.hasNext());
        assertTrue(iterator.next().contains(timeseriesEntryOf(i1, 1, null)));
        assertTrue(iterator.next().contains(timeseriesEntryOf(i2, 2, null)));
    }
}

MutableTimeseries

package io.github.funofprograming.timeseries

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import java.time.Instant
import java.util.NavigableMap
import java.util.TreeMap
import java.util.UUID
import kotlin.collections.containsKey

class TestMutableTimeseries {

    @Test
    fun testInitializeTimeseries_empty() {
        val timeseries = mutableTimeseriesOf<Int>()
        assertNotNull(timeseries)
    }

    @Test
    fun testInitializeTimeseries_oneEntry() {
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_entryCollection_differentTime() {
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(Instant.now(), 1)
                , timeseriesEntryOf(Instant.now(), 2)
                , timeseriesEntryOf(Instant.now(), 3)
            ))
        assertNotNull(timeseries)
        assertEquals(3, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_entryCollection_sameTime() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 1)
                , timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
            ))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_entryMap() {
        val instant = Instant.now()
        val timeseriesEntrySet = setOf(
            timeseriesEntryOf(instant, 1)
            , timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
        )
        val timeseriesEntryMap = TreeMap<Instant, Collection<TimeseriesEntry<Int>>>()
        timeseriesEntryMap[instant] = timeseriesEntrySet
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryMap)

        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testInitializeTimeseries_copy() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 1)
                , timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
            ))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())

        val timeseriesCopy = mutableTimeseriesOf(timeseries)
        assertNotNull(timeseriesCopy)
        assertFalse { timeseriesCopy === timeseries } //different objects
        assertTrue { timeseriesCopy == timeseries } //same entries
        assertEquals(1, timeseriesCopy.countInstants())
        assertEquals(3, timeseriesCopy.countEvents())
    }

    @Test
    fun testPlus_singleEntry_overwriteFalse() {
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val timeseries2 = timeseries.plus((timeseriesEntryOf(Instant.now(), 2)), false)
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(2, timeseries2.countInstants())
        assertEquals(2, timeseries2.countEvents())
    }

    @Test
    fun testPlus_singleEntry_overwriteTrue() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(instant, 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val timeseries2 = timeseries.plus((timeseriesEntryOf(instant, 1)))
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(1, timeseries2.countInstants())
        assertEquals(1, timeseries2.countEvents())
    }

    @Test
    fun testPlus_collectionEntry_overwriteFalse() {
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val timeseries2 = timeseries.plus(setOf(
            timeseriesEntryOf(Instant.now(), 2)
            , timeseriesEntryOf(Instant.now(), 3)
            , timeseriesEntryOf(Instant.now(), 4))
            , false)
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(4, timeseries2.countInstants())
        assertEquals(4, timeseries2.countEvents())
    }

    @Test
    fun testPlus_collectionEntry_overwriteTrue() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.plus(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 5)
            , timeseriesEntryOf(instant, 6)))
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(1, timeseries2.countInstants())
        assertEquals(5, timeseries2.countEvents()) //one entry is overwritten so total 5 events instead of 6
    }

    @Test
    fun testAdd_singleEntry_overwriteFalse() {
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val uuid = timeseries.add((timeseriesEntryOf(Instant.now(), 2)), false)
        assertNotNull(uuid)
        assertEquals(2, timeseries.countInstants())
        assertEquals(2, timeseries.countEvents())
    }

    @Test
    fun testAdd_singleEntry_overwriteTrue() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(instant, 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val uuid = timeseries.add((timeseriesEntryOf(instant, 1)))
        assertNotNull(uuid)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
    }

    @Test
    fun testAdd_collectionEntry_overwriteFalse() {
        val timeseries = mutableTimeseriesOf<Int>(timeseriesEntryOf(Instant.now(), 1))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
        val uuids = timeseries.add(setOf(
            timeseriesEntryOf(Instant.now(), 2)
            , timeseriesEntryOf(Instant.now(), 3)
            , timeseriesEntryOf(Instant.now(), 4))
            , false)
        assertNotNull(uuids)
        assertTrue { uuids.isNotEmpty() }
        assertEquals(3, uuids.size)
        assertEquals(4, timeseries.countInstants())
        assertEquals(4, timeseries.countEvents())
    }

    @Test
    fun testAdd_collectionEntry_overwriteTrue() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val uuids = timeseries.add(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 5)
            , timeseriesEntryOf(instant, 6)))
        assertNotNull(uuids)
        assertTrue { uuids.isNotEmpty() }
        assertEquals(3, uuids.size)
        assertEquals(1, timeseries.countInstants())
        assertEquals(5, timeseries.countEvents()) //one entry is overwritten so total 5 events instead of 6
    }

    @Test
    fun testGet() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val instantEvents = timeseries.get(instant)
        assertNotNull(instantEvents)
        assertTrue { instantEvents.isNotEmpty() }
        assertTrue { instantEvents.size == 3 }
        assertTrue { instantEvents.contains(timeseriesEntryOf(instant, 2)) }
        assertTrue { instantEvents.contains(timeseriesEntryOf(instant, 3)) }
        assertTrue { instantEvents.contains(timeseriesEntryOf(instant, 4)) }
        assertFalse { instantEvents.contains(timeseriesEntryOf(instant, 5)) }

        val instantEvents2 = timeseries.get(Instant.now())
        assertNotNull(instantEvents2)
        assertTrue { instantEvents2.isEmpty() }
    }

    @Test
    fun testGetAll() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val instantEvents = timeseries.getAll()
        assertNotNull(instantEvents)
        assertTrue { instantEvents.isNotEmpty() }
        assertTrue { instantEvents.size == 1 }
        assertTrue { instantEvents.keys.contains(instant) }
        assertTrue { instantEvents[instant]?.contains(timeseriesEntryOf(instant, 2)) ?: false }
        assertTrue { instantEvents[instant]?.contains(timeseriesEntryOf(instant, 3)) ?: false }
        assertTrue { instantEvents[instant]?.contains(timeseriesEntryOf(instant, 4)) ?: false }
    }

    @Test
    fun testGetAllInstants() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val instants = timeseries.getAllInstants()
        assertNotNull(instants)
        assertTrue { instants.isNotEmpty() }
        assertTrue { instants.size == 1 }
        assertTrue { instants.contains(instant) }
    }

    @Test
    fun testGet_instant_eventId() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>()
        val uuids = timeseries.add(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 5)
            , timeseriesEntryOf(instant, 6)))
        val entry = timeseries.get(instant, uuids.first())
        assertNotNull(entry)
        assertEquals(2, entry?.event ?: null)
    }

    @Test
    fun testMinus_singleEntry() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.minus((timeseriesEntryOf(instant, 1)))
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(1, timeseries2.countInstants())
        assertEquals(3, timeseries2.countEvents())
        val timeseries3 = timeseries.minus((timeseriesEntryOf(instant, 2)))
        assertTrue { timeseries3 === timeseries } //same objects
        assertTrue { timeseries3 == timeseries } //same objects
        assertEquals(1, timeseries3.countInstants())
        assertEquals(2, timeseries3.countEvents())
    }

    @Test
    fun testMinus_collectionEntry() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.minus(setOf(
            timeseriesEntryOf(instant, 1)
            , timeseriesEntryOf(instant, 7)))
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(1, timeseries2.countInstants())
        assertEquals(3, timeseries2.countEvents())
        val timeseries3 = timeseries.minus(setOf(timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)))
        assertTrue { timeseries3 === timeseries } //same objects
        assertTrue { timeseries3 == timeseries } //same objects
        assertEquals(1, timeseries3.countInstants())
        assertEquals(1, timeseries3.countEvents())
    }

    @Test
    fun testMinus_instant() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val timeseries2 = timeseries.minus(Instant.EPOCH)
        assertNotNull(timeseries2)
        assertTrue { timeseries2 === timeseries } //same objects
        assertTrue { timeseries2 == timeseries } //same objects
        assertEquals(1, timeseries2.countInstants())
        assertEquals(3, timeseries2.countEvents())
        val timeseries3 = timeseries.minus(instant)
        assertTrue { timeseries3 === timeseries } //same objects
        assertTrue { timeseries3 == timeseries } //same objects
        assertEquals(0, timeseries3.countInstants())
        assertEquals(0, timeseries3.countEvents())
    }

    @Test
    fun testRemove_singleEntry() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val removed = timeseries.remove((timeseriesEntryOf(instant, 1)))
        assertNotNull(removed)
        assertFalse { removed }
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val removed2 = timeseries.remove((timeseriesEntryOf(instant, 2)))
        assertNotNull(removed)
        assertTrue { removed2 }
        assertEquals(1, timeseries.countInstants())
        assertEquals(2, timeseries.countEvents())
    }

    @Test
    fun testRemove_collectionEntry() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val removed = timeseries.remove(setOf(
            timeseriesEntryOf(instant, 1)
            , timeseriesEntryOf(instant, 7)))
        assertNotNull(removed)
        assertFalse { removed }
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val removed2 = timeseries.remove(setOf(timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)))
        assertNotNull(removed)
        assertTrue { removed2 }
        assertEquals(1, timeseries.countInstants())
        assertEquals(1, timeseries.countEvents())
    }

    @Test
    fun testRemove_instant() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val removed = timeseries.remove(Instant.EPOCH)
        assertNotNull(removed)
        assertFalse { removed }
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val removed2 = timeseries.remove(instant)
        assertNotNull(removed)
        assertTrue { removed2 }
        assertEquals(0, timeseries.countInstants())
        assertEquals(0, timeseries.countEvents())
    }

    @Test
    fun testClear() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant, 2)
            , timeseriesEntryOf(instant, 3)
            , timeseriesEntryOf(instant, 4)))
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        timeseries.clear()
        assertEquals(0, timeseries.countInstants())
        assertEquals(0, timeseries.countEvents())
    }

    @Test
    fun testContains_instant() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        assertTrue { timeseries.contains(instant) }
        assertFalse { timeseries.contains(Instant.EPOCH) }
    }

    @Test
    fun testContains_event() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        assertTrue { timeseries.contains(2) }
        assertFalse { timeseries.contains(5) }
    }

    @Test
    fun testContains_eventId() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
        val uuid = timeseries.add(timeseriesEntryOf(instant, 5))
        assertTrue { timeseries.contains(uuid) }
        assertFalse { timeseries.contains(UUID.randomUUID()) }
    }

    @Test
    fun testCountInstants() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testCountEvents() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertEquals(1, timeseries.countInstants())
        assertEquals(3, timeseries.countEvents())
    }

    @Test
    fun testIsEmpty() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertFalse { timeseries.isEmpty() }
        val timeseries2 = mutableTimeseriesOf<Int>()
        assertNotNull(timeseries2)
        assertTrue { timeseries2.isEmpty() }
    }

    @Test
    fun testIsNotEmpty() {
        val instant = Instant.now()
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant, 2)
                , timeseriesEntryOf(instant, 3)
                , timeseriesEntryOf(instant, 4)
            )
        )
        assertNotNull(timeseries)
        assertTrue { timeseries.isNotEmpty() }
        val timeseries2 = mutableTimeseriesOf<Int>()
        assertNotNull(timeseries2)
        assertFalse { timeseries2.isNotEmpty() }
    }

    @Test
    fun testGetEntriesSubMap_exclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val subMap = timeseries.getEntriesSubMap(instant1, instant3)
        assertNotNull(subMap)
        assertEquals(1, subMap.size)
        assertTrue { subMap.containsKey(instant2) }
        assertFalse { subMap.containsKey(instant1) }
        assertFalse { subMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesSubMap_inclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val subMap = timeseries.getEntriesSubMap(instant1, true, instant3, true)
        assertNotNull(subMap)
        assertEquals(3, subMap.size)
        assertTrue { subMap.containsKey(instant1) }
        assertTrue { subMap.containsKey(instant2) }
        assertTrue { subMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesHeadMap_exclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val headMap = timeseries.getEntriesHeadMap(instant3)
        assertNotNull(headMap)
        assertEquals(2, headMap.size)
        assertTrue { headMap.containsKey(instant1) }
        assertTrue { headMap.containsKey(instant2) }
        assertFalse { headMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesHeadMap_inclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val headMap = timeseries.getEntriesHeadMap(instant3, true)
        assertNotNull(headMap)
        assertEquals(3, headMap.size)
        assertTrue { headMap.containsKey(instant1) }
        assertTrue { headMap.containsKey(instant2) }
        assertTrue { headMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesTailMap_exclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val tailMap = timeseries.getEntriesTailMap(instant1)
        assertNotNull(tailMap)
        assertEquals(2, tailMap.size)
        assertFalse { tailMap.containsKey(instant1) }
        assertTrue { tailMap.containsKey(instant2) }
        assertTrue { tailMap.containsKey(instant3) }
    }

    @Test
    fun testGetEntriesTailMap_inclusive() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2),
            timeseriesEntryOf(instant3, 3)
        ))

        val tailMap = timeseries.getEntriesTailMap(instant1, true)
        assertNotNull(tailMap)
        assertEquals(3, tailMap.size)
        assertTrue { tailMap.containsKey(instant1) }
        assertTrue { tailMap.containsKey(instant2) }
        assertTrue { tailMap.containsKey(instant3) }
    }

    @Test
    fun testStart() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant3, 3),
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2)
        ))

        val startEntries = timeseries.start()
        assertNotNull(startEntries)
        assertTrue { startEntries?.isNotEmpty() ?: false }
        assertTrue { startEntries?.contains(timeseriesEntryOf(instant1, 1)) ?: false }
    }

    @Test
    fun testStart_empty() {
        val timeseries = mutableTimeseriesOf<Int>()
        val startEntries = timeseries.start()
        assertNull(startEntries)
    }

    @Test
    fun testEnd() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant3, 3),
            timeseriesEntryOf(instant2, 2)
        ))

        val endEntries = timeseries.end()
        assertNotNull(endEntries)
        assertTrue { endEntries?.isNotEmpty() ?: false }
        assertTrue { endEntries?.contains(timeseriesEntryOf(instant3, 3)) ?: false }
    }

    @Test
    fun testEnd_empty() {
        val timeseries = mutableTimeseriesOf<Int>()
        val endEntries = timeseries.end()
        assertNull(endEntries)
    }

    @Test
    fun testIterator_ascending() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(setOf(
            timeseriesEntryOf(instant3, 3),
            timeseriesEntryOf(instant1, 1),
            timeseriesEntryOf(instant2, 2)
        ))

        val iterator = timeseries.iterator()
        assertNotNull(iterator)
        assertTrue { iterator.hasNext() }

        val first = iterator.next()
        assertTrue { first.contains(timeseriesEntryOf(instant1, 1)) }

        val second = iterator.next()
        assertTrue { second.contains(timeseriesEntryOf(instant2, 2)) }

        val third = iterator.next()
        assertTrue { third.contains(timeseriesEntryOf(instant3, 3)) }

        assertFalse { iterator.hasNext() }
    }

    @Test
    fun testDescendingIterator() {
        val instant1 = Instant.parse("2024-01-01T00:00:00Z")
        val instant2 = Instant.parse("2024-01-02T00:00:00Z")
        val instant3 = Instant.parse("2024-01-03T00:00:00Z")
        val timeseries = mutableTimeseriesOf<Int>(
            setOf(
                timeseriesEntryOf(instant1, 1),
                timeseriesEntryOf(instant3, 3),
                timeseriesEntryOf(instant2, 2)
            )
        )

        val iterator = timeseries.descendingIterator()
        assertNotNull(iterator)
        assertTrue { iterator.hasNext() }

        val first = iterator.next()
        assertTrue { first.contains(timeseriesEntryOf(instant3, 3)) }

        val second = iterator.next()
        assertTrue { second.contains(timeseriesEntryOf(instant2, 2)) }

        val third = iterator.next()
        assertTrue { third.contains(timeseriesEntryOf(instant1, 1)) }

        assertFalse { iterator.hasNext() }
    }
}

package io.github.funofprograming.timeseries;

import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.*;

import static io.github.funofprograming.timeseries.TimeseriesBuildersKt.mutableTimeseriesOf;
import static io.github.funofprograming.timeseries.TimeseriesBuildersKt.timeseriesEntryOf;
import static org.junit.jupiter.api.Assertions.*;

class TestJavaMutableTimeseries {

    @Test
    void testInitializeTimeseries_empty() {
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf();
        assertNotNull(timeseries);
    }

    @Test
    void testInitializeTimeseries_oneEntry() {
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(timeseriesEntryOf(Instant.now(), 1, null));
        assertNotNull(timeseries);
        assertEquals(1, timeseries.countInstants());
        assertEquals(1, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_entryCollection_differentTime() {
        Set<TimeseriesEntry<Integer>> entries = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(Instant.now(), 1, null),
                timeseriesEntryOf(Instant.now(), 2, null),
                timeseriesEntryOf(Instant.now(), 3, null)
        ));
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(entries);
        assertNotNull(timeseries);
        assertEquals(3, timeseries.countInstants());
        assertEquals(3, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_entryCollection_sameTime() {
        Instant instant = Instant.now();
        Set<TimeseriesEntry<Integer>> entries = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 1, null),
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null)
        ));
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(entries);
        assertNotNull(timeseries);
        assertEquals(1, timeseries.countInstants());
        assertEquals(3, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_entryMap() {
        Instant instant = Instant.now();
        Set<TimeseriesEntry<Integer>> timeseriesEntrySet = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 1, null),
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null)
        ));
        TreeMap<Instant, Collection<TimeseriesEntry<Integer>>> timeseriesEntryMap = new TreeMap<>();
        timeseriesEntryMap.put(instant, timeseriesEntrySet);

        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(timeseriesEntryMap);

        assertNotNull(timeseries);
        assertEquals(1, timeseries.countInstants());
        assertEquals(3, timeseries.countEvents());
    }

    @Test
    void testInitializeTimeseries_copy() {
        Instant instant = Instant.now();
        Set<TimeseriesEntry<Integer>> entries = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 1, null),
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null)
        ));
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(entries);

        MutableTimeseries<Integer> timeseriesCopy = mutableTimeseriesOf(timeseries);
        assertNotNull(timeseriesCopy);
        assertNotSame(timeseries, timeseriesCopy); // different objects
        assertEquals(timeseries, timeseriesCopy);   // same entries (equals)
        assertEquals(1, timeseriesCopy.countInstants());
        assertEquals(3, timeseriesCopy.countEvents());
    }

    @Test
    void testPlus_singleEntry_overwriteFalse() {
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(timeseriesEntryOf(Instant.now(), 1, null));
        MutableTimeseries<Integer> timeseries2 = timeseries.plus(timeseriesEntryOf(Instant.now(), 2, null), false);

        assertSame(timeseries, timeseries2);
        assertEquals(2, timeseries2.countInstants());
        assertEquals(2, timeseries2.countEvents());
    }

    @Test
    void testPlus_collectionEntry_overwriteTrue() {
        Instant instant = Instant.now();
        Set<TimeseriesEntry<Integer>> entries = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null),
                timeseriesEntryOf(instant, 4, null)
        ));
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(entries);

        Set<TimeseriesEntry<Integer>> newEntries = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 5, null),
                timeseriesEntryOf(instant, 6, null)
        ));

        MutableTimeseries<Integer> timeseries2 = timeseries.plus(newEntries, true);
        assertEquals(1, timeseries2.countInstants());
        assertEquals(5, timeseries2.countEvents()); // one overwritten
    }

    @Test
    void testGet() {
        Instant instant = Instant.now();
        Set<TimeseriesEntry<Integer>> entries = new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 3, null),
                timeseriesEntryOf(instant, 4, null)
        ));
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(entries);

        Collection<TimeseriesEntry<Integer>> instantEvents = timeseries.get(instant);
        assertNotNull(instantEvents);
        assertFalse(instantEvents.isEmpty());
        assertEquals(3, instantEvents.size());
        assertTrue(instantEvents.contains(timeseriesEntryOf(instant, 2, null)));
    }

    @Test
    void testGet_instant_eventId() {
        Instant instant = Instant.now();
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf();
        Set<TimeseriesEntry<Integer>> entries = new LinkedHashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 2, null),
                timeseriesEntryOf(instant, 5, null)
        ));
        Collection<UUID> uuids = timeseries.add(entries, true);

        TimeseriesEntry<Integer> entry = timeseries.get(instant, uuids.iterator().next());
        assertNotNull(entry);
        assertEquals(2, entry.getEvent());
    }

    @Test
    void testMinus_instant() {
        Instant instant = Instant.now();
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(new HashSet<>(Arrays.asList(
                timeseriesEntryOf(instant, 2, null)
        )));

        MutableTimeseries<Integer> timeseries2 = timeseries.minus(Instant.EPOCH);
        assertSame(timeseries, timeseries2);
        assertEquals(1, timeseries2.countInstants());

        MutableTimeseries<Integer> timeseries3 = timeseries.minus(instant);
        assertEquals(0, timeseries3.countInstants());
        assertEquals(0, timeseries3.countEvents());
    }

    @Test
    void testClear() {
        MutableTimeseries<Integer> timeseries = mutableTimeseriesOf(timeseriesEntryOf(Instant.now(), 2, null));
        timeseries.clear();
        assertEquals(0, timeseries.countInstants());
        assertEquals(0, timeseries.countEvents());
    }

    @Test
    void testGetEntriesSubMap_exclusive() {
        Instant i1 = Instant.parse("2024-01-01T00:00:00Z");
        Instant i2 = Instant.parse("2024-01-02T00:00:00Z");
        Instant i3 = Instant.parse("2024-01-03T00:00:00Z");

        MutableTimeseries<Integer> ts = mutableTimeseriesOf(new HashSet<>(Arrays.asList(
                timeseriesEntryOf(i1, 1, null)
                , timeseriesEntryOf(i2, 2, null)
                , timeseriesEntryOf(i3, 3, null)
        )));

        NavigableMap<Instant, Collection<TimeseriesEntry<Integer>>> subMap = ts.getEntriesSubMap(i1, i3);
        assertEquals(1, subMap.size());
        assertTrue(subMap.containsKey(i2));
        assertFalse(subMap.containsKey(i1));
    }

    @Test
    void testStart() {
        Instant i1 = Instant.parse("2024-01-01T00:00:00Z");
        Instant i2 = Instant.parse("2024-01-02T00:00:00Z");
        MutableTimeseries<Integer> ts = mutableTimeseriesOf(new HashSet<>(Arrays.asList(
                timeseriesEntryOf(i2, 2, null)
                , timeseriesEntryOf(i1, 1, null)
        )));

        Collection<TimeseriesEntry<Integer>> startEntries = ts.start();
        assertNotNull(startEntries);
        assertTrue(startEntries.contains(timeseriesEntryOf(i1, 1, null)));
    }

    @Test
    void testIterator_ascending() {
        Instant i1 = Instant.parse("2024-01-01T00:00:00Z");
        Instant i2 = Instant.parse("2024-01-02T00:00:00Z");
        MutableTimeseries<Integer> ts = mutableTimeseriesOf(new HashSet<>(Arrays.asList(
                timeseriesEntryOf(i2, 2, null)
                , timeseriesEntryOf(i1, 1, null)
        )));

        Iterator<Collection<TimeseriesEntry<Integer>>> iterator = ts.iterator();
        assertTrue(iterator.hasNext());
        assertTrue(iterator.next().contains(timeseriesEntryOf(i1, 1, null)));
        assertTrue(iterator.next().contains(timeseriesEntryOf(i2, 2, null)));
    }
}

Above Junit showcase how to initialize and use all the methods of Timeseries and MutableTimeseries interfaces.

API DOCS

Timeseries Kotlin Java
1.0.0 Kotlin Docs Java Docs

Compatibility matrix

Timeseries Kotlin-Std Java
1.0.0 2.3.10 21

License

Timeseries is released under version 2.0 of the Apache License.

Source

timeseries