diff --git a/pom.xml b/pom.xml index e7bf479e..7a726fb7 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ valutakurs-klient unleash metrikker + tidslinje diff --git a/tidslinje/pom.xml b/tidslinje/pom.xml new file mode 100644 index 00000000..745d9854 --- /dev/null +++ b/tidslinje/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + no.nav.familie.felles + felles + ${revision}${sha1}${changelist} + + + tidslinje + ${revision}${sha1}${changelist} + Felles - Tidslinje + jar + + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.junit.jupiter + junit-jupiter-api + test + + + + src/main/kotlin + src/test/kotlin + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + + + diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Periode.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Periode.kt new file mode 100644 index 00000000..3b03e541 --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Periode.kt @@ -0,0 +1,31 @@ +@file:Suppress("UNCHECKED_CAST") + +package no.nav.familie.tidslinje + +import java.time.LocalDate + +data class Periode( + // NB: Generiske klasser arver type fra "Any?", så verdi kan være null. + val verdi: T, + val fom: LocalDate?, + val tom: LocalDate?, +) { + fun tilTidslinjePeriodeMedDato() = TidslinjePeriodeMedDato(verdi, fom, tom) +} + +fun List>.tilTidslinje(): Tidslinje = + this + .map { it.tilTidslinjePeriodeMedDato() } + .sortedBy { it.fom } + .tilTidslinje() + +fun List>.filtrerIkkeNull(): List> = + this.mapNotNull { periode -> periode.verdi?.let { periode as Periode } } + +fun List>.verdier(): List = this.map { it.verdi } + +data class IkkeNullbarPeriode( + val verdi: T, + val fom: LocalDate, + val tom: LocalDate, +) diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Tidslinje.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Tidslinje.kt new file mode 100644 index 00000000..8a096747 --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Tidslinje.kt @@ -0,0 +1,207 @@ +package no.nav.familie.tidslinje + +import no.nav.familie.tidslinje.utvidelser.klipp +import no.nav.familie.tidslinje.utvidelser.kombinerMed +import no.nav.familie.tidslinje.utvidelser.map +import no.nav.familie.tidslinje.utvidelser.mapper +import no.nav.familie.tidslinje.utvidelser.tilPerioder +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.temporal.TemporalAdjusters + +enum class TidsEnhet { + DAG, + UKE, + MÅNED, + ÅR, +} + +/** + * En tidslinje består av ulike verdier over tid. Det vil si at en tidslinje kan ha en verdi + * A over et tidsintervall og en annen verdi B over et senere tidsintervall. Disse tidsintervallene blir håndert av Periode-klassen. + * En tidslinje kan visualiseres som en liste [A, A, A, A, ... , A, B, B, ... , B, C, C, ... , C] og et startstidspunkt, + * hvor A, B og C er ulike verdier og hver plass i lista representerer en dag. + * Tidslinjer kan innholde uendelige perioder. Det kan ikke være flere perioder med andre verdier etter en uendelig periode. + * En tidslinje støtter verdier av typen [Udefinert], [Null] og [PeriodeVerdi]. En verdi er udefinert når vi ikke vet + * hva verdien skal være (et hull i tidslinja). En verdi er no.nav.familie.tidslinje.Null når vi vet at det ikke finnes noe verdi i dette tidsrommet. + */ +open class Tidslinje( + var startsTidspunkt: LocalDate, + perioder: List>, + var tidsEnhet: TidsEnhet = TidsEnhet.DAG, +) { + var innhold: List> = emptyList() + set(verdi) { + field = this.lagInnholdBasertPåPeriodelengder(verdi) + } + val foreldre: MutableList> = mutableListOf() + var tittel = "" + + init { + this.innhold = perioder + startsTidspunkt = + when (tidsEnhet) { + TidsEnhet.ÅR -> startsTidspunkt.withDayOfYear(1) + TidsEnhet.MÅNED -> startsTidspunkt.withDayOfMonth(1) + TidsEnhet.UKE -> startsTidspunkt.with(DayOfWeek.MONDAY) + TidsEnhet.DAG -> startsTidspunkt + } + } + + fun erTom() = innhold.sumOf { it.lengde } == 0 + + /** + * Kalkulerer slutttidspunkt som en LocalDate. + * Funksjonen returnerer den siste dagen som er med i tidslinja + * Om tidslinja er uendelig, kastes det et unntak + */ + fun kalkulerSluttTidspunkt(): LocalDate { + val antallTidsEnheter: Int = this.innhold.sumOf { it.lengde } + val sluttTidspunkt = this.startsTidspunkt.plus(antallTidsEnheter.toLong() - 1, mapper[this.tidsEnhet]) + + return when (this.tidsEnhet) { + TidsEnhet.ÅR -> sluttTidspunkt.with(TemporalAdjusters.lastDayOfYear()) + TidsEnhet.MÅNED -> sluttTidspunkt.with(TemporalAdjusters.lastDayOfMonth()) + TidsEnhet.UKE -> sluttTidspunkt.with(DayOfWeek.SUNDAY) + TidsEnhet.DAG -> sluttTidspunkt + } + } + + private fun kalkulerSluttTidspunkt(sluttDato: LocalDate): LocalDate = + when (this.tidsEnhet) { + TidsEnhet.ÅR -> sluttDato.with(TemporalAdjusters.lastDayOfYear()) + TidsEnhet.MÅNED -> sluttDato.with(TemporalAdjusters.lastDayOfMonth()) + TidsEnhet.UKE -> sluttDato.with(DayOfWeek.SUNDAY) + TidsEnhet.DAG -> sluttDato + } + + override fun toString(): String = + "StartTidspunkt: " + startsTidspunkt + " Tidsenhet: " + tidsEnhet + + " Total lengde: " + innhold.sumOf { it.lengde } + + " Perioder: " + + innhold.mapIndexed { indeks, it -> + "(Verdi: " + it.periodeVerdi.verdi.toString() + + ", fom: " + + startsTidspunkt.plus( + innhold.take(indeks).sumOf { it.lengde }.toLong(), + mapper[this.tidsEnhet], + ) + + ", tom:" + + kalkulerSluttTidspunkt( + startsTidspunkt.plus( + innhold.take(indeks).sumOf { it.lengde }.toLong() + it.lengde - 1, + mapper[this.tidsEnhet], + ), + ) + + ")" + } + + private fun lagInnholdBasertPåPeriodelengder(innhold: List>): List> { + val arr = mutableListOf>() + + var i = 0 + while (i < innhold.size) { + var j = i + 1 + var lengde = innhold[i].lengde + + while (j < innhold.size && innhold[i].periodeVerdi == innhold[j].periodeVerdi) { + lengde += innhold[j].lengde + j++ + if (j >= innhold.size) break + } + + val tidslinjePeriode = + TidslinjePeriode( + periodeVerdi = innhold[i].periodeVerdi, + lengde = lengde, + erUendelig = false, + ) + i = j + arr.add(tidslinjePeriode) + if (tidslinjePeriode.erUendelig) return arr.toList() + } + + return arr.toList() + } + + override fun equals(other: Any?): Boolean { + if (other !is Tidslinje<*>) return false + return other.startsTidspunkt == this.startsTidspunkt && + other.innhold == this.innhold && + other.tidsEnhet == this.tidsEnhet && + other.tittel == this.tittel + } + + override fun hashCode(): Int = + this.startsTidspunkt.hashCode() + this.innhold.hashCode() + this.tidsEnhet.hashCode() + this.tittel.hashCode() + + companion object +} + +fun tomTidslinje( + startsTidspunkt: LocalDate? = null, + tidsEnhet: TidsEnhet = TidsEnhet.DAG, +): Tidslinje = Tidslinje(startsTidspunkt = startsTidspunkt ?: PRAKTISK_TIDLIGSTE_DAG, emptyList(), tidsEnhet) + +fun Map>.leftJoin( + høyreTidslinjer: Map>, + kombinator: (V?, H?) -> R?, +): Map> { + val venstreTidslinjer = this + val venstreNøkler = venstreTidslinjer.keys + + return venstreNøkler.associateWith { nøkkel -> + val venstreTidslinje = venstreTidslinjer.getOrDefault(nøkkel, tomTidslinje()) + val høyreTidslinje = høyreTidslinjer.getOrDefault(nøkkel, tomTidslinje()) + + venstreTidslinje.kombinerMed(høyreTidslinje, kombinator) + } +} + +fun Map>.outerJoin( + høyreTidslinjer: Map>, + kombinator: (V?, H?) -> R?, +): Map> { + val venstreTidslinjer = this + val alleNøkler = venstreTidslinjer.keys + høyreTidslinjer.keys + + return alleNøkler.associateWith { nøkkel -> + val venstreTidslinje = venstreTidslinjer.getOrDefault(nøkkel, tomTidslinje()) + val høyreTidslinje = høyreTidslinjer.getOrDefault(nøkkel, tomTidslinje()) + + venstreTidslinje.kombinerMed(høyreTidslinje, kombinator) + } +} + +fun Map>.outerJoin( + tidslinjer2: Map>, + tidslinjer3: Map>, + kombinator: (A?, B?, C?) -> R?, +): Map> { + val tidslinjer1 = this + val alleNøkler = tidslinjer1.keys + tidslinjer2.keys + tidslinjer3.keys + + return alleNøkler.associateWith { nøkkel -> + val tidslinje1 = tidslinjer1.getOrDefault(nøkkel, tomTidslinje()) + val tidslinje2 = tidslinjer2.getOrDefault(nøkkel, tomTidslinje()) + val tidslinje3 = tidslinjer3.getOrDefault(nøkkel, tomTidslinje()) + + tidslinje1.kombinerMed(tidslinje2, tidslinje3, kombinator) + } +} + +fun Tidslinje.beskjærEtter(tidslinje: Tidslinje<*>): Tidslinje = + this.klipp(tidslinje.startsTidspunkt, tidslinje.kalkulerSluttTidspunkt()) + +fun Tidslinje.inneholder(verdi: T): Boolean = this.tilPerioder().any { it.verdi == verdi } + +fun Tidslinje.mapVerdi(mapper: (T?) -> R): Tidslinje = + this.map { periodeVerdi -> + when (periodeVerdi) { + is Verdi, + is Null, + -> mapper(periodeVerdi.verdi)?.let { Verdi(it) } ?: Null() + + is Udefinert -> Udefinert() + } + } diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/TidslinjePeriode.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/TidslinjePeriode.kt new file mode 100644 index 00000000..50b150fe --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/TidslinjePeriode.kt @@ -0,0 +1,78 @@ +package no.nav.familie.tidslinje + +const val INF = 1_000_000_000 + +sealed class PeriodeVerdi( + protected val _verdi: T?, +) { + override operator fun equals(other: Any?): Boolean { + if (other !is PeriodeVerdi<*>) return false + if (other._verdi == this._verdi) return true + return false + } + + override fun hashCode(): Int = this._verdi.hashCode() + + abstract val verdi: T? +} + +class Verdi( + override val verdi: T & Any, +) : PeriodeVerdi(verdi) + +class Udefinert : PeriodeVerdi(null) { + override fun equals(other: Any?): Boolean = other is Udefinert<*> + + override fun hashCode(): Int = this._verdi.hashCode() + + override val verdi: T? = this._verdi +} + +class Null : PeriodeVerdi(null) { + override fun equals(other: Any?): Boolean = other is Null<*> + + override fun hashCode(): Int = this._verdi.hashCode() + + override val verdi: T? = this._verdi +} + +/** + * En periode representerer et tidsintervall hvor en tidslinje har en konstant verdi. + * En periode varer en tid [lengde], og kan være uendelig. + * Om [lengde] > [INF] eller [erUendelig] er satt til true, behandles perioden som at den har uendelig lengde. + * En tidslinje støtter verdier av typen [Udefinert], [Null] og [PeriodeVerdi]. En verdi er udefinert når vi ikke vet + * hva verdien skal være (et hull i tidslinja). En verdi er no.nav.familie.tidslinje.Null når vi vet at det ikke finnes en verdi i dette tidsrommet. + */ +data class TidslinjePeriode( + val periodeVerdi: PeriodeVerdi, + var lengde: Int, + var erUendelig: Boolean = false, +) { + init { + if (lengde >= INF) { + erUendelig = true + } + if (erUendelig && lengde < INF) { + lengde = INF + } + if (lengde <= 0) { + throw java.lang.IllegalArgumentException("lengde må være større enn null.") + } + } + + constructor(periodeVerdi: T?, lengde: Int, erUendelig: Boolean = false) : this( + if (periodeVerdi == null) { + Null() + } else { + Verdi( + periodeVerdi, + ) + }, + lengde, + erUendelig, + ) + + override fun toString(): String = "Verdi: " + periodeVerdi.verdi.toString() + ", Lengde: " + lengde +} + +fun T?.tilPeriodeVerdi(): PeriodeVerdi = this?.let { Verdi(it) } ?: Null() diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/TidslinjePeriodeMedDato.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/TidslinjePeriodeMedDato.kt new file mode 100644 index 00000000..64317e13 --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/TidslinjePeriodeMedDato.kt @@ -0,0 +1,109 @@ +package no.nav.familie.tidslinje + +import java.time.LocalDate + +data class TidslinjePeriodeMedDato( + val periodeVerdi: PeriodeVerdi, + val fom: Dato, + val tom: Dato, +) { + constructor( + verdi: T?, + fom: LocalDate?, + tom: LocalDate?, + ) : this( + periodeVerdi = verdi?.let { Verdi(it) } ?: Null(), + fom = Dato(fom ?: PRAKTISK_TIDLIGSTE_DAG), + tom = Dato(tom ?: PRAKTISK_SENESTE_DAG), + ) + + class Dato( + private val dato: LocalDate, + ) : Comparable { + fun tilLocalDateEllerNull(): LocalDate? = + if (this.dato == PRAKTISK_TIDLIGSTE_DAG || this.dato == PRAKTISK_SENESTE_DAG) { + null + } else { + this.dato + } + + override fun compareTo(other: Dato): Int = this.dato.compareTo(other.dato) + } + + fun tilPeriode() = Periode(periodeVerdi.verdi, fom.tilLocalDateEllerNull(), tom.tilLocalDateEllerNull()) +} + +fun List>.tilTidslinje(): Tidslinje { + val perioder = this.tilTidslinjePerioder() + return Tidslinje( + startsTidspunkt = this.firstOrNull()?.fom?.tilDatoEllerPraktiskTidligsteDag() ?: PRAKTISK_TIDLIGSTE_DAG, + perioder = perioder, + ) +} + +private fun List>.fyllInnTommePerioder(): List> = + this.fold(emptyList()) { periodeListeMedTommePerioder, periode -> + val sisteElement = periodeListeMedTommePerioder.lastOrNull() + + if (sisteElement == null) { + periodeListeMedTommePerioder + periode + } else if (sisteElement.tom.tilDatoEllerPraktiskSenesteDag() == + periode.fom + .tilDatoEllerPraktiskTidligsteDag() + .minusDays(1) + ) { + periodeListeMedTommePerioder + periode + } else { + periodeListeMedTommePerioder + + TidslinjePeriodeMedDato( + Udefinert(), + TidslinjePeriodeMedDato.Dato(sisteElement.tom.tilDatoEllerPraktiskSenesteDag().plusDays(1)), + TidslinjePeriodeMedDato.Dato(periode.fom.tilDatoEllerPraktiskTidligsteDag().minusDays(1)), + ) + + periode + } + } + +private fun List>.tilTidslinjePerioder(): List> { + this.validerKunFørsteEllerSistePeriodeErUendelig() + this.validerIngenOverlapp() + + return this + .sortedBy { it.fom } + .fyllInnTommePerioder() + .map { + TidslinjePeriode( + periodeVerdi = it.periodeVerdi, + lengde = it.fom.tilDatoEllerPraktiskTidligsteDag().diffIDager(it.tom.tilDatoEllerPraktiskSenesteDag()), + erUendelig = it.tom.tilLocalDateEllerNull() == null, + ) + } +} + +fun List>.validerIngenOverlapp(feilmelding: String = "Feil med tidslinje. Overlapp på periode") { + this + .sortedBy { it.fom } + .zipWithNext { a, b -> + if (a.tom.tilDatoEllerPraktiskSenesteDag().isAfter(b.fom.tilDatoEllerPraktiskTidligsteDag())) { + error(message = feilmelding) + } + } +} + +private fun List>.validerKunFørsteEllerSistePeriodeErUendelig() { + val sortertListe = this.sortedBy { it.fom } + + sortertListe.forEachIndexed { indeks, periode -> + if (indeks != 0 && periode.fom.tilLocalDateEllerNull() == null) { + error("Feil med tidslinje. Flere perioder med fom=null") + } + if (indeks != sortertListe.lastIndex && periode.tom.tilLocalDateEllerNull() == null) { + error("Feil med tidslinje. Periode som ikke kommer på slutten har tom=null") + } + } +} + +private fun TidslinjePeriodeMedDato.Dato.tilDatoEllerPraktiskTidligsteDag(): LocalDate = + this.tilLocalDateEllerNull() ?: PRAKTISK_TIDLIGSTE_DAG + +private fun TidslinjePeriodeMedDato.Dato.tilDatoEllerPraktiskSenesteDag(): LocalDate = this.tilLocalDateEllerNull() ?: PRAKTISK_SENESTE_DAG diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Utils.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Utils.kt new file mode 100644 index 00000000..8a81ca4b --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/Utils.kt @@ -0,0 +1,14 @@ +package no.nav.familie.tidslinje + +import java.time.Duration +import java.time.LocalDate + +val PRAKTISK_TIDLIGSTE_DAG = LocalDate.of(0, 1, 1) +val PRAKTISK_SENESTE_DAG = LocalDate.MAX.minusYears(1) + +fun LocalDate.diffIDager(annen: LocalDate): Int = + Duration + // legger på én dag på sluttdatoen siden den er exlusive + .between(this.atStartOfDay(), annen.plusDays(1).atStartOfDay()) + .toDaysPart() + .toInt() diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/FiltrerTidslinjer.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/FiltrerTidslinjer.kt new file mode 100644 index 00000000..348a41da --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/FiltrerTidslinjer.kt @@ -0,0 +1,16 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi + +fun Tidslinje.filtrer(predicate: (T?) -> Boolean) = + this.map { + when (it) { + is Verdi, is Null -> if (predicate(it.verdi)) it else Udefinert() + is Udefinert -> Udefinert() + } + } + +fun Tidslinje.filtrerIkkeNull(): Tidslinje = filtrer { it != null } diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerTidslinjer.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerTidslinjer.kt new file mode 100644 index 00000000..ad375cc5 --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerTidslinjer.kt @@ -0,0 +1,49 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.PRAKTISK_TIDLIGSTE_DAG +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import no.nav.familie.tidslinje.tomTidslinje + +fun Collection>.slåSammen(): Tidslinje> { + val minsteTidspunkt = this.minOfOrNull { it.startsTidspunkt } ?: PRAKTISK_TIDLIGSTE_DAG + return this.fold(tomTidslinje(startsTidspunkt = minsteTidspunkt)) { sammenlagt, neste -> + sammenlagt.biFunksjon(neste) { periodeVerdiFraSammenlagt, periodeVerdiFraNeste -> + when (periodeVerdiFraSammenlagt) { + is Verdi -> + when (periodeVerdiFraNeste) { + is Verdi -> Verdi(periodeVerdiFraSammenlagt.verdi + periodeVerdiFraNeste.verdi) + else -> periodeVerdiFraSammenlagt + } + + is Null -> + when (periodeVerdiFraNeste) { + is Verdi -> Verdi(listOf(periodeVerdiFraNeste.verdi)) + else -> Null() + } + + is Udefinert -> + when (periodeVerdiFraNeste) { + is Verdi -> Verdi(listOf(periodeVerdiFraNeste.verdi)) + is Null -> Null() + is Udefinert -> Udefinert() + } + } + } + } +} + +fun Collection>.kombiner(listeKombinator: (Iterable) -> R): Tidslinje = + this.slåSammen().map { + when (it) { + is Verdi -> { + val resultat = listeKombinator(it.verdi) + if (resultat != null) Verdi(resultat) else Null() + } + + is Null -> Null() + is Udefinert -> Udefinert() + } + } diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/PeriodeUtvidelser.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/PeriodeUtvidelser.kt new file mode 100644 index 00000000..500121a5 --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/PeriodeUtvidelser.kt @@ -0,0 +1,35 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.PeriodeVerdi +import no.nav.familie.tidslinje.TidslinjePeriode + +/** + * Beregner en ny periode ved bruk av [operator] basert på periodene this og [operand]. + * Den nye perioden har lengde [lengde] og verdien blir beregnet ut ifra å benytte seg av + * funksjonen [operator]. + * [operator] vil få verdiene som de to input periodene består av som input. + */ +fun TidslinjePeriode.biFunksjon( + operand: TidslinjePeriode, + lengde: Int, + erUendelig: Boolean, + operator: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): TidslinjePeriode = TidslinjePeriode(operator(this.periodeVerdi, operand.periodeVerdi), lengde, erUendelig) + +fun List>.slåSammenLike(): List> = + this.fold(emptyList()) { acc, tidslinjePeriode -> + val sisteElementIAcc = acc.lastOrNull() + + if (sisteElementIAcc == null) { + listOf(tidslinjePeriode) + } else if (sisteElementIAcc.periodeVerdi == tidslinjePeriode.periodeVerdi) { + acc.dropLast(1) + + TidslinjePeriode( + periodeVerdi = sisteElementIAcc.periodeVerdi, + lengde = sisteElementIAcc.lengde + tidslinjePeriode.lengde, + erUendelig = sisteElementIAcc.erUendelig || tidslinjePeriode.erUendelig, + ) + } else { + acc + tidslinjePeriode + } + } diff --git a/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeUtvidelser.kt b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeUtvidelser.kt new file mode 100644 index 00000000..c6d04aac --- /dev/null +++ b/tidslinje/src/main/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeUtvidelser.kt @@ -0,0 +1,736 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.INF +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.PRAKTISK_SENESTE_DAG +import no.nav.familie.tidslinje.Periode +import no.nav.familie.tidslinje.PeriodeVerdi +import no.nav.familie.tidslinje.TidsEnhet +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.TidslinjePeriodeMedDato +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.filtrerIkkeNull +import no.nav.familie.tidslinje.tilPeriodeVerdi +import java.time.LocalDate +import java.time.temporal.ChronoUnit +import kotlin.math.absoluteValue + +val TIDENES_ENDE = LocalDate.MAX + +val mapper = + mapOf( + TidsEnhet.ÅR to ChronoUnit.YEARS, + TidsEnhet.MÅNED to ChronoUnit.MONTHS, + TidsEnhet.UKE to ChronoUnit.WEEKS, + TidsEnhet.DAG to ChronoUnit.DAYS, + ) + +fun List>.join( + operand: Tidslinje, + operator: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): List> = this.mapIndexed { _, tidslinjeBarn -> tidslinjeBarn.biFunksjon(operand, kombineringsfunksjon = operator) } + +fun List>.join( + operand: List>, + operator: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): List> { + if (this.size != operand.size) throw IllegalArgumentException("Listene må ha lik lengde") + return this.mapIndexed { index, tidslinjeBarn -> + tidslinjeBarn.biFunksjon( + operand[index], + kombineringsfunksjon = operator, + ) + } +} + +fun Tidslinje.medTittel(tittel: String): Tidslinje { + this.tittel = tittel + return this +} + +/** + * Konverterer to input-tidslinjer til å bli av samme lengde. Dette gjør den ved å legge til en "padding" bestående av en periode + * med lengde lik differansen mellom de to tidspunktene og verdi gitt av [nullVerdi] til tidslinjen. + * Antar tidslinjene er av samme tidsenhet!! + */ +private fun konverterTilSammeLengde( + tidslinje1: Tidslinje, + tidslinje2: Tidslinje, +): Pair, Tidslinje> { + val kopi1 = Tidslinje(tidslinje1.startsTidspunkt, tidslinje1.innhold, tidsEnhet = tidslinje1.tidsEnhet) + val kopi2 = Tidslinje(tidslinje2.startsTidspunkt, tidslinje2.innhold, tidsEnhet = tidslinje2.tidsEnhet) + + kopi1.tittel = tidslinje1.tittel + kopi2.tittel = tidslinje2.tittel + + kopi1.foreldre.addAll(tidslinje1.foreldre) + kopi2.foreldre.addAll(tidslinje2.foreldre) + + val udefinert1 = Udefinert() + val udefinert2 = Udefinert() + + var tidsenhetForskjell = + kopi1.startsTidspunkt + .until(kopi2.startsTidspunkt, mapper[kopi1.tidsEnhet]) + .toInt() + .absoluteValue + + if (kopi1.startsTidspunkt > kopi2.startsTidspunkt) { + kopi1.innhold = listOf(TidslinjePeriode(udefinert1, tidsenhetForskjell, false)) + kopi1.innhold + kopi1.startsTidspunkt = kopi2.startsTidspunkt + } else if (kopi2.startsTidspunkt > kopi1.startsTidspunkt) { + kopi2.innhold = listOf(TidslinjePeriode(udefinert2, tidsenhetForskjell, false)) + kopi2.innhold + kopi2.startsTidspunkt = kopi1.startsTidspunkt + } + + if (kopi1.kalkulerSluttTidspunkt() != kopi2.kalkulerSluttTidspunkt()) { + if (kopi1.innhold.isNotEmpty() && kopi1.innhold.last().erUendelig) { + kopi2.innhold = kopi2.innhold + + listOf( + TidslinjePeriode( + periodeVerdi = udefinert2, + lengde = INF, + erUendelig = true, + ), + ) + return Pair(kopi1, kopi2) + } else if (kopi2.innhold.isNotEmpty() && kopi2.innhold.last().erUendelig) { + kopi1.innhold = + kopi1.innhold + listOf(TidslinjePeriode(periodeVerdi = udefinert1, lengde = INF, erUendelig = true)) + return Pair(kopi1, kopi2) + } + + if (kopi1.kalkulerSluttTidspunkt() < kopi2.kalkulerSluttTidspunkt()) { + tidsenhetForskjell = + kopi1 + .kalkulerSluttTidspunkt() + .until( + kopi2.kalkulerSluttTidspunkt().plusDays(1), + mapper[kopi1.tidsEnhet], + ).toInt() + .absoluteValue + kopi1.innhold = kopi1.innhold + listOf(TidslinjePeriode(udefinert1, tidsenhetForskjell, false)) + } else if (kopi2.kalkulerSluttTidspunkt() < kopi1.kalkulerSluttTidspunkt()) { + tidsenhetForskjell = + kopi1 + .kalkulerSluttTidspunkt() + .plusDays(1) + .until( + kopi2.kalkulerSluttTidspunkt(), + mapper[kopi1.tidsEnhet], + ).toInt() + .absoluteValue + kopi2.innhold = kopi2.innhold + listOf(TidslinjePeriode(udefinert2, tidsenhetForskjell, false)) + } + } + + return Pair(kopi1, kopi2) +} + +/** + * Tar inn en tidslinje av en vilkårlig tidsenhet (sålangt kan det kun være MÅNED eller DAG) og returnerer den som DAG. + */ +fun Tidslinje.konverterTilDag(): Tidslinje { + if (this.tidsEnhet == TidsEnhet.DAG) return this + + var tidspunkt = this.startsTidspunkt + + when (this.tidsEnhet) { + TidsEnhet.UKE -> { + for (periode in this.innhold) { + val nyttTidspunkt = tidspunkt.plusWeeks(periode.lengde.toLong()) + periode.lengde = tidspunkt.until(nyttTidspunkt, ChronoUnit.DAYS).toInt() + tidspunkt = nyttTidspunkt + } + } + + TidsEnhet.MÅNED -> { + for (periode in this.innhold) { + val nyttTidspunkt = tidspunkt.plusMonths(periode.lengde.toLong()) + periode.lengde = tidspunkt.until(nyttTidspunkt, ChronoUnit.DAYS).toInt() + tidspunkt = nyttTidspunkt + } + } + + else -> { + for (periode in this.innhold) { + val nyttTidspunkt = tidspunkt.plusYears(periode.lengde.toLong()) + periode.lengde = tidspunkt.until(nyttTidspunkt, ChronoUnit.DAYS).toInt() + tidspunkt = nyttTidspunkt + } + } + } + + this.tidsEnhet = TidsEnhet.DAG + + return this +} + +/** + * Tar inn en operator som bestemmer hvordan periodeverdiene i tidslinja skal mappes til andre periodeverdier. + * [operator] er en funksjon som tar inn en periodeverdi og returnerer en periodeverdi. + * Dette åpner for at man kan mappe no.nav.familie.tidslinje.Null og no.nav.familie.tidslinje.Udefinert objekter til andre verdier. + */ +fun Tidslinje.map(operator: (elem: PeriodeVerdi) -> PeriodeVerdi): Tidslinje { + val perioder = mutableListOf>() + + this.innhold.forEach { perioder.add(TidslinjePeriode(operator(it.periodeVerdi), it.lengde, it.erUendelig)) } + + val tidslinje = Tidslinje(this.startsTidspunkt, perioder, this.tidsEnhet) + + tidslinje.foreldre.addAll(this.foreldre) + + return tidslinje + .medTittel(this.tittel) +} + +/** + * Fjerner alle periodeverdier i [periodeVerdier] fra slutten og begynnelsen av tidslinja. + * Ved fjerning av perioder i begynnelsen av tislinja, vil startspunktet bli flyttet. + */ +fun Tidslinje.trim(vararg periodeVerdier: PeriodeVerdi): Tidslinje = + this + .trimVenstre(*periodeVerdier) + .trimHøyre(*periodeVerdier) + .medTittel(this.tittel) + +fun Tidslinje.trimVenstre(vararg periodeVerdier: PeriodeVerdi): Tidslinje { + val perioder = ArrayList(this.innhold) + var startsTidspunkt = this.startsTidspunkt + + for (periode in this.innhold) { + if (periodeVerdier.contains(periode.periodeVerdi)) { + startsTidspunkt = startsTidspunkt.plus(periode.lengde.toLong(), mapper[this.tidsEnhet]) + perioder.remove(periode) + } else { + break + } + } + val resultat = Tidslinje(startsTidspunkt, perioder, this.tidsEnhet) + + this.foreldre.forEach { + if (!resultat.foreldre.contains(it)) { + resultat.foreldre.add(it) + } + } + + return resultat +} + +fun Tidslinje.trimHøyre(vararg periodeVerdier: PeriodeVerdi): Tidslinje { + val perioder = ArrayList(this.innhold) + + for (periode in this.innhold.reversed()) { + if (periodeVerdier.contains(periode.periodeVerdi)) { + perioder.remove(periode) + } else { + break + } + } + + val resultat = Tidslinje(this.startsTidspunkt, perioder, this.tidsEnhet) + + this.foreldre.forEach { + if (!resultat.foreldre.contains(it)) { + resultat.foreldre.add(it) + } + } + + return resultat +} + +/** + * Tar inn en tidslinje med tidsenhet DAG og returnerer en med tidsenhet MÅNED. + * Parameteren [vindu] bestemmer hvor mange måneder som skal bli tatt med i beregeningen av månedsverdien for gjeldene måned. + * Med vindu parameteren satt for man en sliding-window effekt med steglende en. + * Operator definerer hvordan verdiene for alle månedene skal bli valgt (f.eks. skal det tas gjennomsnitt av alle verdiene + * innad i den måneden, skal den største verdien velges eller skal den siste verdien velges). + * [dato] parameteren i operator oppdateres til å være første dag i gjeldene måned under beregningene. + */ +fun Tidslinje.konverterTilMåned( + antallMndBakoverITid: Int = 0, + antallMndFremoverITid: Int = 0, + operator: (dato: LocalDate, månedListe: List>>) -> PeriodeVerdi, +): Tidslinje { + val listeAvMåneder: MutableList>> = this.splittPåMåned() + + if (listeAvMåneder.size < antallMndBakoverITid + antallMndFremoverITid) { + throw java.lang.IllegalArgumentException("Det er for få månender i tidslinja for dette vinduet.") + } + + listeAvMåneder.addAll(0, (0 until antallMndBakoverITid).map { emptyList() }) + listeAvMåneder.addAll((0 until antallMndFremoverITid).map { emptyList() }) + + val perioder: MutableList> = mutableListOf() + var dato = this.startsTidspunkt.withDayOfMonth(1) + + listeAvMåneder.windowed(size = antallMndBakoverITid + antallMndFremoverITid + 1, partialWindows = false) { vindu -> + perioder.add(TidslinjePeriode(operator(dato, vindu), 1, false)) + dato = dato.plusMonths(1) + // dersom inneværende måned er uendelig, må man beregne verdien dersom vinduet kun dekker den uendelige periodeVerdien. + if (vindu[antallMndBakoverITid].last().erUendelig) { + val listeMedUendeligPeriodeVerdier = + (0..antallMndBakoverITid + antallMndFremoverITid).map { listOf(vindu[antallMndBakoverITid].last()) } + perioder.add(TidslinjePeriode(operator(dato, listeMedUendeligPeriodeVerdier), INF, true)) + } + } + + return Tidslinje(this.startsTidspunkt, perioder, TidsEnhet.MÅNED) + .medTittel(this.tittel) +} + +/** + * Shifter en tidslinje [antall] tidsenheter (enten DAG eller MÅNED) mot høyre. + */ +fun Tidslinje.høyreShift(antall: Int = 1): Tidslinje = + Tidslinje(this.startsTidspunkt.plus(antall.toLong(), mapper[this.tidsEnhet]), this.innhold, this.tidsEnhet) + .medTittel(this.tittel) + +/** + * Splitter periodene i en tidslinje på månedsgrenser og returnerer en liste av + * lister med perioder der hver liste representerer en egen måned. + */ +fun Tidslinje.splittPåMåned(): MutableList>> { + var nåværendeMåned = this.startsTidspunkt + var antallDagerIgjenIMåned = this.startsTidspunkt.lengthOfMonth() - this.startsTidspunkt.dayOfMonth + 1 + + var månedListe: MutableList> = mutableListOf() // representerer periodene innad i en måned + + val listeAvMåneder: MutableList>> = + mutableListOf() // liste av lister som representerer en måned + + this.innhold.forEachIndexed { index, periode -> + + if (periode.erUendelig) { // Her håndteres uendelige perioder + // om vi er midt i en måned må vi først legge til en ikke-uendelig periode som fyller opp denne + if (nåværendeMåned.lengthOfMonth() > antallDagerIgjenIMåned) { + månedListe.add(TidslinjePeriode(periode.periodeVerdi, antallDagerIgjenIMåned, false)) + listeAvMåneder.add(månedListe) + månedListe = mutableListOf() + nåværendeMåned = nåværendeMåned.plusMonths(1) + } + månedListe.add(periode) // her adder vi den uendelige perioden + listeAvMåneder.add(månedListe) // om koden har kommet hit vil den være ferdig, da uendelige perioder alltid er bakerst + } else if (periode.lengde < antallDagerIgjenIMåned) { // Her håndteres perioder som går opp i inneværende måned + månedListe.add(periode) + antallDagerIgjenIMåned -= periode.lengde + + if (index + 1 == this.innhold.size) { // om vi er på siste periode er vi ferdige og månedListe kan addes. + listeAvMåneder.add(månedListe) + } + } else { // Her håndteres perioder som er lenger enn inneværende måned og ikke uendelig + var lengde = periode.lengde // holder oversikt over hvor mange dager vi har igjen av en gitt periode + + while (lengde > antallDagerIgjenIMåned) { // "Så lenge perioden har igjen flere dager enn det er dager igjen i måneden" + månedListe.add(TidslinjePeriode(periode.periodeVerdi, antallDagerIgjenIMåned)) + listeAvMåneder.add(månedListe) + månedListe = mutableListOf() + lengde -= antallDagerIgjenIMåned + nåværendeMåned = nåværendeMåned.plusMonths(1) + antallDagerIgjenIMåned = nåværendeMåned.lengthOfMonth() + } + // kommer koden hit betyr det at gjenstående lengde til perioden er mindre enn antallDagerIgjenIMåned + if (lengde > 0) { + månedListe.add(TidslinjePeriode(periode.periodeVerdi, lengde)) + antallDagerIgjenIMåned -= lengde + + if (antallDagerIgjenIMåned == 0) { + listeAvMåneder.add(månedListe) + månedListe = mutableListOf() + nåværendeMåned = nåværendeMåned.plusMonths(1) + antallDagerIgjenIMåned = nåværendeMåned.lengthOfMonth() + } else if (index + 1 == this.innhold.size) { // om vi er på siste periode er vi ferdige og månedListe kan addes. + listeAvMåneder.add(månedListe) + } + } + } + } + return listeAvMåneder +} + +private fun klippeOperator( + status1: PeriodeVerdi, + status2: PeriodeVerdi, +): PeriodeVerdi = + if (status1 is Udefinert || status2 is Udefinert) { + Udefinert() + } else if (status1 is Null || status2 is Null) { + Null() + } else { + if (status2.verdi == true) { + status1 + } else { + Udefinert() + } + } + +fun Tidslinje.klipp( + startsTidspunkt: LocalDate, + sluttTidspunkt: LocalDate, +): Tidslinje { + val foreldre = this.foreldre + + var resultat = + if (sluttTidspunkt.isAfter(startsTidspunkt)) { + val justertSluttTidspunkt = + if (sluttTidspunkt == TIDENES_ENDE) sluttTidspunkt else sluttTidspunkt.plusDays(1) + + val tidslinjeKlipp = + Tidslinje( + startsTidspunkt, + listOf( + TidslinjePeriode( + true, + lengde = + startsTidspunkt + .until( + justertSluttTidspunkt, + mapper[this.tidsEnhet], + ).toInt(), + ), + ), + this.tidsEnhet, + ) + this + .biFunksjon(tidslinjeKlipp) { status1, status2 -> + klippeOperator( + status1, + status2, + ) + }.fjernForeldre() + } else { + Tidslinje(startsTidspunkt, emptyList(), this.tidsEnhet) + } + + resultat = resultat.trim(Udefinert()) + + if (resultat.startsTidspunkt > sluttTidspunkt) { + resultat.startsTidspunkt = startsTidspunkt + } + + resultat.foreldre.addAll(foreldre) + return resultat + .medTittel(this.tittel) +} + +fun Tidslinje.biFunksjonSnitt( + operand: Tidslinje, + operator: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): Tidslinje { + val startsTidspunkt = + if (this.startsTidspunkt < operand.startsTidspunkt) operand.startsTidspunkt else this.startsTidspunkt + + val sluttTidspunkt1 = this.kalkulerSluttTidspunkt() + val sluttTidspunkt2 = operand.kalkulerSluttTidspunkt() + + val sluttTidspunkt = if (sluttTidspunkt1 < sluttTidspunkt2) sluttTidspunkt1 else sluttTidspunkt2 + + return this + .biFunksjon(operand, operator) + .klipp(startsTidspunkt, sluttTidspunkt) +} + +/** + * Beregner en ny tidslinje ved bruk av [kombineringsfunksjon] basert på tidslinjene this og [annen]. + * Går gjennom alle periodene i hver tidslinje og beregner nye perioder. + * Hver nye periode som blir lagt inn i den resulterende tidslinja starter der den forrige stoppet og + * har lengde lik det minste tidsrommet hvor input tidslinjene har konstant verdi. + * Påfølgende perioder med lik verdi, blir slått sammen til en periode i den resulterende tidslinja. + * [kombineringsfunksjon] blir brukt for å beregne verdiene til de generete periodene basert på verdiene til input tidslinjene i + * de respektive tidsrommene. Om en av input-tidslinjene er uendleig(dvs at den siste perioden har uendelig varighet), vil + * også den resulterende tidslinja være uendelig. + * [PeriodeVerdi] er en wrapper-classe som blir brukt for å håndtere no.nav.familie.tidslinje.Udefinert og no.nav.familie.tidslinje.Null. [kombineringsfunksjon] + * må ta høyde for at input kan være av en av disse typene, og definere hvordan disse situasjonene håndteres. + * MERK: operator skal returnere enten Udefindert, no.nav.familie.tidslinje.Null eller no.nav.familie.tidslinje.PeriodeVerdi. + */ +fun Tidslinje.biFunksjon( + annen: Tidslinje, + kombineringsfunksjon: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): Tidslinje { + val lst: MutableList> = mutableListOf() + var kopi1 = this + var kopi2 = annen + + if (this.tidsEnhet != annen.tidsEnhet) { + kopi1 = this.konverterTilDag() + kopi2 = annen.konverterTilDag() + } + + val (kopi3, kopi4) = konverterTilSammeLengde(kopi1, kopi2) + + val it1: Iterator> = kopi3.innhold.iterator() + val it2: Iterator> = kopi4.innhold.iterator() + + var tmpTidslinjePeriode: TidslinjePeriode + + var tidslinjePeriode1: TidslinjePeriode? = null + var tidslinjePeriode2: TidslinjePeriode? = null + + var lengde1 = 0 + var lengde2 = 0 + + while (it1.hasNext() || it2.hasNext()) { + tidslinjePeriode1 = tidslinjePeriode1 ?: it1.next() + tidslinjePeriode2 = tidslinjePeriode2 ?: it2.next() + + if (tidslinjePeriode2.erUendelig && tidslinjePeriode1.erUendelig) { + lst.add( + tidslinjePeriode1.biFunksjon( + operand = tidslinjePeriode2, + lengde = INF, + erUendelig = true, + operator = kombineringsfunksjon, + ), + ) + break + } + + lengde1 = if (lengde1 <= 0) tidslinjePeriode1.lengde else lengde1 + lengde2 = if (lengde2 <= 0) tidslinjePeriode2.lengde else lengde2 + + while (lengde1 > 0 && lengde2 > 0) { + if (lengde1 < lengde2) { + lengde2 -= lengde1 + tmpTidslinjePeriode = + tidslinjePeriode1.biFunksjon( + operand = tidslinjePeriode2, + lengde = lengde1, + erUendelig = false, + operator = kombineringsfunksjon, + ) + lengde1 = 0 + } else { + lengde1 -= lengde2 + tmpTidslinjePeriode = + tidslinjePeriode1.biFunksjon( + operand = tidslinjePeriode2, + lengde = lengde2, + erUendelig = false, + operator = kombineringsfunksjon, + ) + lengde2 = 0 + } + lst.add(tmpTidslinjePeriode) + } + + if (lengde1 <= 0) { + tidslinjePeriode1 = null + } + if (lengde2 <= 0) { + tidslinjePeriode2 = null + } + } + + val resultatTidslinje = Tidslinje(kopi3.startsTidspunkt, lst, kopi3.tidsEnhet) + + @Suppress("UNCHECKED_CAST") + resultatTidslinje.foreldre.add(kopi3 as Tidslinje) + @Suppress("UNCHECKED_CAST") + resultatTidslinje.foreldre.add(kopi4 as Tidslinje) + + return resultatTidslinje + .medTittel(this.tittel) +} + +/** + * Beregner en ny tidslinje ved bruk av [operator] basert på tidslinjene this og [operand]. + * Går gjennom alle periodene i hver tidslinje og beregner nye perioder. + * Hver nye periode som blir lagt inn i den resulterende tidslinja starter der den forrige stoppet og + * har lengde lik det minste tidsrommet hvor input tidslinjene har konstant verdi. + * Påfølgende perioder med lik verdi, blir slått sammen til en periode i den resulterende tidslinja. + * [Operator] blir brukt for å beregne verdiene til de generete periodene basert på verdiene til input tidslinjene i + * de respektive tidsrommene. Om en av input-tidslinjene er uendleig(dvs at den siste perioden har uendelig varighet), vil + * også den resulterende tidslinja være uendelig. + * Denne metoden krever at de to input tidslinjene og den resulterende tidslinja består av samme type verdier T. + */ +fun Tidslinje.binærOperator( + operand: Tidslinje, + operator: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): Tidslinje = this.biFunksjon(operand, operator) + +/** + * Lager en ny tidslengde fra en streng [innhold] og en mapper [mapper]. + * Mappere må inneholde en mapping for enhver char i strengen til et annet objekt av typen [R]. + * [tidsEnhet] spesifiserer hvor lang tid hver char skal vare. + * Periodene i den resulterende tidslinja vil ha lengder angitt i dager. + * Tidslunja starter på [startsTidspunkt] + */ +fun Tidslinje.Companion.lagTidslinjeFraStreng( + innhold: String, + startDato: LocalDate, + mapper: Map, + tidsEnhet: TidsEnhet, +): Tidslinje { + val lst: MutableList> = mutableListOf() + + innhold.forEach { + if (mapper[it] == null) { + throw java.lang.IllegalArgumentException("Kunne ikke mappe fra char $it til et objekt") + } + lst.add(TidslinjePeriode(mapper[it], 1, false)) + } + + return Tidslinje(startDato, lst, tidsEnhet) +} + +/** + * Initialiserer en tidslinje fra listen [innhold]. + * [tidsEnhet] spesifiserer hvor lange periodene for hvert element varer. + * Periodene i den resulterende tidslinja vil ha lengder angitt i dager. + * Tidslinja starter på [startsTidspunkt] og behandler [nullVerdi] som nullverdi. + */ +fun Tidslinje.Companion.lagTidslinjeFraListe( + innhold: List, + startDato: LocalDate, + tidsEnhet: TidsEnhet, +): Tidslinje { + val perioder = + innhold.map { + TidslinjePeriode(it, 1, false) + } + + return Tidslinje(startDato, perioder, tidsEnhet = tidsEnhet) +} + +/** + * Slår sammen en liste av tidslinjer med samme type ved å ta i bruk reduce. [operator] bestemmer regelen som skal brukes + * når man slår sammen to liste-elementer. [nullVerdi] må sendes inn fordi denne trengs av binærOperator. + */ +fun List>.slåSammenLikeTidslinjer( + operator: (elem1: PeriodeVerdi, elem2: PeriodeVerdi) -> PeriodeVerdi, +): Tidslinje { + if (this.isEmpty()) { + throw java.lang.IllegalArgumentException("Lista kan ikke være tom") + } + + val resultatTidslinje: Tidslinje + + if (this.size == 1) { + resultatTidslinje = Tidslinje(this[0].startsTidspunkt, this[0].innhold, this[0].tidsEnhet) + @Suppress("UNCHECKED_CAST") + resultatTidslinje.foreldre.add(this[0] as Tidslinje) + } else { + resultatTidslinje = + this.reduce { t1, t2 -> t1.binærOperator(t2) { elem1, elem2 -> operator(elem1, elem2) } }.fjernForeldre() + resultatTidslinje.foreldre.addAll(this.filterIsInstance>()) + } + + return resultatTidslinje +} + +fun Tidslinje.fjernForeldre(): Tidslinje { + this.foreldre.clear() + return this +} + +fun Tidslinje.hentVerdier(): List = this.innhold.slåSammenLike().map { it.periodeVerdi.verdi } + +/** + * Summerer opp tiden for hver periode og legger inn i en TidslinjePeriodeMedDato + * + * Om vi har en tidslinje med starttidspunkt 1. januar og tre perioder som varer én månde hver med verdiene a, b og c + * vil resulatet bli: + * + * List(TidslinjePeriodeMedDato( + * verdi: a + * fom: 1. januar + * tom: 31. januar + * ) + * TidslinjePeriodeMedDato( + * verdi: b + * fom: 1. februar + * tom: 28. februar + * ) + * TidslinjePeriodeMedDato( + * verdi: c + * fom: 1. mars + * tom: 31. mars + * )) + * + */ +fun Tidslinje.tilTidslinjePerioderMedDato(): List> { + val (tidslinjePeriodeMedLocalDateListe, _) = + this.innhold.fold(Pair(emptyList>(), 0L)) { + ( + tidslinjePeriodeMedLocalDateListe: List>, + tidFraStarttidspunktFom: Long, + ), + tidslinjePeriode, + -> + val tidFraStarttidspunktTilNesteFom = tidFraStarttidspunktFom + tidslinjePeriode.lengde + + Pair( + tidslinjePeriodeMedLocalDateListe + + TidslinjePeriodeMedDato( + periodeVerdi = tidslinjePeriode.periodeVerdi, + fom = + TidslinjePeriodeMedDato.Dato( + this.startsTidspunkt.leggTil( + tidsEnhet, + tidFraStarttidspunktFom, + ), + ), + tom = + if (tidslinjePeriode.erUendelig) { + TidslinjePeriodeMedDato.Dato(PRAKTISK_SENESTE_DAG) + } else { + TidslinjePeriodeMedDato.Dato( + this.startsTidspunkt + .leggTil(tidsEnhet, tidFraStarttidspunktTilNesteFom) + .minusDays(1), + ) + }, + ), + tidFraStarttidspunktTilNesteFom, + ) + } + return tidslinjePeriodeMedLocalDateListe +} + +private fun LocalDate.leggTil( + tidsEnhet: TidsEnhet, + antall: Long, +): LocalDate = + when (tidsEnhet) { + TidsEnhet.DAG -> this.plusDays(antall) + TidsEnhet.UKE -> this.plusWeeks(antall) + TidsEnhet.MÅNED -> this.plusMonths(antall) + TidsEnhet.ÅR -> this.plusYears(antall) + } + +fun Tidslinje.kombinerMed( + annen: Tidslinje, + kombineringsfunksjon: (elem1: T?, elem2: R?) -> RESULTAT?, +): Tidslinje = + this.biFunksjon(annen) { periodeverdiVenstre, periodeverdiHøyre -> + kombineringsfunksjon(periodeverdiVenstre.verdi, periodeverdiHøyre.verdi) + .tilPeriodeVerdi() + } + +fun Tidslinje.kombinerMed( + tidslinje2: Tidslinje, + tidslinje3: Tidslinje, + kombineringsfunksjon: (elem1: T?, elem2: R?, elem3: S?) -> RESULTAT?, +): Tidslinje { + val tidslinje1Og2: Tidslinje> = + this.biFunksjon(tidslinje2) { elem1PeriodeVerdi, elem2PeriodeVerdi -> + Pair(elem1PeriodeVerdi.verdi, elem2PeriodeVerdi.verdi).tilPeriodeVerdi() + } + + return tidslinje1Og2.biFunksjon(tidslinje3) { elem1Og2, elem3PeriodeVerdi -> + val (elem1, elem2) = elem1Og2.verdi ?: Pair(null, null) + kombineringsfunksjon(elem1, elem2, elem3PeriodeVerdi.verdi).tilPeriodeVerdi() + } +} + +fun Tidslinje.tilPerioder(): List> = this.tilTidslinjePerioderMedDato().map { it.tilPeriode() } + +fun Tidslinje.tilPerioderIkkeNull(): List> = this.tilPerioder().filtrerIkkeNull() + +fun Tidslinje.slåSammenLikePerioder(): Tidslinje = + Tidslinje( + startsTidspunkt = this.startsTidspunkt, + perioder = this.innhold.slåSammenLike(), + tidsEnhet = this.tidsEnhet, + ) diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/PeriodeTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/PeriodeTest.kt new file mode 100644 index 00000000..f2e94922 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/PeriodeTest.kt @@ -0,0 +1,161 @@ +package no.nav.familie.tidslinje + +import no.nav.familie.tidslinje.utvidelser.kombinerMed +import no.nav.familie.tidslinje.utvidelser.tilPerioder +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class PeriodeTest { + private val førsteJanuar = LocalDate.of(2022, 1, 1) + private val sisteDagIJanuar = LocalDate.of(2022, 1, 31) + private val førsteFebruar = LocalDate.of(2022, 2, 1) + private val sisteDagIFebruar = LocalDate.of(2022, 2, 28) + private val førsteMars = LocalDate.of(2022, 3, 1) + private val sisteDagIMars = LocalDate.of(2022, 3, 31) + private val førsteApril = LocalDate.of(2022, 4, 1) + private val sisteDagIApril = LocalDate.of(2022, 4, 30) + + @Test + fun `tilPerioder - Skal beholde datoer ved manipulering på tidslinje`() { + val tidslinjeA = listOf(Periode("a", førsteJanuar, sisteDagIMars)).tilTidslinje() + val tidslinjeB = listOf(Periode("b", førsteFebruar, sisteDagIFebruar)).tilTidslinje() + + val periode = + tidslinjeA + .kombinerMed(tidslinjeB) { a, b -> + b ?: a + }.tilPerioder() + + Assertions.assertEquals(3, periode.size) + + Assertions.assertEquals(førsteJanuar, periode[0].fom) + Assertions.assertEquals(sisteDagIJanuar, periode[0].tom) + + Assertions.assertEquals(førsteFebruar, periode[1].fom) + Assertions.assertEquals(sisteDagIFebruar, periode[1].tom) + + Assertions.assertEquals(førsteMars, periode[2].fom) + Assertions.assertEquals(sisteDagIMars, periode[2].tom) + } + + @Test + fun `tilPerioder - skal finne fjernet perioder fra tidslinjer`() { + val tidslinjeA = listOf(Periode("a", førsteJanuar, sisteDagIMars)).tilTidslinje() + val tidslinjeB = + listOf( + Periode("b", førsteJanuar, sisteDagIJanuar), + Periode("b", førsteMars, sisteDagIMars), + ).tilTidslinje() + + val periode = tidslinjeA.kombinerMed(tidslinjeB) { v1, v2 -> if (v2 == null) v1 else null }.tilPerioder().filtrerIkkeNull() + + Assertions.assertEquals(1, periode.size) + + Assertions.assertEquals(førsteFebruar, periode[0].fom) + Assertions.assertEquals(sisteDagIFebruar, periode[0].tom) + } + + @Test + fun `tilPerioder - skal finne lagret perioder fra tidslinjer`() { + val tidslinjeA = listOf(Periode("a", førsteJanuar, sisteDagIMars)).tilTidslinje() + val tidslinjeB = listOf(Periode("b", førsteJanuar, sisteDagIApril)).tilTidslinje() + + val periode = tidslinjeB.kombinerMed(tidslinjeA) { v1, v2 -> if (v2 == null) v1 else null }.tilPerioder().filtrerIkkeNull() + + Assertions.assertEquals(1, periode.size) + + Assertions.assertEquals(førsteApril, periode[0].fom) + Assertions.assertEquals(sisteDagIApril, periode[0].tom) + } + + @Test + fun `tilPerioder - Skal kunne håndtere splitt i tidslinje`() { + val perioder = + listOf( + Periode("a", førsteJanuar, sisteDagIJanuar), + Periode("c", førsteMars, sisteDagIMars), + ).tilTidslinje().tilPerioder() + + Assertions.assertEquals(3, perioder.size) + + Assertions.assertEquals(førsteJanuar, perioder[0].fom) + Assertions.assertEquals(sisteDagIJanuar, perioder[0].tom) + Assertions.assertEquals("a", perioder[0].verdi) + + Assertions.assertEquals(førsteFebruar, perioder[1].fom) + Assertions.assertEquals(sisteDagIFebruar, perioder[1].tom) + Assertions.assertEquals(null, perioder[1].verdi) + + Assertions.assertEquals(førsteMars, perioder[2].fom) + Assertions.assertEquals(sisteDagIMars, perioder[2].tom) + Assertions.assertEquals("c", perioder[2].verdi) + } + + @Test + fun `tilPerioder - Skal kunne håndtere nullverdier i starten og slutten av tidslinje`() { + val perioder = + listOf( + Periode("a", null, sisteDagIJanuar), + Periode("b", førsteFebruar, sisteDagIFebruar), + Periode("c", førsteMars, null), + ).tilTidslinje().tilPerioder() + + Assertions.assertEquals(3, perioder.size) + + Assertions.assertEquals(null, perioder[0].fom) + Assertions.assertEquals(sisteDagIJanuar, perioder[0].tom) + Assertions.assertEquals("a", perioder[0].verdi) + + Assertions.assertEquals(førsteFebruar, perioder[1].fom) + Assertions.assertEquals(sisteDagIFebruar, perioder[1].tom) + Assertions.assertEquals("b", perioder[1].verdi) + + Assertions.assertEquals(førsteMars, perioder[2].fom) + Assertions.assertEquals(null, perioder[2].tom) + Assertions.assertEquals("c", perioder[2].verdi) + } + + @Test + fun `tilTidslinje - Skal kaste feil dersom det er flere tom-datoer med nullverdi`() { + val perioder = + listOf( + Periode("a", null, sisteDagIJanuar), + Periode("b", førsteFebruar, null), + Periode("c", førsteMars, null), + ) + + Assertions.assertThrows(Exception::class.java) { perioder.tilTidslinje() } + } + + @Test + fun `tilTidslinje - Skal kaste feil dersom det er flere fom-datoer med nullverdi`() { + val perioder = + listOf( + Periode("a", null, sisteDagIJanuar), + Periode("b", null, sisteDagIFebruar), + Periode("c", førsteMars, null), + ) + + Assertions.assertThrows(Exception::class.java) { perioder.tilTidslinje() } + } + + @Test + fun `tilTidslinje - Skal kaste feil om det er overlapp i periodene`() { + val perioder = + listOf( + Periode("a", null, sisteDagIJanuar), + Periode("b", førsteFebruar, sisteDagIMars), + Periode("c", førsteMars, null), + ) + + Assertions.assertThrows(Exception::class.java) { perioder.tilTidslinje() } + } + + @Test + fun `tilTidslinje og tilPerioder - Skal håndtere tom liste`() { + val perioder = emptyList>() + + Assertions.assertEquals(0, perioder.tilTidslinje().tilPerioder().size) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/TidslinjePeriodeMedDatoTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/TidslinjePeriodeMedDatoTest.kt new file mode 100644 index 00000000..6c3c20f8 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/TidslinjePeriodeMedDatoTest.kt @@ -0,0 +1,139 @@ +package no.nav.familie.tidslinje + +import no.nav.familie.tidslinje.utvidelser.biFunksjon +import no.nav.familie.tidslinje.utvidelser.tilTidslinjePerioderMedDato +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TidslinjePeriodeMedDatoTest { + private val førsteJanuar = LocalDate.of(2022, 1, 1) + private val sisteDagIJanuar = LocalDate.of(2022, 1, 31) + private val førsteFebruar = LocalDate.of(2022, 2, 1) + private val sisteDagIFebruar = LocalDate.of(2022, 2, 28) + private val førsteMars = LocalDate.of(2022, 3, 1) + private val sisteDagIMars = LocalDate.of(2022, 3, 31) + + @Test + fun `tilTidslinjePerioderMedDato - Skal beholde datoer ved manipulering på tidslinje`() { + val tidslinjeA = listOf(TidslinjePeriodeMedDato("a", førsteJanuar, sisteDagIMars)).tilTidslinje() + val tidslinjeB = listOf(TidslinjePeriodeMedDato("b", førsteFebruar, sisteDagIFebruar)).tilTidslinje() + + val tidslinjePerioderMedDato = + tidslinjeA + .biFunksjon(tidslinjeB) { a, b -> + if (b is Verdi) { + b + } else { + a + } + }.tilTidslinjePerioderMedDato() + + Assertions.assertEquals(3, tidslinjePerioderMedDato.size) + + Assertions.assertEquals(førsteJanuar, tidslinjePerioderMedDato[0].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIJanuar, tidslinjePerioderMedDato[0].tom.tilLocalDateEllerNull()) + + Assertions.assertEquals(førsteFebruar, tidslinjePerioderMedDato[1].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIFebruar, tidslinjePerioderMedDato[1].tom.tilLocalDateEllerNull()) + + Assertions.assertEquals(førsteMars, tidslinjePerioderMedDato[2].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIMars, tidslinjePerioderMedDato[2].tom.tilLocalDateEllerNull()) + } + + @Test + fun `tilTidslinjePerioderMedDato - Skal kunne håndtere splitt i tidslinje`() { + val tidslinjePerioderMedDato = + listOf( + TidslinjePeriodeMedDato("a", førsteJanuar, sisteDagIJanuar), + TidslinjePeriodeMedDato("c", førsteMars, sisteDagIMars), + ).tilTidslinje().tilTidslinjePerioderMedDato() + + Assertions.assertEquals(3, tidslinjePerioderMedDato.size) + + Assertions.assertEquals(førsteJanuar, tidslinjePerioderMedDato[0].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIJanuar, tidslinjePerioderMedDato[0].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Verdi::class.java, tidslinjePerioderMedDato[0].periodeVerdi::class.java) + Assertions.assertEquals("a", tidslinjePerioderMedDato[0].periodeVerdi.verdi) + + Assertions.assertEquals(førsteFebruar, tidslinjePerioderMedDato[1].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIFebruar, tidslinjePerioderMedDato[1].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Udefinert::class.java, tidslinjePerioderMedDato[1].periodeVerdi::class.java) + Assertions.assertEquals(null, tidslinjePerioderMedDato[1].periodeVerdi.verdi) + + Assertions.assertEquals(førsteMars, tidslinjePerioderMedDato[2].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIMars, tidslinjePerioderMedDato[2].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Verdi::class.java, tidslinjePerioderMedDato[2].periodeVerdi::class.java) + Assertions.assertEquals("c", tidslinjePerioderMedDato[2].periodeVerdi.verdi) + } + + @Test + fun `tilTidslinjePerioderMedDato - Skal kunne håndtere nullverdier i starten og slutten av tidslinje`() { + val tidslinjePerioderMedDato = + listOf( + TidslinjePeriodeMedDato("a", null, sisteDagIJanuar), + TidslinjePeriodeMedDato("b", førsteFebruar, sisteDagIFebruar), + TidslinjePeriodeMedDato("c", førsteMars, null), + ).tilTidslinje().tilTidslinjePerioderMedDato() + + Assertions.assertEquals(3, tidslinjePerioderMedDato.size) + + Assertions.assertEquals(null, tidslinjePerioderMedDato[0].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIJanuar, tidslinjePerioderMedDato[0].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Verdi::class.java, tidslinjePerioderMedDato[0].periodeVerdi::class.java) + Assertions.assertEquals("a", tidslinjePerioderMedDato[0].periodeVerdi.verdi) + + Assertions.assertEquals(førsteFebruar, tidslinjePerioderMedDato[1].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIFebruar, tidslinjePerioderMedDato[1].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Verdi::class.java, tidslinjePerioderMedDato[1].periodeVerdi::class.java) + Assertions.assertEquals("b", tidslinjePerioderMedDato[1].periodeVerdi.verdi) + + Assertions.assertEquals(førsteMars, tidslinjePerioderMedDato[2].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(null, tidslinjePerioderMedDato[2].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Verdi::class.java, tidslinjePerioderMedDato[2].periodeVerdi::class.java) + Assertions.assertEquals("c", tidslinjePerioderMedDato[2].periodeVerdi.verdi) + } + + @Test + fun `tilTidslinje - Skal kaste feil dersom det er flere tom-datoer med nullverdi`() { + val tidslinjePerioderMedDato = + listOf( + TidslinjePeriodeMedDato("a", null, sisteDagIJanuar), + TidslinjePeriodeMedDato("b", førsteFebruar, null), + TidslinjePeriodeMedDato("c", førsteMars, null), + ) + + Assertions.assertThrows(Exception::class.java) { tidslinjePerioderMedDato.tilTidslinje() } + } + + @Test + fun `tilTidslinje - Skal kaste feil dersom det er flere fom-datoer med nullverdi`() { + val tidslinjePerioderMedDato = + listOf( + TidslinjePeriodeMedDato("a", null, sisteDagIJanuar), + TidslinjePeriodeMedDato("b", null, sisteDagIFebruar), + TidslinjePeriodeMedDato("c", førsteMars, null), + ) + + Assertions.assertThrows(Exception::class.java) { tidslinjePerioderMedDato.tilTidslinje() } + } + + @Test + fun `tilTidslinje - Skal kaste feil om det er overlapp i periodene`() { + val tidslinjePerioderMedDato = + listOf( + TidslinjePeriodeMedDato("a", null, sisteDagIJanuar), + TidslinjePeriodeMedDato("b", førsteFebruar, sisteDagIMars), + TidslinjePeriodeMedDato("c", førsteMars, null), + ) + + Assertions.assertThrows(Exception::class.java) { tidslinjePerioderMedDato.tilTidslinje() } + } + + @Test + fun `tilTidslinje og tilTidslinjePerioderMedDato - Skal håndtere tom liste`() { + val tidslinjePerioderMedDato = emptyList>() + + Assertions.assertEquals(0, tidslinjePerioderMedDato.tilTidslinje().tilTidslinjePerioderMedDato().size) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerMedTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerMedTest.kt new file mode 100644 index 00000000..a959b789 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerMedTest.kt @@ -0,0 +1,85 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Periode +import no.nav.familie.tidslinje.tilTidslinje +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class KombinerMedTest { + private val førsteJanuar = LocalDate.of(2022, 1, 1) + private val sisteDagIJanuar = LocalDate.of(2022, 1, 31) + private val førsteFebruar = LocalDate.of(2022, 2, 1) + private val sisteDagIFebruar = LocalDate.of(2022, 2, 28) + private val førsteMars = LocalDate.of(2022, 3, 1) + private val sisteDagIMars = LocalDate.of(2022, 3, 31) + private val førsteApril = LocalDate.of(2022, 4, 1) + private val sisteDagIApril = LocalDate.of(2022, 4, 30) + + /** + * a = |111-| + * b = |-2-2| + * (a ?: 0) + (b ?: 0) = |1312| + **/ + @Test + fun `kombinerMed - Skal kombinere overlappende verdier på tidslinjene`() { + val tidslinjeA = listOf(Periode(1, førsteJanuar, sisteDagIMars)).tilTidslinje() + val tidslinjeB = + listOf(Periode(2, førsteFebruar, sisteDagIFebruar), Periode(2, førsteApril, sisteDagIApril)).tilTidslinje() + + val perioder = + tidslinjeA + .kombinerMed(tidslinjeB) { verdiFraTidslinjeA, verdiFraTidslinjeB -> + (verdiFraTidslinjeA ?: 0) + (verdiFraTidslinjeB ?: 0) + }.tilPerioder() + + Assertions.assertEquals(4, perioder.size) + + Assertions.assertEquals(førsteJanuar, perioder[0].fom) + Assertions.assertEquals(sisteDagIJanuar, perioder[0].tom) + Assertions.assertEquals(1, perioder[0].verdi) + + Assertions.assertEquals(førsteFebruar, perioder[1].fom) + Assertions.assertEquals(sisteDagIFebruar, perioder[1].tom) + Assertions.assertEquals(3, perioder[1].verdi) + + Assertions.assertEquals(førsteMars, perioder[2].fom) + Assertions.assertEquals(sisteDagIMars, perioder[2].tom) + Assertions.assertEquals(1, perioder[2].verdi) + + Assertions.assertEquals(førsteApril, perioder[3].fom) + Assertions.assertEquals(sisteDagIApril, perioder[3].tom) + Assertions.assertEquals(2, perioder[3].verdi) + } + + /** + * a = |1--| + * b = |--2| + * (a ?: 0) + (b ?: 0) = |102| + **/ + @Test + fun `kombinerMed - Skal ikke kombinere verdier som ikke overlapper`() { + val tidslinjeA = listOf(Periode(1, førsteJanuar, sisteDagIJanuar)).tilTidslinje() + val tidslinjeB = listOf(Periode(2, førsteMars, sisteDagIMars)).tilTidslinje() + + val perioder = + tidslinjeA + .kombinerMed(tidslinjeB) { verdiFraTidslinjeA, verdiFraTidslinjeB -> + (verdiFraTidslinjeA ?: 0) + (verdiFraTidslinjeB ?: 0) + }.tilPerioder() + + Assertions.assertEquals(3, perioder.size) + + Assertions.assertEquals(førsteJanuar, perioder[0].fom) + Assertions.assertEquals(sisteDagIJanuar, perioder[0].tom) + Assertions.assertEquals(1, perioder[0].verdi) + + Assertions.assertEquals(førsteFebruar, perioder[1].fom) + Assertions.assertEquals(sisteDagIFebruar, perioder[1].tom) + Assertions.assertEquals(0, perioder[1].verdi) + + Assertions.assertEquals(førsteMars, perioder[2].fom) + Assertions.assertEquals(sisteDagIMars, perioder[2].tom) + Assertions.assertEquals(2, perioder[2].verdi) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerTidslinjerTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerTidslinjerTest.kt new file mode 100644 index 00000000..9b99cab5 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KombinerTidslinjerTest.kt @@ -0,0 +1,62 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.TidslinjePeriodeMedDato +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.tilTidslinje +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class KombinerTidslinjerTest { + private val førsteJanuar = LocalDate.of(2022, 1, 1) + private val sisteDagIJanuar = LocalDate.of(2022, 1, 31) + private val førsteFebruar = LocalDate.of(2022, 2, 1) + private val sisteDagIFebruar = LocalDate.of(2022, 2, 28) + private val førsteMars = LocalDate.of(2022, 3, 1) + private val sisteDagIMars = LocalDate.of(2022, 3, 31) + private val førsteApril = LocalDate.of(2022, 4, 1) + private val sisteDagIApril = LocalDate.of(2022, 4, 30) + private val førsteMai = LocalDate.of(2022, 5, 1) + private val sisteDagIMai = LocalDate.of(2022, 5, 31) + + @Test + fun `kombinerTidslinjer - skal kunne kombinere liste av tidslinjer til én tilslinje med lister som verdier`() { + val tidslinjeA = listOf(TidslinjePeriodeMedDato("a", førsteJanuar, sisteDagIJanuar)).tilTidslinje() + val tidslinjeB = listOf(TidslinjePeriodeMedDato("b", førsteJanuar, sisteDagIFebruar)).tilTidslinje() + val tidslinjeC = listOf(TidslinjePeriodeMedDato("c", førsteJanuar, sisteDagIMars)).tilTidslinje() + val nullTidlisline = + listOf( + TidslinjePeriodeMedDato( + Null(), + TidslinjePeriodeMedDato.Dato(førsteMai), + TidslinjePeriodeMedDato.Dato(sisteDagIMai), + ), + ).tilTidslinje() + + val kombinerteTidslinjerPerioder = + listOf(tidslinjeA, tidslinjeB, tidslinjeC, nullTidlisline).slåSammen().tilTidslinjePerioderMedDato() + + Assertions.assertEquals(5, kombinerteTidslinjerPerioder.size) + + Assertions.assertEquals(førsteJanuar, kombinerteTidslinjerPerioder[0].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIJanuar, kombinerteTidslinjerPerioder[0].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(listOf("a", "b", "c"), kombinerteTidslinjerPerioder[0].periodeVerdi.verdi) + + Assertions.assertEquals(førsteFebruar, kombinerteTidslinjerPerioder[1].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIFebruar, kombinerteTidslinjerPerioder[1].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(listOf("b", "c"), kombinerteTidslinjerPerioder[1].periodeVerdi.verdi) + + Assertions.assertEquals(førsteMars, kombinerteTidslinjerPerioder[2].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIMars, kombinerteTidslinjerPerioder[2].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(listOf("c"), kombinerteTidslinjerPerioder[2].periodeVerdi.verdi) + + Assertions.assertEquals(førsteApril, kombinerteTidslinjerPerioder[3].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIApril, kombinerteTidslinjerPerioder[3].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Udefinert::class.java, kombinerteTidslinjerPerioder[3].periodeVerdi::class.java) + + Assertions.assertEquals(førsteMai, kombinerteTidslinjerPerioder[4].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIMai, kombinerteTidslinjerPerioder[4].tom.tilLocalDateEllerNull()) + Assertions.assertEquals(Null::class.java, kombinerteTidslinjerPerioder[4].periodeVerdi::class.java) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KonverterTidTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KonverterTidTest.kt new file mode 100644 index 00000000..b7637fd0 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/KonverterTidTest.kt @@ -0,0 +1,650 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.INF +import no.nav.familie.tidslinje.TidsEnhet +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +class KonverterTidTest { + @Test + fun `Kan gå fra dager til måneder`() { + val dato1Start = LocalDate.of(2022, 7, 1) + val dato1Slutt = LocalDate.of(2022, 7, 31) + + val dato2Start = LocalDate.of(2022, 8, 1) + val dato2Slutt = LocalDate.of(2022, 8, 31) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = tidslinje.konverterTilMåned { _, it -> it.last().last().periodeVerdi }.høyreShift() + val correct = listOf(1, 2) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato2Start, tidslinjeMåned.startsTidspunkt) + } + + @Test + fun `Kan gå fra dager til måneder test 2`() { + val dato1Start = LocalDate.of(2022, 7, 5) + val dato1Slutt = LocalDate.of(2022, 8, 4) + + val dato2Start = LocalDate.of(2022, 8, 5) + val dato2Slutt = LocalDate.of(2022, 9, 3) + + val dato3Start = LocalDate.of(2022, 9, 4) + val dato3Slutt = LocalDate.of(2022, 10, 31) + + val dato4Start = LocalDate.of(2022, 11, 1) + val dato4Slutt = LocalDate.of(2022, 11, 30) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = tidslinje.konverterTilMåned { _, it -> it.last().last().periodeVerdi }.høyreShift() + val correct = listOf(1, 2, 3, 4) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato2Start.withDayOfMonth(1), tidslinjeMåned.startsTidspunkt) + } + + @Test + fun `kan konvertere til måneder når det er flere TidslinjePerioder i løpet av en måned`() { + val dato1Start = LocalDate.of(2022, 7, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 20) + + val dato3Start = LocalDate.of(2022, 7, 21) + val dato3Slutt = LocalDate.of(2022, 10, 31) + + val dato4Start = LocalDate.of(2022, 11, 1) + val dato4Slutt = LocalDate.of(2022, 11, 30) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = tidslinje.konverterTilMåned { _, it -> it.last().last().periodeVerdi }.høyreShift() + val correct = listOf(3, 4) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato1Start.plusMonths(1).withDayOfMonth(1), tidslinjeMåned.startsTidspunkt) + } + + @Test + fun `flere måneder med flere TidslinjePerioder i hver måned`() { + val dato1Start = LocalDate.of(2022, 7, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 8, 11) + + val dato4Start = LocalDate.of(2022, 8, 12) + val dato4Slutt = LocalDate.of(2022, 8, 31) + + val dato5Start = LocalDate.of(2022, 9, 1) + val dato5Slutt = LocalDate.of(2022, 9, 3) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(5, dato5Start.until(dato5Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = tidslinje.konverterTilMåned { _, it -> it.last().last().periodeVerdi }.høyreShift() + val correct = listOf(2, 4, 5) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato1Start.plusMonths(1).withDayOfMonth(1), tidslinjeMåned.startsTidspunkt) + } + + @Test + fun `TidslinjePeriode går over flere måneder på slutten`() { + val dato1Start = LocalDate.of(2022, 7, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val dato4Start = LocalDate.of(2022, 10, 12) + val dato4Slutt = LocalDate.of(2022, 11, 30) + + val dato5Start = LocalDate.of(2022, 12, 1) + val dato5Slutt = LocalDate.of(2023, 3, 5) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(5, dato5Start.until(dato5Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = tidslinje.splittPåMåned() + val correct = listOf(2, 3, 3, 4, 4, 5, 5, 5, 5) + + Assertions.assertEquals(correct, tidslinjeMåned.map { it.last().periodeVerdi.verdi }.toList()) + } + + @Test + fun `TidslinjePeriode går over flere måneder på slutten gg`() { + val dato1Start = LocalDate.of(2022, 7, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val dato4Start = LocalDate.of(2022, 10, 12) + val dato4Slutt = LocalDate.of(2022, 11, 30) + + val dato5Start = LocalDate.of(2022, 12, 1) + val dato5Slutt = LocalDate.of(2023, 3, 5) + + val tmp = + listOf( + TidslinjePeriode(1.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4.0, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(5.0, dato5Start.until(dato5Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = + tidslinje.konverterTilMåned { _, it -> + Verdi( + it + .last() + .map { it.periodeVerdi.verdi!! } + .toList() + .average(), + ) + } + val correct: List = listOf(1.5, 3.0, 3.5, 4.0, 5.0) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + } + + @Test + fun `kunne beregne månedlig valutakurs fra dagskurser`() { + val test = + Tidslinje.lagTidslinjeFraListe( + listOf( + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + 26.0, + 27.0, + 28.0, + 29.0, + 30.0, + 31.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + 26.0, + 27.0, + 28.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + 26.0, + 27.0, + 28.0, + 29.0, + 30.0, + 31.0, + ), + LocalDate.of(2022, 1, 1), + TidsEnhet.DAG, + ) + + val tidslinjeMåned = + test + .konverterTilMåned { _, it -> + Verdi( + it + .last() + .map { it.periodeVerdi.verdi!! } + .toList() + .average(), + ) + }.høyreShift() + val correct = listOf(16.0, 14.5, 16.0) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals( + LocalDate.of(2022, 1, 1).plusMonths(1).withDayOfMonth(1), + tidslinjeMåned.startsTidspunkt, + ) + } + + @Test + fun `tester med operatoren verdien som har vart lengst blir valgt for inneværende TidslinjePeriode`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val dato4Start = LocalDate.of(2022, 10, 12) + val dato4Slutt = LocalDate.of(2022, 11, 30) + + val dato5Start = LocalDate.of(2022, 12, 1) + val dato5Slutt = LocalDate.of(2023, 3, 5) + + val tmp = + listOf( + TidslinjePeriode(1.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4.0, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(5.0, dato5Start.until(dato5Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val splittPåMåned = tidslinje.splittPåMåned() + + val tidslinjeMåned = tidslinje.konverterTilMåned { _, it -> it.last().maxBy { it.lengde }.periodeVerdi } + + val correctBeforeTidslinje: List = + listOf( + 1.0, + 2.0, + 3.0, + 3.0, + 4.0, + 4.0, + 5.0, + 5.0, + 5.0, + 5.0, + ) // Dette er sånn lista skulle vært, men siden Tidslinje slår sammen like blir det + val correct: List = listOf(1.0, 2.0, 3.0, 4.0, 5.0) + + // Assertions.assertEquals(tidslinjeMåned.innhold[2].lengde, (dato3Start.until(LocalDate.of(2022, 9, 30), ChronoUnit.DAYS).toInt() + 1)) + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + Assertions.assertEquals( + correctBeforeTidslinje, + splittPåMåned.map { it.maxBy { periode -> periode.lengde }.periodeVerdi.verdi }, + ) + } + + @Test + fun `tester med boolske verdier`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val dato4Start = LocalDate.of(2022, 10, 12) + val dato4Slutt = LocalDate.of(2022, 11, 30) + + val dato5Start = LocalDate.of(2022, 12, 1) + val dato5Slutt = LocalDate.of(2023, 3, 5) + + val tmp = + listOf( + TidslinjePeriode(true, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(false, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(true, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(true, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(false, dato5Start.until(dato5Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = tidslinje.konverterTilMåned { _, it -> it.last().last().periodeVerdi } + + val correct: List = + listOf( + true, + false, + true, + false, + ) // Dette er sånn lista skulle vært, men siden Tidslinje slår sammen like blir det + + Assertions.assertEquals(tidslinjeMåned.innhold[2].lengde, 4) + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + // Assertions.assertEquals(correctBeforeTidslinje, splittPåMåned.map { it.maxBy { it.lengde }.periodeVerdi.verdi }) + } + + @Test + fun `Kan håndtere tidslinjer med uendelig slutt`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val dato4Start = LocalDate.of(2022, 10, 12) + val dato4Slutt = LocalDate.of(2022, 11, 10) + + val dato5Start = LocalDate.of(2022, 11, 11) + val dato5Slutt = LocalDate.of(2023, 3, 5) + + val tmp = + listOf( + TidslinjePeriode(1.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(2.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(3.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(4.0, dato4Start.until(dato4Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(5.0, dato5Start.until(dato5Slutt, ChronoUnit.DAYS).toInt() + 1, true), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val splittPåMåned = tidslinje.splittPåMåned() + + val tidslinjeMåned = + tidslinje.konverterTilMåned { _, it -> + it.last().maxBy { it.lengde }.periodeVerdi + } // velger den verdien som varer lengst innad i en måned. + + val correctBeforeTidslinje: List = + listOf( + 1.0, + 2.0, + 3.0, + 3.0, + 4.0, + 5.0, + 5.0, + ) // Dette er sånn lista skulle vært, men siden Tidslinje slår sammen like blir det + val correct: List = listOf(1.0, 2.0, 3.0, 4.0, 5.0) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi }.toList()) + Assertions.assertEquals(1, tidslinjeMåned.innhold[3].lengde) + assertTrue { INF <= tidslinjeMåned.innhold[4].lengde } + Assertions.assertEquals( + correctBeforeTidslinje, + splittPåMåned.map { it.maxBy { periode -> periode.lengde }.periodeVerdi.verdi }, + ) + assertTrue(tidslinjeMåned.innhold.last().erUendelig) + } + + @Test + fun `kan ta gjennomsnittet over to måneder for å bestemme verdien til en måned`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val tmp = + listOf( + TidslinjePeriode(5.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(7.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(15.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = + tidslinje.konverterTilMåned(1) { _, vindu -> + if (vindu[0].isEmpty()) { + Verdi(vindu[1].last().periodeVerdi.verdi!!) + } else { + val avg = (vindu[0].last().periodeVerdi.verdi!! + vindu[1].last().periodeVerdi.verdi!!) / 2 + Verdi(avg) + } + } + + val correct: List = listOf(5, (5 + 7) / 2, (7 + 15) / 2, 15) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi!!.toInt() }.toList()) + } + + @Test + fun `kan spesifisere at januar skal få en annen verdi enn andre måneder`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val tmp = + listOf( + TidslinjePeriode(5.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(7.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(15.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + var tidslinjeMåned = + tidslinje.konverterTilMåned(2) { dato, _ -> + if (dato.month == java.time.Month.JULY) { + Verdi(1.0) + } else { + Verdi( + 0.0, + ) + } + } + + var correct = listOf(0.0, 1.0, 0.0) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi!! }.toList()) + + tidslinjeMåned = + tidslinje.konverterTilMåned(2) { dato, _ -> + if (dato.month == java.time.Month.OCTOBER) { + Verdi(1.0) + } else { + Verdi( + 0.0, + ) + } + } + + correct = listOf(0.0, 1.0) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi!! }.toList()) + } + + @Test + fun `kan kovertere til måned med window 1 på begge sider når siste mnd er uendelig`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val tmp = + listOf( + TidslinjePeriode(5.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(7.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(15.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1, erUendelig = true), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = + tidslinje.konverterTilMåned(antallMndBakoverITid = 1, antallMndFremoverITid = 1) { _, vindu -> + if (vindu[0].isEmpty()) { + Verdi((vindu[1].last().periodeVerdi.verdi!! + vindu[2].last().periodeVerdi.verdi!!) / 2) + } else if (vindu[2].isEmpty()) { + Verdi((vindu[0].last().periodeVerdi.verdi!! + vindu[1].last().periodeVerdi.verdi!!) / 2) + } else { + val avg = + ( + vindu[0].last().periodeVerdi.verdi!! + + vindu[1].last().periodeVerdi.verdi!! + + vindu[2].last().periodeVerdi.verdi!! + ) / 3 + Verdi(avg) + } + } + + val correct: List = listOf((5 + 7) / 2, (5 + 7 + 15) / 3, (7 + 15) / 2, 15) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi!!.toInt() }.toList()) + } + + @Test + fun `kan kovertere til måned med window kun ett element og uendelig ende`() { + val dato1Start = LocalDate.of(2022, 6, 5) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 31) + + val dato3Start = LocalDate.of(2022, 8, 1) + val dato3Slutt = LocalDate.of(2022, 10, 11) + + val tmp = + listOf( + TidslinjePeriode(5.0, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(7.0, dato2Start.until(dato2Slutt, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(15.0, dato3Start.until(dato3Slutt, ChronoUnit.DAYS).toInt() + 1, erUendelig = true), + ) + + val tidslinje = Tidslinje(dato1Start, tmp) + + val tidslinjeMåned = + tidslinje.konverterTilMåned { _, vindu -> + Verdi(vindu[0].last().periodeVerdi.verdi!!) + } + + val correct: List = listOf(5, 7, 15) + + Assertions.assertEquals(correct, tidslinjeMåned.innhold.map { it.periodeVerdi.verdi!!.toInt() }.toList()) + assertTrue(tidslinjeMåned.innhold.last().erUendelig) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/MapOgStripTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/MapOgStripTest.kt new file mode 100644 index 00000000..f3f088db --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/MapOgStripTest.kt @@ -0,0 +1,108 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class MapOgStripTest { + private var lst1 = emptyList>() + private var t1: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + + private fun init(lst1: List>) { + this.lst1 = lst1 + this.t1 = Tidslinje(LocalDate.now(), lst1) + } + + @Test + fun `kan mappe verdiene i en tidslinje til verdier av en annen type`() { + init( + listOf( + TidslinjePeriode(5, 1, false), + TidslinjePeriode(3, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(1, 1, false), + ), + ) + + val tidslinje = + t1.map { + if (it.verdi!! > 1) { + Verdi(true) + } else { + Verdi(false) + } + } + + val korrekt = listOf(true, false) + + Assertions.assertEquals(korrekt, tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + } + + @Test + fun `kan mappe udefinert og null til ulike verdier`() { + init( + listOf( + TidslinjePeriode(5, 1, false), + TidslinjePeriode(null, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(Udefinert(), 1, false), + ), + ) + + val tidslinje = + t1.map { + when (it) { + is Null -> Verdi(1) + is Udefinert -> Verdi(2) + else -> Verdi(3) + } + } + + val korrekt = listOf(3, 1, 3, 2) + + Assertions.assertEquals(korrekt, tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + } + + @Test + fun `kan strippe vekk udefinert og null fra begynnelsen og slutten av en tidslinje`() { + init( + listOf( + TidslinjePeriode(null, 1, false), + TidslinjePeriode(5, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(Udefinert(), 1, false), + ), + ) + + val tidslinje = t1.trim(Udefinert(), Null()) + + val korrekt = listOf(5, 2) + + Assertions.assertEquals(korrekt, tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + Assertions.assertEquals(LocalDate.now().plusDays(1), tidslinje.startsTidspunkt) + } + + @Test + fun `kan strippe vekk TidslinjePerioderverider bestående av heltall`() { + init( + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(5, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(1, 1, false), + ), + ) + + val tidslinje = t1.trim(Verdi(1)) + + val korrekt = listOf(5, 2) + + Assertions.assertEquals(korrekt, tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + Assertions.assertEquals(LocalDate.now().plusDays(1), tidslinje.startsTidspunkt) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/OmregningTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/OmregningTest.kt new file mode 100644 index 00000000..7d6b1b62 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/OmregningTest.kt @@ -0,0 +1,142 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.TidsEnhet +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +class OmregningTest { + @Test + fun `kan omgjøre fra uke til dag`() { + val dato1Start = LocalDate.of(2022, 7, 4) + val dato1Slutt = LocalDate.of(2022, 7, 10) + + val dato2Start = LocalDate.of(2022, 7, 11) + val dato2Slutt = LocalDate.of(2022, 7, 17) + + val dato3Start = LocalDate.of(2022, 7, 18) + val dato3Slutt = LocalDate.of(2022, 7, 24) + + val dato4Start = LocalDate.of(2022, 7, 25) + val dato4Slutt = LocalDate.of(2022, 7, 31) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.WEEKS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.WEEKS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.WEEKS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.WEEKS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp, tidsEnhet = TidsEnhet.UKE) + + val correct = listOf(1, 2, 3, 4) + + val tidslinjeDag = tidslinje.konverterTilDag() + + Assertions.assertEquals(correct, tidslinjeDag.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato1Start, tidslinjeDag.startsTidspunkt) + + Assertions.assertEquals(dato4Slutt, tidslinjeDag.kalkulerSluttTidspunkt()) + } + + @Test + fun `kan omgjøre fra måned til dag`() { + val dato1Start = LocalDate.of(2022, 7, 1) + val dato1Slutt = LocalDate.of(2022, 7, 31) + + val dato2Start = LocalDate.of(2022, 8, 1) + val dato2Slutt = LocalDate.of(2022, 8, 31) + + val dato3Start = LocalDate.of(2022, 9, 1) + val dato3Slutt = LocalDate.of(2022, 9, 30) + + val dato4Start = LocalDate.of(2022, 10, 1) + val dato4Slutt = LocalDate.of(2022, 10, 31) + + val tmp = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.MONTHS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.MONTHS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.MONTHS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.MONTHS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tmp, tidsEnhet = TidsEnhet.MÅNED) + + val correct = listOf(1, 2, 3, 4) + + val tidslinjeDag = tidslinje.konverterTilDag() + + Assertions.assertEquals(correct, tidslinjeDag.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato1Start, tidslinjeDag.startsTidspunkt) + + Assertions.assertEquals(dato4Slutt, tidslinjeDag.kalkulerSluttTidspunkt()) + } + + @Test + fun `kan omgjøre fra år til dag`() { + val dato1Start = LocalDate.of(2022, 1, 1) + val dato1Slutt = LocalDate.of(2023, 12, 31) + + val dato2Start = LocalDate.of(2024, 1, 1) + val dato2Slutt = LocalDate.of(2024, 12, 31) + + val dato3Start = LocalDate.of(2025, 1, 1) + val dato3Slutt = LocalDate.of(2025, 12, 31) + + val dato4Start = LocalDate.of(2026, 1, 1) + val dato4Slutt = LocalDate.of(2026, 12, 31) + + val tidslinjePerioder = + listOf( + TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.YEARS).toInt() + 1), + TidslinjePeriode(2, dato2Start.until(dato2Slutt, ChronoUnit.YEARS).toInt() + 1), + TidslinjePeriode(3, dato3Start.until(dato3Slutt, ChronoUnit.YEARS).toInt() + 1), + TidslinjePeriode(4, dato4Start.until(dato4Slutt, ChronoUnit.YEARS).toInt() + 1), + ) + + val tidslinje = Tidslinje(dato1Start, tidslinjePerioder, tidsEnhet = TidsEnhet.ÅR) + + val forventedeVerdier = listOf(1, 2, 3, 4) + + val tidslinjeDag = tidslinje.konverterTilDag() + + Assertions.assertEquals(forventedeVerdier, tidslinjeDag.innhold.map { it.periodeVerdi.verdi }.toList()) + + Assertions.assertEquals(dato1Start.withDayOfYear(1), tidslinjeDag.startsTidspunkt) + + Assertions.assertEquals(dato4Slutt, tidslinjeDag.kalkulerSluttTidspunkt()) + } + + @Test + fun `Riktig start- og slutttidspunkt`() { + val dato1Start = LocalDate.of(2022, 1, 1) + val dato1Slutt = LocalDate.of(2023, 12, 31) + + val tmpÅR = listOf(TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.YEARS).toInt() + 1)) + val tmpMåned = listOf(TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.MONTHS).toInt() + 1)) + val tmpUke = listOf(TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.WEEKS).toInt() + 1)) + val tmpDag = listOf(TidslinjePeriode(1, dato1Start.until(dato1Slutt, ChronoUnit.DAYS).toInt() + 1)) + + val tidslinjeÅR = Tidslinje(dato1Start, tmpÅR, tidsEnhet = TidsEnhet.ÅR) + val tidslinjeMåned = Tidslinje(dato1Start, tmpMåned, tidsEnhet = TidsEnhet.MÅNED) + val tidslinjeUke = Tidslinje(dato1Start, tmpUke, tidsEnhet = TidsEnhet.UKE) + val tidslinjeDag = Tidslinje(dato1Start, tmpDag, tidsEnhet = TidsEnhet.DAG) + + Assertions.assertEquals(dato1Start, tidslinjeÅR.startsTidspunkt) + Assertions.assertEquals(dato1Start, tidslinjeMåned.startsTidspunkt) + Assertions.assertEquals(dato1Start, tidslinjeDag.startsTidspunkt) + Assertions.assertEquals(LocalDate.of(2021, 12, 27), tidslinjeUke.startsTidspunkt) + + Assertions.assertEquals(dato1Slutt, tidslinjeÅR.kalkulerSluttTidspunkt()) + Assertions.assertEquals(dato1Slutt, tidslinjeMåned.kalkulerSluttTidspunkt()) + Assertions.assertEquals(dato1Slutt, tidslinjeUke.kalkulerSluttTidspunkt()) + Assertions.assertEquals(dato1Slutt, tidslinjeDag.kalkulerSluttTidspunkt()) + } +} diff --git "a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/Sl\303\245SammenLikeTest.kt" "b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/Sl\303\245SammenLikeTest.kt" new file mode 100644 index 00000000..8c30a959 --- /dev/null +++ "b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/Sl\303\245SammenLikeTest.kt" @@ -0,0 +1,69 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Periode +import no.nav.familie.tidslinje.filtrerIkkeNull +import no.nav.familie.tidslinje.tilTidslinje +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class SlåSammenLikeTest { + private val førsteJanuar = LocalDate.of(2022, 1, 1) + private val sisteDagIJanuar = LocalDate.of(2022, 1, 31) + private val sisteDagIMars = LocalDate.of(2022, 3, 31) + private val førsteApril = LocalDate.of(2022, 4, 1) + private val sisteDagIApril = LocalDate.of(2022, 4, 30) + + @Test + fun `slåSammenLike - Skal slå sammen like perioder som ligger inntil hverandre`() { + val tidslinje = + listOf(Periode("a", førsteJanuar, sisteDagIMars), Periode("a", førsteApril, sisteDagIApril)).tilTidslinje() + + val tidslinjeSlåttSammen = tidslinje.slåSammenLikePerioder() + val perioder = tidslinjeSlåttSammen.tilPerioder() + + Assertions.assertEquals(1, perioder.size) + + Assertions.assertEquals(førsteJanuar, perioder[0].fom) + Assertions.assertEquals(sisteDagIApril, perioder[0].tom) + Assertions.assertEquals("a", perioder[0].verdi) + } + + @Test + fun `slåSammenLike - Skal ikke slå sammen like perioder som ikke ligger inntil hverandre`() { + val tidslinje = + listOf(Periode("a", førsteJanuar, sisteDagIJanuar), Periode("a", førsteApril, sisteDagIApril)).tilTidslinje() + + val tidslinjeSlåttSammen = tidslinje.slåSammenLikePerioder() + val perioder = tidslinjeSlåttSammen.tilPerioder().filtrerIkkeNull() + + Assertions.assertEquals(2, perioder.size) + + Assertions.assertEquals(førsteJanuar, perioder[0].fom) + Assertions.assertEquals(sisteDagIJanuar, perioder[0].tom) + Assertions.assertEquals("a", perioder[0].verdi) + + Assertions.assertEquals(førsteApril, perioder[1].fom) + Assertions.assertEquals(sisteDagIApril, perioder[1].tom) + Assertions.assertEquals("a", perioder[1].verdi) + } + + @Test + fun `slåSammenLike - Skal ikke slå sammen ulike perioder som ligger inntil hverandre`() { + val tidslinje = + listOf(Periode("a", førsteJanuar, sisteDagIMars), Periode("b", førsteApril, sisteDagIApril)).tilTidslinje() + + val tidslinjeSlåttSammen = tidslinje.slåSammenLikePerioder() + val perioder = tidslinjeSlåttSammen.tilPerioder() + + Assertions.assertEquals(2, perioder.size) + + Assertions.assertEquals(førsteJanuar, perioder[0].fom) + Assertions.assertEquals(sisteDagIMars, perioder[0].tom) + Assertions.assertEquals("a", perioder[0].verdi) + + Assertions.assertEquals(førsteApril, perioder[1].fom) + Assertions.assertEquals(sisteDagIApril, perioder[1].tom) + Assertions.assertEquals("b", perioder[1].verdi) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/SnittOgKlippTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/SnittOgKlippTest.kt new file mode 100644 index 00000000..459e9fb7 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/SnittOgKlippTest.kt @@ -0,0 +1,109 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.TidsEnhet +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class SnittOgKlippTest { + @Test + fun `kan ta snittet av to tidslinjer med ulik lengde`() { + val tidslinje1 = + Tidslinje( + startsTidspunkt = LocalDate.of(2022, 2, 1), + perioder = + listOf( + TidslinjePeriode(periodeVerdi = 1, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 2, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 15, lengde = 2, erUendelig = false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + val tidslinje2 = + Tidslinje( + startsTidspunkt = LocalDate.of(2022, 2, 1), + perioder = + listOf( + TidslinjePeriode(periodeVerdi = 1, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 2, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 15, lengde = 11, erUendelig = false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + val resultat = + tidslinje1.biFunksjonSnitt(tidslinje2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val fasit = mutableListOf(2, 4, 30) + + Assertions.assertEquals( + fasit, + resultat.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt på månedsnivå", + ) + Assertions.assertEquals(resultat.tidsEnhet, TidsEnhet.MÅNED) + + val endDate = LocalDate.of(2022, 2, 1).plusMonths(3) + Assertions.assertEquals(endDate.withDayOfMonth(endDate.lengthOfMonth()), resultat.kalkulerSluttTidspunkt()) + } + + @Test + fun `kan klippe en tidslinje slik at dem får riktig start og sluttdato`() { + var tidslinje = + Tidslinje( + startsTidspunkt = LocalDate.of(2022, 2, 1), + perioder = + listOf( + TidslinjePeriode(periodeVerdi = 1, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 2, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 15, lengde = 11, erUendelig = false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + var startDato = LocalDate.of(2022, 3, 1) + var sluttDato = LocalDate.of(2022, 4, 30) + + var fasit = mutableListOf(2, 15) + + tidslinje = tidslinje.klipp(startDato, sluttDato) + + Assertions.assertEquals(fasit, tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + Assertions.assertEquals(startDato, tidslinje.startsTidspunkt) + Assertions.assertEquals(sluttDato, tidslinje.kalkulerSluttTidspunkt()) + + tidslinje = + Tidslinje( + startsTidspunkt = LocalDate.of(2022, 2, 1), + perioder = + listOf( + TidslinjePeriode(periodeVerdi = 1, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 2, lengde = 1, erUendelig = false), + TidslinjePeriode(periodeVerdi = 15, lengde = 11, erUendelig = false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + startDato = LocalDate.of(2022, 2, 3) + sluttDato = LocalDate.of(2022, 2, 6) + + fasit = mutableListOf(1) + + tidslinje = tidslinje.konverterTilDag().klipp(startDato, sluttDato) + + Assertions.assertEquals(fasit, tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + Assertions.assertEquals(startDato, tidslinje.startsTidspunkt) + Assertions.assertEquals(sluttDato, tidslinje.kalkulerSluttTidspunkt()) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeEnkelTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeEnkelTest.kt new file mode 100644 index 00000000..152ea28a --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeEnkelTest.kt @@ -0,0 +1,180 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TidslinjeEnkelTest { + private var lst1 = emptyList>() + private var lst2 = emptyList>() + + private var t1: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + private var t2: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + + private fun init( + lst1: List>, + lst2: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.t1 = Tidslinje(LocalDate.now(), lst1) + this.t2 = Tidslinje(LocalDate.now(), lst2) + } + + @Test + fun `kan summere og substrahere to tidslinjer bestående av heltall`() { + init( + listOf(TidslinjePeriode(1, 1, false), TidslinjePeriode(2, 1, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(4, 1, false)), + ) + + var t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + val lst3 = lst1.map { it.periodeVerdi.verdi!! }.toMutableList() + for (i in 0 until lst3.size) { + lst3[i] = lst3[i].plus(lst2[i].periodeVerdi.verdi!!) + } + + Assertions.assertEquals(lst3, t3.innhold.map { it.periodeVerdi.verdi }.toList(), "Kunne ikke addere to tidslinjer") + + t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! - t2.verdi!!) } + + val lst4 = listOf(-2) + + Assertions.assertEquals(lst4, t3.innhold.map { it.periodeVerdi.verdi }.toList(), "Kunne ikke substrahere to tidslinjer") + } + + @Test + fun `kan gange to tidslinjer bestående av heltall sammen`() { + init( + listOf(TidslinjePeriode(1, 1, false), TidslinjePeriode(2, 1, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(4, 1, false)), + ) + + val t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! * t2.verdi!!) } + + val lst3 = lst1.map { it.periodeVerdi.verdi!! }.toMutableList() + for (i in 0 until lst3.size) { + lst3[i] *= lst2[i].periodeVerdi.verdi!! + } + + Assertions.assertEquals(lst3, t3.innhold.map { it.periodeVerdi.verdi }.toList(), "Kunne ikke addere to tidslinjer") + } + + @Test + fun `kan summere to tidslinjer med ulikt antall TidslinjePerioder`() { + init( + listOf(TidslinjePeriode(1, 2, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(4, 1, false)), + ) + + var t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + var lst3 = lst2.map { it.periodeVerdi.verdi!! }.toMutableList() + for (i in 0 until lst3.size) { + lst3[i] += lst1[0].periodeVerdi.verdi!! + } + + Assertions.assertEquals( + lst3, + t3.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulikt antall TidslinjePerioder: første sett", + ) + + lst1 = listOf(TidslinjePeriode(1, 8, false)) + lst2 = listOf(TidslinjePeriode(3, 4, false), TidslinjePeriode(4, 3, false), TidslinjePeriode(5, 1, false)) + t1 = Tidslinje(LocalDate.now(), lst1) + t2 = Tidslinje(LocalDate.now(), lst2) + + t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + lst3 = lst2.map { it.periodeVerdi.verdi!! }.toMutableList() + for (i in 0 until lst3.size) { + lst3[i] += lst1[0].periodeVerdi.verdi!! + } + + Assertions.assertEquals( + lst3, + t3.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulikt antall TidslinjePerioder: andre sett", + ) + } + + @Test + fun `Ved initsialisering av tidlinjer vil påfølgende TidslinjePerioder med lik verdi slått sammen`() { + val tidslinjePerioder = + listOf( + TidslinjePeriode(1, 6, false), + TidslinjePeriode(1, 6, false), + TidslinjePeriode(1, 6, false), + TidslinjePeriode(1, 6, false), + ) + val t1 = Tidslinje(LocalDate.now(), tidslinjePerioder) + Assertions.assertEquals(1, t1.innhold.size) + Assertions.assertEquals(1, t1.innhold[0].periodeVerdi.verdi) + } + + @Test + fun `kan gjøre beregninger med tidslinjer som inneholder store mengder data`() { + val lst1 = mutableListOf>() + val lst2 = mutableListOf>() + val lst3 = mutableListOf() + for (i in 0..1_000_000) { + lst1.add(TidslinjePeriode(i, 2, false)) + lst2.add(TidslinjePeriode(2 * i, 2, false)) + lst3.add(3 * i) + } + + val tidslinje1 = Tidslinje(LocalDate.now(), lst1) + val tidslinje2 = Tidslinje(LocalDate.now(), lst2) + + val kombinertTidslinje = tidslinje1.biFunksjon(tidslinje2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + Assertions.assertEquals(lst3, kombinertTidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + } + + @Test + fun `kan bruke objekter som verdier i tidslinjene`() { + val tidslinjePerioder1 = + listOf( + TidslinjePeriode(Beløp(2.0, "nok"), 1), + TidslinjePeriode(Beløp(3.0, "nok"), 1), + TidslinjePeriode(Beløp(3.0, "nok"), 1), + ) + val tidslinjePerioder2 = + listOf( + TidslinjePeriode(Beløp(2.0, "nok"), 1), + TidslinjePeriode(Beløp(3.0, "nok"), 1), + TidslinjePeriode(Beløp(4.0, "nok"), 1), + ) + + val tidslinje1 = Tidslinje(LocalDate.now(), tidslinjePerioder1) + + assertTrue { tidslinje1.innhold.size == 2 } + + val tidslinje2 = Tidslinje(LocalDate.now(), tidslinjePerioder2) + + val tidslinje3 = tidslinje1.biFunksjon(tidslinje2) { t1, t2 -> Verdi(Beløp(t1.verdi!!.verdi + t2.verdi!!.verdi, "nok")) } + + val verdierTidslinjePerioder1 = tidslinjePerioder1.map { it.periodeVerdi.verdi!!.verdi }.toMutableList() + + for (i in 0 until verdierTidslinjePerioder1.size) { + verdierTidslinjePerioder1[i] += tidslinjePerioder2[i].periodeVerdi.verdi!!.verdi + } + + Assertions.assertEquals( + verdierTidslinjePerioder1, + tidslinje3.innhold.map { it.periodeVerdi.verdi!!.verdi }.toList(), + "Kunne ikke addere to tidslinjer", + ) + } +} + +data class Beløp( + val verdi: Double, + val valutaKode: String, +) diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeHierarkiTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeHierarkiTest.kt new file mode 100644 index 00000000..9adb049c --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeHierarkiTest.kt @@ -0,0 +1,96 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TidslinjeHierarkiTest { + private var lst1 = emptyList>() + private var lst2 = emptyList>() + private var lst3 = emptyList>() + + private var t1: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + private var t2: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + private var t3: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + + private fun init( + lst1: List>, + lst2: List>, + lst3: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.lst3 = lst3 + this.t1 = Tidslinje(LocalDate.now(), lst1) + this.t2 = Tidslinje(LocalDate.now(), lst2) + this.t3 = Tidslinje(LocalDate.now(), lst3) + } + + @Test + fun `kan legge sammen lister og par med tidslinjer og danne et hierarkisk system for hvordan en tidslinje er bygget opp`() { + init( + listOf(TidslinjePeriode(5, 1, false), TidslinjePeriode(3, 1, false)), + listOf(TidslinjePeriode(2, 1, false), TidslinjePeriode(1, 1, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(2, 1, false)), + ) + var tidslinjeListe = listOf(t1, t2, t3) + val t4 = + tidslinjeListe.slåSammenLikeTidslinjer { t1, t2 -> if (t1 is Null || t2 is Null) Null() else Verdi(t1.verdi!! + t2.verdi!!) } + + Assertions.assertEquals(3, t4.foreldre.size) + Assertions.assertEquals(tidslinjeListe.filterIsInstance>(), t4.foreldre) + + val t5 = Tidslinje(LocalDate.now(), listOf(TidslinjePeriode(5, 3, false), TidslinjePeriode(3, 1, false))) + + tidslinjeListe = listOf(t4, t5) + + val t6 = + tidslinjeListe.slåSammenLikeTidslinjer { t1, t2 -> + if (t1 is Udefinert || t2 is Udefinert) { + Udefinert() + } else if (t1 is Null || t2 is Null) { + Null() + } else { + Verdi(t1.verdi!! + t2.verdi!!) + } + } + + Assertions.assertEquals(2, t6.foreldre.size) + Assertions.assertEquals(t4.foreldre, t6.foreldre[0].foreldre) + assertTrue(t5.foreldre.isEmpty()) + } + + @Test + fun `lagrer ikke foreldre dersom de fjernes`() { + init( + listOf(TidslinjePeriode(5, 1, false), TidslinjePeriode(3, 1, false)), + listOf(TidslinjePeriode(2, 1, false), TidslinjePeriode(1, 1, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(2, 1, false)), + ) + val t5 = t1.biFunksjon(t2) { t1, t2 -> if (t1 is Null || t2 is Null) Null() else Verdi(t1.verdi!! - t2.verdi!!) } + t5.fjernForeldre() + + assertTrue(t5.foreldre.isEmpty()) + } + + @Test + fun `lagrer foreldre dersom leggTilForeldre-flagget er satt til true (default)`() { + init( + listOf(TidslinjePeriode(5, 1, false), TidslinjePeriode(3, 1, false)), + listOf(TidslinjePeriode(2, 1, false), TidslinjePeriode(1, 1, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(2, 1, false)), + ) + val t5 = t1.biFunksjon(t2) { t1, t2 -> if (t1 is Null || t2 is Null) Null() else Verdi(t1.verdi!! - t2.verdi!!) } + + val tidslinjeListe = listOf(t1, t2) + + Assertions.assertEquals(2, t5.foreldre.size) + Assertions.assertEquals(tidslinjeListe.filterIsInstance>(), t5.foreldre) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeInitTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeInitTest.kt new file mode 100644 index 00000000..2998b45f --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeInitTest.kt @@ -0,0 +1,79 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.TidsEnhet +import no.nav.familie.tidslinje.Tidslinje +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.time.LocalDate + +class TidslinjeInitTest { + private val tidslinjeSomStreng = "tttttfftftfftftff" + private val mapper = + mapOf( + 't' to true, + 'f' to false, + ) + + @Test + fun `kan lage tidslinjer fra en streng og en mapper`() { + val tidslinje = + Tidslinje.lagTidslinjeFraStreng( + tidslinjeSomStreng, + LocalDate.now(), + mapper, + TidsEnhet.MÅNED, + ) + + assertTrue { tidslinje.innhold.size == 10 } + assertTrue { tidslinje.innhold[0].periodeVerdi.verdi!! } + Assertions.assertEquals( + listOf(true, false, true, false, true, false, true, false, true, false), + tidslinje.innhold.map { it.periodeVerdi.verdi }.toList(), + ) + } + + @Test + fun `Kan slå sammen TidslinjePerioder med lik verdi når man initsialiserer en tidslinje med en streng`() { + val tidslinje = + Tidslinje.lagTidslinjeFraStreng( + "ttttttttt", + LocalDate.now(), + mapper, + TidsEnhet.MÅNED, + ) + + assertTrue { tidslinje.innhold.size == 1 } + assertTrue { tidslinje.innhold[0].periodeVerdi.verdi!! } + Assertions.assertEquals(listOf(true), tidslinje.innhold.map { it.periodeVerdi.verdi }.toList()) + } + + @Test + fun `kan lage tidslinjer fra en streng`() { + val tidslinje = + Tidslinje.lagTidslinjeFraListe( + tidslinjeSomStreng.toList(), + LocalDate.now(), + TidsEnhet.MÅNED, + ) + + assertTrue { tidslinje.innhold.size == 10 } + Assertions.assertEquals( + listOf('t', 'f', 't', 'f', 't', 'f', 't', 'f', 't', 'f'), + tidslinje.innhold.map { it.periodeVerdi.verdi }.toList(), + ) + } + + @Test + fun `kaster unntak om mapperen ikke innholder en av karakterene`() { + assertThrows { + Tidslinje.lagTidslinjeFraStreng( + "tttttttttdisse karakterene støttes ikke", + LocalDate.now(), + mapper, + TidsEnhet.MÅNED, + ) + } + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeLengdeTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeLengdeTest.kt new file mode 100644 index 00000000..e95fb2f9 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeLengdeTest.kt @@ -0,0 +1,307 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.TidsEnhet +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TidslinjeLengdeTest { + private var lst1 = emptyList>() + private var lst2 = emptyList>() + + private var t1: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + private var t2: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + + private fun initSammeStartdato( + lst1: List>, + lst2: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.t1 = Tidslinje(LocalDate.now(), lst1) + this.t2 = Tidslinje(LocalDate.now(), lst2) + } + + private fun initForskjelligStartdato( + lst1: List>, + lst2: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.t1 = Tidslinje(LocalDate.now(), lst1) + this.t2 = Tidslinje(LocalDate.now().minusDays(3), lst2) + } + + @Test + fun `kan håndtere to tidslinjer med ulik slutt`() { + initSammeStartdato( + listOf(TidslinjePeriode(1, 3, false), TidslinjePeriode(2, 1, false)), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(4, 1, false)), + ) + + // Her testes det med nullverdi lik -1 for bifunksjon, dvs hvis en av tidslinjene er null i en sammenligning vil den returnere null. + val t3 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val lst3 = mutableListOf(4, 5, null) + + Assertions.assertEquals( + lst3, + t3.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt", + ) + + val t4 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! - el2.verdi!!) + } + } + + val lst4 = mutableListOf(-2, -3, null) + + Assertions.assertEquals( + lst4, + t4.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke substrahere to tidslinjer med ulik slutt", + ) + } + + @Test + fun `kan håndtere to tidslinjer med ulik startdato`() { + initForskjelligStartdato( + listOf(TidslinjePeriode(1, 1, false), TidslinjePeriode(2, 1, false)), + listOf(TidslinjePeriode(10, 3, false), TidslinjePeriode(3, 1, false), TidslinjePeriode(4, 1, false)), + ) + + val t5 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val lst5 = mutableListOf(null, 4, 6) + + Assertions.assertEquals( + lst5, + t5.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt", + ) + + val t6 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! - el2.verdi!!) + } + } + + val lst6 = mutableListOf(null, -2) + + Assertions.assertEquals( + lst6, + t6.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke substrahere to tidslinjer med ulik slutt", + ) + } + + @Test + fun `kan håndtere to tidslinjer med ulik start- OG sluttdato`() { + initForskjelligStartdato( + listOf(TidslinjePeriode(1, 1, false), TidslinjePeriode(2, 1, false), TidslinjePeriode(15, 4, false)), + listOf(TidslinjePeriode(10, 3, false), TidslinjePeriode(3, 1, false), TidslinjePeriode(4, 1, false)), + ) + + val t7 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val lst7 = mutableListOf(null, 4, 6, null) + + Assertions.assertEquals( + lst7, + t7.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt", + ) + + val t8 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! - el2.verdi!!) + } + } + + val lst8 = mutableListOf(null, -2, null) + + Assertions.assertEquals( + lst8, + t8.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke substrahere to tidslinjer med ulik slutt", + ) + } + + @Test + fun `kan håndtere to tidslinjer med ulik start- OG sluttdato OG nullinput`() { + initForskjelligStartdato( + listOf(TidslinjePeriode(null, 1, false), TidslinjePeriode(2, 1, false), TidslinjePeriode(15, 4, false)), + listOf(TidslinjePeriode(10, 3, false), TidslinjePeriode(3, 1, false), TidslinjePeriode(null, 1, false)), + ) + + val t7 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else if (el1 is Null || el2 is Null) { + Null() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val lst7 = mutableListOf(null, null, null) + + Assertions.assertEquals( + lst7, + t7.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt", + ) + + val t8 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else if (el1 is Null || el2 is Null) { + Null() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val lst8 = mutableListOf(null, null, null) + + Assertions.assertEquals( + lst8, + t8.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke substrahere to tidslinjer med ulik slutt", + ) + } + + @Test + fun `kan håndtere to tidslinjer med ulik start- og sluttdato på månedsnivå`() { + val t1 = + Tidslinje( + startsTidspunkt = LocalDate.now(), + perioder = + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(15, 5, false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + val t2 = + Tidslinje( + startsTidspunkt = LocalDate.now().minusMonths(2), + perioder = + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(15, 4, false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + val resultat = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val fasit = mutableListOf(null, 16, 17, 30, null) + + Assertions.assertEquals( + fasit, + resultat.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt på månedsnivå", + ) + Assertions.assertEquals(resultat.tidsEnhet, TidsEnhet.MÅNED) + val endDate = LocalDate.now().plusMonths(6) + Assertions.assertEquals(endDate.withDayOfMonth(endDate.lengthOfMonth()), resultat.kalkulerSluttTidspunkt()) + } + + @Test + fun `kan håndtere to tidslinjer på mpnednivå med ulik start og sluttidspunkt`() { + val t1 = + Tidslinje( + startsTidspunkt = LocalDate.of(2022, 2, 1), + perioder = + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(15, 2, false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + val t2 = + Tidslinje( + startsTidspunkt = LocalDate.of(2022, 2, 1), + perioder = + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(15, 11, false), + ), + tidsEnhet = TidsEnhet.MÅNED, + ) + + val resultat = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + val fasit = mutableListOf(2, 4, 30, null) + + Assertions.assertEquals( + fasit, + resultat.innhold.map { it.periodeVerdi.verdi }.toList(), + "Kunne ikke addere to tidslinjer med ulik slutt på månedsnivå", + ) + Assertions.assertEquals(resultat.tidsEnhet, TidsEnhet.MÅNED) + + val endDate = LocalDate.of(2022, 2, 1).plusMonths(12) + Assertions.assertEquals(endDate.withDayOfMonth(endDate.lengthOfMonth()), resultat.kalkulerSluttTidspunkt()) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeListeTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeListeTest.kt new file mode 100644 index 00000000..29d93bde --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeListeTest.kt @@ -0,0 +1,156 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +class TidslinjeListeTest { + private var lst1 = emptyList>() + private var lst2 = emptyList>() + private var lst3 = emptyList>() + private val nullVerdi = null + + private var tidslinjeList: List> = + listOf(Tidslinje(LocalDate.now(), emptyList()), Tidslinje(LocalDate.now(), emptyList())) + + private fun init( + lst1: List>, + lst2: List>, + lst3: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.lst3 = lst3 + this.tidslinjeList = + listOf(Tidslinje(LocalDate.now(), lst1), Tidslinje(LocalDate.now(), lst2), Tidslinje(LocalDate.now(), lst3)) + } + + private fun initUlikStartSlutt( + lst1: List>, + lst2: List>, + lst3: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.lst3 = lst3 + this.tidslinjeList = + listOf( + Tidslinje(LocalDate.now().minusDays(2), lst1), + Tidslinje(LocalDate.now().plusDays(1), lst2), + Tidslinje(LocalDate.now(), lst3), + ) + } + + @Test + fun `kan slå sammen flere tidslinjer av samme type`() { + init( + listOf(TidslinjePeriode(true, 3, false), TidslinjePeriode(false, 1, false)), + listOf(TidslinjePeriode(true, 1, false), TidslinjePeriode(true, 1, false)), + listOf(TidslinjePeriode(false, 1, false), TidslinjePeriode(true, 1, false)), + ) + val test = + tidslinjeList.slåSammenLikeTidslinjer { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! && el2.verdi!!) + } + } + + val fakta = mutableListOf(false, true, nullVerdi) + + Assertions.assertEquals( + fakta, + test.innhold.map { it.periodeVerdi.verdi }.toList(), + "klarte ikke å slå sammen flere lister", + ) + } + + @Test + fun `enkel test for å sjekke at true og false TidslinjePerioder ikke slås sammen til en`() { + val tidslinjeTest: Tidslinje = + Tidslinje(LocalDate.now(), listOf(TidslinjePeriode(true, 1, false), TidslinjePeriode(false, 1, false))) + + val fakta = mutableListOf(true, false) + + Assertions.assertEquals( + fakta, + tidslinjeTest.innhold.map { it.periodeVerdi.verdi }.toList(), + "klarte ikke håndtere lister med true og false", + ) + } + + @Test + fun `kan slå sammen flere tidslinjer av samme type med ulik start og slutt`() { + initUlikStartSlutt( + listOf(TidslinjePeriode(true, 3, false), TidslinjePeriode(false, 1, false)), + listOf(TidslinjePeriode(true, 2, false), TidslinjePeriode(true, 1, false)), + listOf(TidslinjePeriode(false, 2, false), TidslinjePeriode(true, 1, false)), + ) + val test = + tidslinjeList.slåSammenLikeTidslinjer { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! && el2.verdi!!) + } + } + + val fakta = mutableListOf(nullVerdi, false, nullVerdi) + + Assertions.assertEquals( + fakta, + test.innhold.map { it.periodeVerdi.verdi }.toList(), + "klarte ikke slå sammen tre lister av samme type med ulik start og slutt", + ) + } + + @Test + fun `sjekke at startdato settes riktig av slåSammenLikeTidslinjer`() { + var start1 = LocalDate.of(2022, 6, 1) + var stopp1 = LocalDate.of(2022, 7, 31) + + var start2 = LocalDate.of(2022, 8, 1) + var stopp2 = LocalDate.of(2022, 9, 30) + + val t1 = + Tidslinje( + start1, + listOf( + TidslinjePeriode(true, start1.until(stopp1, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(false, start2.until(stopp2, ChronoUnit.DAYS).toInt() + 1), + ), + ) + + start1 = LocalDate.of(2022, 5, 1) + stopp1 = LocalDate.of(2022, 6, 30) + + start2 = LocalDate.of(2022, 7, 1) + stopp2 = LocalDate.of(2022, 8, 31) + + val t2 = + Tidslinje( + start1, + listOf( + TidslinjePeriode(true, start1.until(stopp1, ChronoUnit.DAYS).toInt() + 1), + TidslinjePeriode(false, start2.until(stopp2, ChronoUnit.DAYS).toInt() + 1), + ), + ) + + val tidslinjeList: List> = listOf(t1, t2) + val result = + tidslinjeList.slåSammenLikeTidslinjer { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! && el2.verdi!!) + } + } + Assertions.assertEquals(LocalDate.of(2022, 5, 1), result.startsTidspunkt) + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeNullUndefTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeNullUndefTest.kt new file mode 100644 index 00000000..28a59212 --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeNullUndefTest.kt @@ -0,0 +1,94 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.INF +import no.nav.familie.tidslinje.Null +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TidslinjeNullUndefTest { + private var lst1 = emptyList>() + private var lst2 = emptyList>() + + private var t1: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + private var t2: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + + private fun init( + lst1: List>, + lst2: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.t1 = Tidslinje(LocalDate.now(), lst1) + this.t2 = Tidslinje(LocalDate.now(), lst2) + } + + @Test + fun `kan legge sammen tidslinjer av ulik lengde hvor den ene da blir paddet med udefinert`() { + init( + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(null, 1, false), + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 2, false), + ), + listOf(TidslinjePeriode(3, 1, false), TidslinjePeriode(1, 1, false), TidslinjePeriode(1, 1, false)), + ) + + val testTidslinje = + t1.biFunksjon(t2) { t1, t2 -> + if (t1 is Udefinert || t2 is Udefinert) { + Udefinert() + } else if (t1 is Null || t2 is Null) { + Null() + } else { + Verdi(t1.verdi!! + t2.verdi!!) + } + } + + val fakta = mutableListOf(4, null, 2, null) + + Assertions.assertEquals( + fakta, + testTidslinje.innhold.map { it.periodeVerdi.verdi }.toList(), + "klarte ikke legge sammen listene", + ) + Assertions.assertInstanceOf(Udefinert::class.java, testTidslinje.innhold.last().periodeVerdi) + Assertions.assertInstanceOf(Null::class.java, testTidslinje.innhold[1].periodeVerdi) + } + + @Test + fun `kan legge sammen tidslinjer av samme lengde med null-verdier`() { + init( + listOf(TidslinjePeriode(Udefinert(), 3, false), TidslinjePeriode(2, 1, false), TidslinjePeriode(99, INF, true)), + listOf(TidslinjePeriode(3, 2, false), TidslinjePeriode(1, 1, false), TidslinjePeriode(1, 1, false)), + ) + + val testTidslinje = + t1.biFunksjon(t2) { t1, t2 -> + if (t1 is Null || t2 is Null) { + Null() + } else if (t1 is Udefinert || t2 is Udefinert) { + Udefinert() + } else { + Verdi(t1.verdi!! + t2.verdi!!) + } + } + + val fakta = mutableListOf(null, 3, null) + + Assertions.assertEquals( + fakta, + testTidslinje.innhold.map { it.periodeVerdi.verdi }.toList(), + "K", + ) + Assertions.assertInstanceOf(Udefinert::class.java, testTidslinje.innhold.last().periodeVerdi) + Assertions.assertInstanceOf(Udefinert::class.java, testTidslinje.innhold.first().periodeVerdi) + assertTrue { testTidslinje.innhold.last().erUendelig } + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjePeriodeTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjePeriodeTest.kt new file mode 100644 index 00000000..31738f8f --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjePeriodeTest.kt @@ -0,0 +1,35 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TidslinjePeriodeTest { + @Test + fun `to TidslinjePerioder kan summeres og substraheres`() { + val p1 = TidslinjePeriode(1, 2, false) + val p2 = TidslinjePeriode(3, 2, false) + + var p3 = p1.biFunksjon(p2, 2, false) { el1, el2 -> Verdi(el1.verdi!! + el2.verdi!!) } + + assertNotNull(p3.periodeVerdi.verdi) + Assertions.assertEquals(4, p3.periodeVerdi.verdi) + + p3 = p1.biFunksjon(p2, 2, false) { el1, el2 -> Verdi(el1.verdi!! - el2.verdi!!) } + + Assertions.assertEquals(-2, p3.periodeVerdi.verdi) + } + + @Test + fun `en TidslinjePeriode må ha lengde større enn null`() { + assertThrows { + TidslinjePeriode(1, -1, false) + } + assertThrows { + TidslinjePeriode(1, 0, false) + } + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeUendlighetTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeUendlighetTest.kt new file mode 100644 index 00000000..8e35be3c --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TidslinjeUendlighetTest.kt @@ -0,0 +1,124 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.INF +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Udefinert +import no.nav.familie.tidslinje.Verdi +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TidslinjeUendlighetTest { + private var lst1 = emptyList>() + private var lst2 = emptyList>() + + private var t1: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + private var t2: Tidslinje = Tidslinje(LocalDate.now(), emptyList()) + + private fun init( + lst1: List>, + lst2: List>, + ) { + this.lst1 = lst1 + this.lst2 = lst2 + this.t1 = Tidslinje(LocalDate.now(), lst1) + this.t2 = Tidslinje(LocalDate.now(), lst2) + } + + @Test + fun `kan legge sammen to tidslinjer, hvor den ene er uendelig`() { + init( + listOf(TidslinjePeriode(1, 1, false), TidslinjePeriode(2, INF, true)), + listOf(TidslinjePeriode(3, 1, false)), + ) + + var t3 = + t1.biFunksjon(t2) { t1, t2 -> + if (t1 is Udefinert || t2 is Udefinert) Udefinert() else Verdi(t1.verdi!! + t2.verdi!!) + } + + assertTrue { t3.innhold.size == 2 } + assertTrue { t3.innhold.last().erUendelig } + + init( + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(3, 1, false), + TidslinjePeriode(4, 1, false), + TidslinjePeriode(5, 1, false), + TidslinjePeriode(6, 1, false), + TidslinjePeriode(7, INF, true), + ), + listOf(TidslinjePeriode(3, 1, false)), + ) + + t3 = + t1.biFunksjon(t2) { el1, el2 -> + if (el1 is Udefinert || el2 is Udefinert) { + Udefinert() + } else { + Verdi(el1.verdi!! + el2.verdi!!) + } + } + + assertTrue { t3.innhold.size == 2 } + assertTrue { t3.innhold.last().erUendelig } + } + + @Test + fun `kan legge sammen uendelige tidslinjer`() { + init( + listOf(TidslinjePeriode(1, 1, false), TidslinjePeriode(2, INF, true)), + listOf(TidslinjePeriode(3, 1, true)), + ) + + var t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + assertTrue { t3.innhold.size == 2 } + assertTrue { t3.innhold.last().erUendelig } + + val t4 = t1.binærOperator(t2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + assertTrue { t4.innhold.size == 2 } + assertTrue { t4.innhold.last().erUendelig } + + init( + listOf(TidslinjePeriode(1, 1, true), TidslinjePeriode(2, INF, false)), + listOf(TidslinjePeriode(1, 1, true), TidslinjePeriode(3, 1, false)), + ) + + t3 = t1.biFunksjon(t2) { t1, t2 -> Verdi(t1.verdi!! + t2.verdi!!) } + + assertTrue { t3.innhold.size == 1 } + assertTrue { t3.innhold.last().erUendelig } + } + + @Test + fun `kan håndtere uendlighet i midten av en tidlinje`() { + init( + listOf( + TidslinjePeriode(1, 1, false), + TidslinjePeriode(2, 1, false), + TidslinjePeriode(3, 1, true), + TidslinjePeriode(4, 1, false), + TidslinjePeriode(5, 1, false), + TidslinjePeriode(6, 1, false), + TidslinjePeriode(7, INF, true), + ), + listOf(TidslinjePeriode(3, 1, false)), + ) + + assertTrue { t1.innhold.size == 3 } + assertTrue { t1.innhold.last().erUendelig } + + val t3 = + t1.biFunksjon(t2) { t1, t2 -> + if (t1 is Udefinert || t2 is Udefinert) Udefinert() else Verdi(t1.verdi!! + t2.verdi!!) + } + + assertTrue { t3.innhold.size == 2 } + assertTrue { t3.innhold.last().erUendelig } + } +} diff --git a/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TilTidslinjePerioderMedLocalDateTest.kt b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TilTidslinjePerioderMedLocalDateTest.kt new file mode 100644 index 00000000..265c89ff --- /dev/null +++ b/tidslinje/src/test/kotlin/no/nav/familie/tidslinje/utvidelser/TilTidslinjePerioderMedLocalDateTest.kt @@ -0,0 +1,90 @@ +package no.nav.familie.tidslinje.utvidelser + +import no.nav.familie.tidslinje.Tidslinje +import no.nav.familie.tidslinje.TidslinjePeriode +import no.nav.familie.tidslinje.Verdi +import no.nav.familie.tidslinje.diffIDager +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class TilTidslinjePerioderMedLocalDateTest { + private val førsteJanuar = LocalDate.of(2022, 1, 1) + private val sisteDagIJanuar = LocalDate.of(2022, 1, 31) + private val førsteFebruar = LocalDate.of(2022, 2, 1) + private val sisteDagIFebruar = LocalDate.of(2022, 2, 28) + private val førsteMars = LocalDate.of(2022, 3, 1) + private val sisteDagIMars = LocalDate.of(2022, 3, 31) + + @Test + fun `Skal gi ut liste med perioder med riktig tom og fom gitt starttidspunkt og lengde på periodene`() { + val tidslinje = + Tidslinje( + førsteJanuar, + listOf( + TidslinjePeriode(Verdi("a"), førsteJanuar.diffIDager(sisteDagIJanuar)), + TidslinjePeriode(Verdi("b"), førsteFebruar.diffIDager(sisteDagIFebruar)), + TidslinjePeriode(Verdi("c"), førsteMars.diffIDager(sisteDagIMars)), + ), + ) + + val tidslinjePerioderMedDato = tidslinje.tilTidslinjePerioderMedDato() + + Assertions.assertEquals(3, tidslinjePerioderMedDato.size) + + Assertions.assertEquals(førsteJanuar, tidslinjePerioderMedDato[0].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIJanuar, tidslinjePerioderMedDato[0].tom.tilLocalDateEllerNull()) + Assertions.assertEquals("a", tidslinjePerioderMedDato[0].periodeVerdi.verdi) + + Assertions.assertEquals(førsteFebruar, tidslinjePerioderMedDato[1].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIFebruar, tidslinjePerioderMedDato[1].tom.tilLocalDateEllerNull()) + Assertions.assertEquals("b", tidslinjePerioderMedDato[1].periodeVerdi.verdi) + + Assertions.assertEquals(førsteMars, tidslinjePerioderMedDato[2].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIMars, tidslinjePerioderMedDato[2].tom.tilLocalDateEllerNull()) + Assertions.assertEquals("c", tidslinjePerioderMedDato[2].periodeVerdi.verdi) + } + + @Test + fun `Skal gi ut riktige datoer etter manipulering på tidslinjene`() { + val tidslinje1 = + Tidslinje( + førsteJanuar, + listOf( + TidslinjePeriode(Verdi("a"), førsteJanuar.diffIDager(sisteDagIMars)), + ), + ) + + val tidslinje2 = + Tidslinje( + førsteFebruar, + listOf( + TidslinjePeriode(Verdi("b"), førsteFebruar.diffIDager(sisteDagIFebruar)), + ), + ) + + val tidslinjePerioderMedDato = + tidslinje1 + .biFunksjon(tidslinje2) { a, b -> + if (b is Verdi) { + b + } else { + a + } + }.tilTidslinjePerioderMedDato() + + Assertions.assertEquals(3, tidslinjePerioderMedDato.size) + + Assertions.assertEquals(førsteJanuar, tidslinjePerioderMedDato[0].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIJanuar, tidslinjePerioderMedDato[0].tom.tilLocalDateEllerNull()) + Assertions.assertEquals("a", tidslinjePerioderMedDato[0].periodeVerdi.verdi) + + Assertions.assertEquals(førsteFebruar, tidslinjePerioderMedDato[1].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIFebruar, tidslinjePerioderMedDato[1].tom.tilLocalDateEllerNull()) + Assertions.assertEquals("b", tidslinjePerioderMedDato[1].periodeVerdi.verdi) + + Assertions.assertEquals(førsteMars, tidslinjePerioderMedDato[2].fom.tilLocalDateEllerNull()) + Assertions.assertEquals(sisteDagIMars, tidslinjePerioderMedDato[2].tom.tilLocalDateEllerNull()) + Assertions.assertEquals("a", tidslinjePerioderMedDato[2].periodeVerdi.verdi) + } +}