diff --git a/pom.xml b/pom.xml
index d03aab0e85..12378ee780 100644
--- a/pom.xml
+++ b/pom.xml
@@ -318,6 +318,10 @@
no.nav.familie.felles
metrikker
+
+ no.nav.familie.felles
+ tidslinje
+
no.nav.familie.kontrakter
felles
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeJoin.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeJoin.kt
new file mode 100644
index 0000000000..f9e559e38a
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeJoin.kt
@@ -0,0 +1,55 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon
+
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.tomTidslinje
+import no.nav.familie.tidslinje.utvidelser.kombinerMed
+
+/**
+ * Extension-metode for å kombinere to nøkkel-verdi-map'er der verdiene er tidslinjer
+ * Nøkkelen må være av samme type K
+ * Verdiene i tidslinjene i map'en på venstre side må alle være av typen V
+ * Verdiene i tidslinjene i map'en på høyre side må alle være av typen H
+ * Kombinator-funksjonen kalles med verdiene av fra venstre og høyre tidslinje for samme nøkkel og tidspunkt.
+ * blir sendt som verdier hvis venstre, høyre eller begge tidslinjer mangler verdi for et tidspunkt
+ * Resultatet er en ny map der nøklene er av type K, og tidslinjene har innhold av typen (nullable) R.
+ * Bare nøkler som finnes i begge map'ene vil finnes i den resulterende map'en
+ */
+fun Map>.join(
+ høyreTidslinjer: Map>,
+ kombinator: (V?, H?) -> R?,
+): Map> {
+ val venstreTidslinjer = this
+ val alleNøkler = venstreTidslinjer.keys.intersect(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)
+ }
+}
+
+/**
+ * Extension-metode for å kombinere to nøkkel-verdi-map'er der verdiene er tidslinjer
+ * Nøkkelen må være av samme type K
+ * Verdiene i tidslinjene i map'en på venstre side må alle være av typen V
+ * Verdiene i tidslinjene i map'en på høyre side må alle være av typen H
+ * Kombinator-funksjonen kalles med verdiene av fra venstre og høyre tidslinje for samme nøkkel og tidspunkt.
+ * Kombinator-funksjonen blir IKKE kalt hvis venstre, høyre eller begge tidslinjer mangler verdi for et tidspunkt
+ * Resultatet er en ny map der nøklene er av type K, og tidslinjene har innhold av typen (nullable) R.
+ * Bare nøkler som finnes i begge map'ene vil finnes i den resulterende map'en
+ */
+fun Map>.joinIkkeNull(
+ høyreTidslinjer: Map>,
+ kombinator: (V, H) -> R?,
+): Map> {
+ val venstreTidslinjer = this
+ val alleNøkler = venstreTidslinjer.keys.intersect(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.kombinerUtenNullMed(høyreTidslinje, kombinator)
+ }
+}
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeKombinator.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeKombinator.kt
new file mode 100644
index 0000000000..a724f58fe4
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeKombinator.kt
@@ -0,0 +1,143 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon
+
+import no.nav.familie.tidslinje.Null
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.Udefinert
+import no.nav.familie.tidslinje.Verdi
+import no.nav.familie.tidslinje.tilPeriodeVerdi
+import no.nav.familie.tidslinje.utvidelser.biFunksjon
+import no.nav.familie.tidslinje.utvidelser.kombinerMed
+import no.nav.familie.tidslinje.utvidelser.map
+import no.nav.familie.tidslinje.utvidelser.slåSammen
+import no.nav.familie.tidslinje.utvidelser.trim
+
+/**
+ * Extension-metode for å kombinere to tidslinjer der begge har verdi
+ * Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene
+ * Hver av tidslinjene kan ha ulik type, hhv V og H
+ * Hvis V eller H mangler verdi, så vil ikke resulterende tidslinje få en verdi for det tidspunktet
+ * Kombintor-funksjonen tar ellers V og H og returnerer (nullable) R
+ * Hvis kombinator-funksjonen returner , antas det at tidslinjen ikke skal ha verdi for tidspunktet
+ * Resultatet er en tidslinje med verdi R
+ */
+fun Tidslinje.kombinerUtenNullMed(
+ høyreTidslinje: Tidslinje,
+ kombineringsfunksjon: (V, H) -> R?,
+): Tidslinje =
+ this.biFunksjon(høyreTidslinje) { periodeverdiVenstre, periodeverdiHøyre ->
+ when {
+ periodeverdiVenstre is Verdi && periodeverdiHøyre is Verdi ->
+ kombineringsfunksjon(periodeverdiVenstre.verdi, periodeverdiHøyre.verdi).tilPeriodeVerdi()
+
+ else -> Null()
+ }
+ }
+
+/**
+ * Extension-metode for å kombinere liste av tidslinjer
+ * Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
+ * Verdi (V) må være av samme type
+ * Kombintor-funksjonen tar inn Iterable og returner (nullable) R
+ * Null-verdier fjernes før de sendes til kombinator-funksjonen, som betyr at en tom iterator kan bli sendt
+ * Hvis resultatet fra kombinatoren er null, tolkes det som at det ikke skal være en verdi
+ * Resultatet er en tidslinje med verdi R
+ */
+fun Collection>.kombinerUtenNull(
+ listeKombinator: (Iterable) -> R?,
+): Tidslinje = kombinerNullableKombinator { it.filterNotNull().let(listeKombinator) }
+
+/**
+ * Extension-metode for å kombinere liste av tidslinjer
+ * Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
+ * Verdi (V) må være av samme type
+ * Kombintor-funksjonen tar inn Iterable og returner (nullable) R
+ * Null-verdier fjernes, og listen av verdier sendes til kombinator-funksjonen bare hvis den inneholder verdier
+ * Hvis reesultatet fra kombinatoren er null, tolkes det som at det ikke skal være en verdi
+ * Resultatet er en tidslinje med verdi R
+ */
+fun Collection>.kombinerUtenNullOgIkkeTom(
+ listeKombinator: (Iterable) -> R?,
+): Tidslinje = kombinerNullableKombinator { it.filterNotNull().takeIf { it.isNotEmpty() }?.let(listeKombinator) }
+
+/**
+ * Extension-metode for å kombinere liste av tidslinjer
+ * Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
+ * Verdien V må være av samme type
+ * Resultatet er en tidslinje med verdi Iterable
+ */
+fun Collection>.kombiner() = this.kombinerNullableKombinator { if (it.toList().isNotEmpty()) it else null }
+
+/**
+ * Extension-metode for å kombinere en nøkkel-verdi-map'er der verdiene er tidslinjer, med en enkelt tidslinje
+ * Verdien i tidslinjene i map'en på venstre side må alle være av typen V
+ * Verdien i tidslinjen på høyre side er av typen H
+ * Kombinator-funksjonen kalles for hvert tidspunkt med med verdien for det tidspunktet fra høyre tidslinje og
+ * verdien fra den enkelte av venstre tidslinjer etter tur.
+ * Kombinator-funksjonen blir IKKE kalt hvis venstre, høyre eller begge tidslinjer mangler verdi for et tidspunkt
+ * Resultatet er en ny map der nøklene er av type K, og tidslinjene har verdi av typen (nullable) R.
+ */
+fun Map>.kombinerKunVerdiMed(
+ høyreTidslinje: Tidslinje,
+ kombinator: (V, H) -> R?,
+): Map> {
+ val venstreTidslinjer = this
+
+ return venstreTidslinjer.mapValues { (_, venstreTidslinje) ->
+ venstreTidslinje.kombinerUtenNullMed(høyreTidslinje, kombinator)
+ }
+}
+
+/**
+ * Extension-metode for å kombinere tre tidslinjer
+ * Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
+ * Hver av tidslinjene kan ha ulik type, hhv A, B og C
+ * Kombintor-funksjonen tar inn av A, B og C og returner (nullable) R
+ * Resultatet er en tidslinje med verdi R
+ */
+fun Tidslinje.kombinerKunVerdiMed(
+ tidslinjeB: Tidslinje,
+ tidslinjeC: Tidslinje,
+ kombinator: (A, B, C) -> R?,
+): Tidslinje =
+ this.kombinerMed(tidslinjeB, tidslinjeC) { a, b, c ->
+ when {
+ a != null && b != null && c != null -> kombinator(a, b, c)
+ else -> null
+ }
+ }
+
+fun Tidslinje.erIkkeTom() = !this.erTom()
+
+fun Tidslinje.harOverlappMed(tidslinje: Tidslinje) = this.kombinerUtenNullMed(tidslinje) { v, h -> true }.trim(Null()).erIkkeTom()
+
+fun Tidslinje.harIkkeOverlappMed(tidslinje: Tidslinje) = !this.harOverlappMed(tidslinje)
+
+fun Tidslinje.kombinerMedNullable(
+ høyreTidslinje: Tidslinje?,
+ kombinator: (V?, H?) -> V?,
+): Tidslinje =
+ if (høyreTidslinje != null) {
+ kombinerMed(høyreTidslinje, kombinator)
+ } else {
+ this
+ }
+
+/**
+ * Extension-metode for å kombinere liste av tidslinjer
+ * Kombinasjonen baserer seg på å iterere gjennom alle tidspunktene fra alle tidslinjene
+ * Verdi (V) må være av samme type
+ * Kombintor-funksjonen tar inn Iterable og returner (nullable) R
+ * Resultatet er en tidslinje med verdi R
+ */
+fun Collection>.kombinerNullableKombinator(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/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeUtil.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeUtil.kt
new file mode 100644
index 0000000000..16c4de84a2
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeUtil.kt
@@ -0,0 +1,47 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon
+
+import no.nav.familie.ba.sak.common.førsteDagIInneværendeMåned
+import no.nav.familie.ba.sak.common.førsteDagINesteMåned
+import no.nav.familie.ba.sak.common.sisteDagIForrigeMåned
+import no.nav.familie.ba.sak.common.sisteDagIInneværendeMåned
+import no.nav.familie.ba.sak.common.sisteDagIMåned
+import no.nav.familie.ba.sak.kjerne.grunnlag.personopplysninger.Person
+import no.nav.familie.tidslinje.Periode
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.tilTidslinje
+import java.time.LocalDate
+import java.time.YearMonth
+
+fun erUnder18ÅrVilkårTidslinje(fødselsdato: LocalDate): Tidslinje =
+ opprettBooleanTidslinje(
+ fraDato = fødselsdato.førsteDagINesteMåned(),
+ tilDato = fødselsdato.plusYears(18).sisteDagIForrigeMåned(),
+ )
+
+fun erUnder6ÅrTidslinje(person: Person): Tidslinje =
+ opprettBooleanTidslinje(
+ fraDato = person.fødselsdato.førsteDagIInneværendeMåned(),
+ tilDato = person.fødselsdato.plusYears(6).sisteDagIForrigeMåned(),
+ )
+
+fun erTilogMed3ÅrTidslinje(fødselsdato: LocalDate): Tidslinje =
+ opprettBooleanTidslinje(
+ fraDato = fødselsdato.førsteDagINesteMåned(),
+ tilDato = fødselsdato.plusYears(3).sisteDagIMåned(),
+ )
+
+fun opprettBooleanTidslinje(
+ fraÅrMåned: YearMonth,
+ tilÅrMåned: YearMonth,
+) = listOf(
+ Periode(
+ verdi = true,
+ fom = fraÅrMåned.førsteDagIInneværendeMåned(),
+ tom = tilÅrMåned.sisteDagIInneværendeMåned(),
+ ),
+).tilTidslinje()
+
+fun opprettBooleanTidslinje(
+ fraDato: LocalDate,
+ tilDato: LocalDate,
+) = listOf(Periode(verdi = true, fom = fraDato, tom = tilDato)).tilTidslinje()
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/BigDecimalTidslinje.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/BigDecimalTidslinje.kt
new file mode 100644
index 0000000000..b0727dddee
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/BigDecimalTidslinje.kt
@@ -0,0 +1,22 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.matematikk
+
+import no.nav.familie.ba.sak.kjerne.personident.Aktør
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon.join
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon.kombinerUtenNullOgIkkeTom
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon.mapIkkeNull
+import no.nav.familie.tidslinje.Tidslinje
+import java.math.BigDecimal
+import java.math.RoundingMode
+
+fun Map>.minus(
+ bTidslinjer: Map>,
+) = this.join(bTidslinjer) { a, b ->
+ when {
+ a != null && b != null -> a - b
+ else -> a
+ }
+}
+
+fun Map>.sum() = values.kombinerUtenNullOgIkkeTom { it.reduce { sum, verdi -> sum.plus(verdi) } }
+
+fun Tidslinje.rundAvTilHeltall() = this.mapIkkeNull { it.setScale(0, RoundingMode.HALF_UP) }
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/SammenliknbarTidslinje.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/SammenliknbarTidslinje.kt
new file mode 100644
index 0000000000..436b7f082f
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/SammenliknbarTidslinje.kt
@@ -0,0 +1,9 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.matematikk
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon.joinIkkeNull
+import no.nav.familie.tidslinje.Tidslinje
+
+fun > minsteAvHver(
+ aTidslinjer: Map>,
+ bTidslinjer: Map>,
+) = aTidslinjer.joinIkkeNull(bTidslinjer) { a, b -> minOf(a, b) }
diff --git "a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Beskj\303\246reTidslinje.kt" "b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Beskj\303\246reTidslinje.kt"
new file mode 100644
index 0000000000..18a3109486
--- /dev/null
+++ "b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Beskj\303\246reTidslinje.kt"
@@ -0,0 +1,84 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.tomTidslinje
+import no.nav.familie.tidslinje.utvidelser.klipp
+import java.time.LocalDate
+
+/**
+ * Extension-metode for å beskjære (forkorte) en tidslinje etter til-og-med fra en annen tidslinje
+ * Etter beskjæringen vil tidslinjen maksimalt strekke seg fra [this]s startTidspunkt og til [tidslinje]s sluttTidspunkt
+ * Perioder som ligger helt utenfor grensene vil forsvinne.
+ * Perioden i hver ende som ligger delvis innenfor, vil forkortes.
+ * Hvis ny og eksisterende grenseverdi begge er uendelige, vil den nye benyttes
+ * Beskjæring mot tom tidslinje vil gi tom tidslinje
+ */
+fun Tidslinje.beskjærTilOgMedEtter(tidslinje: Tidslinje<*>): Tidslinje =
+ when {
+ tidslinje.erTom() -> tomTidslinje()
+ else ->
+ klipp(
+ startsTidspunkt = startsTidspunkt,
+ sluttTidspunkt = tidslinje.kalkulerSluttTidspunkt(),
+ )
+ }
+
+/**
+ * Extension-metode for å beskjære (forkorte) en tidslinje
+ * Etter beskjæringen vil tidslinjen maksimalt strekke seg fra innsendt [fraOgMed] og til [tilOgMed]
+ * Perioder som ligger helt utenfor grensene vil forsvinne.
+ * Perioden i hver ende som ligger delvis innenfor, vil forkortes.
+ * Uendelige endepunkter vil beskjæres til endelig
+ */
+fun Tidslinje.beskjær(
+ fraOgMed: LocalDate,
+ tilOgMed: LocalDate,
+): Tidslinje =
+ when {
+ erTom() -> tomTidslinje()
+ else ->
+ klipp(
+ startsTidspunkt = fraOgMed,
+ sluttTidspunkt = tilOgMed,
+ )
+ }
+
+/**
+ * Extension-metode for å beskjære fom dato på en tidslinje
+ * Etter beskjæringen vil tidslinjen maksimalt strekke seg fra innsendt [fraOgMed] og til eksisterende tilOgMed
+ */
+fun Tidslinje.beskjærFraOgMed(
+ fraOgMed: LocalDate,
+): Tidslinje =
+ when {
+ erTom() -> tomTidslinje()
+ else ->
+ klipp(
+ startsTidspunkt = fraOgMed,
+ sluttTidspunkt = kalkulerSluttTidspunkt(),
+ )
+ }
+
+/**
+ * Extension-metode for å beskjære tom dato på en tidslinje
+ * Etter beskjæringen vil tidslinjen maksimalt strekke seg fra eksisterende fraOgMed og til innsendt [tilOgMed]
+ */
+fun Tidslinje.beskjærTilOgMed(
+ tilOgMed: LocalDate,
+): Tidslinje =
+ when {
+ erTom() -> tomTidslinje()
+ else ->
+ klipp(
+ startsTidspunkt = startsTidspunkt,
+ sluttTidspunkt = tilOgMed,
+ )
+ }
+
+/**
+ * Extension-metode for å beskjære tom dato på et map av tidslinjer
+ * Etter beskjæringen vil tidslinjen maksimalt strekke seg fra eksisterende fraOgMed og til innsendt [tilOgMed]
+ */
+fun Map>.beskjærTilOgMed(
+ tilOgMed: LocalDate,
+): Map> = this.mapValues { (_, tidslinje) -> tidslinje.beskjærTilOgMed(tilOgMed) }
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/EndreTid.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/EndreTid.kt
new file mode 100644
index 0000000000..b2e064dc42
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/EndreTid.kt
@@ -0,0 +1,90 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.tidslinje.Null
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.Udefinert
+import no.nav.familie.tidslinje.tilPeriodeVerdi
+import no.nav.familie.tidslinje.utvidelser.konverterTilDag
+import no.nav.familie.tidslinje.utvidelser.konverterTilMåned
+import no.nav.familie.tidslinje.utvidelser.trim
+
+/**
+ * Extension-metode for å konvertere fra dag-basert tidslinje til måned-basert tidslinje
+ * mapper-funksjonen tar inn listen av alle dagverdiene i én måned, og returner verdien måneden skal ha
+ * Dagverdiene kommer i samme rekkefølge som dagene i måneden, og vil ha null-verdi hvis dagen ikke har en verdi
+ */
+fun Tidslinje.tilMåned(mapper: (List) -> V?): Tidslinje =
+ this.konverterTilMåned { dato, månedListe ->
+ val månedVerdier = månedListe.first().map { dag -> dag.periodeVerdi.verdi }
+ mapper(månedVerdier).tilPeriodeVerdi()
+ }
+
+/**
+ * Extention-metode som konverterer en dag-basert tidslinje til en måned-basert tidslinje.
+ * -funksjonen tar inn verdiene fra de to dagene før og etter månedsskiftet,
+ * det vil si verdiene fra siste dag i forrige måned og første dag i inneværemde måned.
+ * -funksjonen kalles bare dersom begge dagene har en verdi.
+ * Return-verdien er innholdet som blir brukt for inneværende måned.
+ * Hvis retur-verdien er , vil den resulterende måneden mangle verdi.
+ * Funksjonen vil bruke månedsskiftene fra måneden før tidslinjen starter frem til og med siste måned i tidslinjen.
+ */
+fun Tidslinje.tilMånedFraMånedsskifteIkkeNull(
+ mapper: (innholdSisteDagForrigeMåned: V, innholdFørsteDagDenneMåned: V) -> V?,
+): Tidslinje =
+ this
+ .konverterTilMåned(antallMndBakoverITid = 1) { dato, måneder ->
+ val sisteDagForrigeMåned =
+ måneder
+ .first()
+ .lastOrNull()
+ ?.periodeVerdi
+ ?.verdi
+ val førsteDagDenneMåned =
+ måneder
+ .last()
+ .firstOrNull()
+ ?.periodeVerdi
+ ?.verdi
+
+ if (sisteDagForrigeMåned == null || førsteDagDenneMåned == null) {
+ Null()
+ } else {
+ mapper(sisteDagForrigeMåned, førsteDagDenneMåned).tilPeriodeVerdi()
+ }
+ }.trim(Null(), Udefinert())
+
+/**
+ * Extention-metode som konverterer en dag-basert tidslinje til en måned-basert tidslinje.
+ * -funksjonen tar inn verdiene fra de to dagene før og etter månedsskiftet,
+ * det vil si verdiene fra siste dag i forrige måned og første dag i inneværemde måned.
+ * Return-verdien er innholdet som blir brukt for inneværende måned.
+ * Hvis retur-verdien er , vil den resulterende måneden mangle verdi.
+ * Funksjonen vil bruke månedsskiftene fra måneden før tidslinjen starter frem til og med siste måned i tidslinjen.
+ */
+fun Tidslinje.tilMånedFraMånedsskifte(
+ mapper: (innholdSisteDagForrigeMåned: V?, innholdFørsteDagDenneMåned: V?) -> V?,
+): Tidslinje =
+ this
+ .konverterTilMåned(antallMndBakoverITid = 1) { dato, måneder ->
+ val sisteDagForrigeMåned =
+ måneder
+ .first()
+ .lastOrNull()
+ ?.periodeVerdi
+ ?.verdi
+ val førsteDagDenneMåned =
+ måneder
+ .last()
+ .firstOrNull()
+ ?.periodeVerdi
+ ?.verdi
+
+ mapper(sisteDagForrigeMåned, førsteDagDenneMåned).tilPeriodeVerdi()
+ }
+
+/**
+ * Extension-metode for å konvertere fra Måned-tidslinje til Dag-tidslinje
+ * Første dag i fra-og-med-måneden brukes som første dag i perioden
+ * Siste dag i til-og-med-måneden brukes som siste dag i perioden
+ */
+fun Tidslinje.tilDag(): Tidslinje = this.konverterTilDag()
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/FiltrerTidslinje.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/FiltrerTidslinje.kt
new file mode 100644
index 0000000000..25ed24b2df
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/FiltrerTidslinje.kt
@@ -0,0 +1,30 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.beskjærEtter
+import no.nav.familie.tidslinje.utvidelser.filtrer
+import no.nav.familie.tidslinje.utvidelser.kombinerMed
+
+fun Tidslinje.filtrerIkkeNull(filter: (V) -> Boolean): Tidslinje = filtrer { it != null && filter(it) }
+
+/**
+ * Extension-metode for å filtrere tidslinjen mot en boolsk tidslinje
+ * Resultatet får samme lengde som tidslinjen det opereres på
+ * Det vil finnes perioder som tilsvarer periodene fra kilde-tidslinjen,
+ * men innholdet blir null hvis den boolske tidslinjen er false
+ */
+fun Tidslinje.filtrerMed(boolskTidslinje: Tidslinje): Tidslinje =
+ this
+ .kombinerMed(boolskTidslinje) { verdi, erSann ->
+ when (erSann) {
+ true -> verdi
+ else -> null
+ }
+ }.beskjærEtter(this)
+
+/**
+ * Extension-metode for å filtrere innholdet i en map av tidslinjer
+ */
+fun Map>.filtrerHverKunVerdi(
+ filter: (V) -> Boolean,
+) = mapValues { (_, tidslinje) -> tidslinje.filtrer { if (it != null) filter(it) else false } }
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/MapTidslinje.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/MapTidslinje.kt
new file mode 100644
index 0000000000..8205e78de6
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/MapTidslinje.kt
@@ -0,0 +1,16 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.tidslinje.Null
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.Udefinert
+import no.nav.familie.tidslinje.Verdi
+import no.nav.familie.tidslinje.utvidelser.map
+
+fun Tidslinje.mapIkkeNull(mapper: (V) -> R?): Tidslinje =
+ this.map { periodeVerdi ->
+ when (periodeVerdi) {
+ is Verdi -> mapper(periodeVerdi.verdi)?.let { Verdi(it) } ?: Null()
+ is Null -> Null()
+ is Udefinert -> Udefinert()
+ }
+ }
diff --git a/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Zip.kt b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Zip.kt
new file mode 100644
index 0000000000..4006e8b00e
--- /dev/null
+++ b/src/main/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Zip.kt
@@ -0,0 +1,40 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.tidslinje.Periode
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.tilTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+
+/**
+ * Returnerer en tidslinje med par av hvert etterfølgende element i tidslinjen.
+ *
+ * val aTilD = "abcd"
+ * val bokstavTidslinje = aTilF.tilCharTidslinje(jan(2020))
+ * val bokstavParTidslinje = bokstavTidslinje.zipMedNeste(ZipPadding.FØR)
+ *
+ * println(bokstavTidslinje) //
+ * 2020-01 - 2020-01: a | 2020-02 - 2020-02: b | 2020-03 - 2020-03: c | 2020-04 - 2020-04: d
+ *
+ * println(bokstavParTidslinje) //
+ * 2020-01 - 2020-01: (null, a) | 2020-02 - 2020-02: (a, b) | 2020-03 - 2020-03: (b, c) | 2020-04 - 2020-04: (c, d)
+ */
+enum class ZipPadding {
+ FØR,
+ ETTER,
+ INGEN_PADDING,
+}
+
+fun Tidslinje.zipMedNeste(zipPadding: ZipPadding = ZipPadding.INGEN_PADDING): Tidslinje> {
+ val padding = listOf(Periode(null, null, null))
+
+ return when (zipPadding) {
+ ZipPadding.FØR -> padding + tilPerioder()
+ ZipPadding.ETTER -> tilPerioder() + padding
+ ZipPadding.INGEN_PADDING -> tilPerioder()
+ }.zipWithNext { forrige, denne ->
+ val verdi = forrige.verdi to denne.verdi
+ val fom = if (zipPadding == ZipPadding.ETTER) forrige.fom else denne.fom
+ val tom = if (zipPadding == ZipPadding.ETTER) forrige.tom else denne.tom
+ Periode(verdi, fom, tom)
+ }.tilTidslinje()
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/TidslinjeKombinasjonTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/TidslinjeKombinasjonTest.kt
new file mode 100644
index 0000000000..348b069038
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/TidslinjeKombinasjonTest.kt
@@ -0,0 +1,87 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilStringTidslinje
+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
+
+class TidslinjeKombinasjonTest {
+ private val kombinator = { venstre: Char?, høyre: Char? ->
+ (venstre?.toString() ?: "").trim() + (høyre?.toString() ?: "").trim()
+ }
+
+ @Test
+ fun testEndeligeLikeLangTidslinjer() {
+ assertTidslinjer(
+ linje1 = "abcdef",
+ linje2 = "fedcba",
+ "af",
+ "be",
+ "cd",
+ "dc",
+ "eb",
+ "fa",
+ )
+ }
+
+ @Test
+ fun testEndeligeTidslinjerMedForskjelligLengde() {
+ assertTidslinjer(
+ linje1 = " ab",
+ linje2 = "fedcba",
+ "f",
+ "e",
+ "ad",
+ "bc",
+ "b",
+ "a",
+ )
+ }
+
+ @Test
+ fun testUendeligeTidslinjerFremover() {
+ assertTidslinjer(
+ linje1 = "abc>",
+ linje2 = "abacd>",
+ "aa",
+ "bb",
+ "ca",
+ "cc",
+ "cd",
+ ">",
+ )
+ }
+
+ @Test
+ fun testUendeligeTidslinjerBeggeVeier() {
+ assertTidslinjer(
+ linje1 = "",
+ )
+ }
+
+ private fun assertTidslinjer(
+ linje1: String,
+ linje2: String,
+ vararg forventet: String,
+ ) {
+ val fom = jan(2020)
+ val char1 = linje1.tilCharTidslinje(fom)
+ val char2 = linje2.tilCharTidslinje(fom)
+
+ val kombinertePerioder = char1.kombinerMed(char2, kombinator).tilPerioder()
+ val forventedePerioder = forventet.toList().tilStringTidslinje(fom).tilPerioder()
+
+ Assertions.assertEquals(forventedePerioder, kombinertePerioder)
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeJoinTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeJoinTest.kt
new file mode 100644
index 0000000000..7243af287d
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeJoinTest.kt
@@ -0,0 +1,62 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.apr
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.mar
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilStringTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class TidslinjeJoinTest {
+ private val tidslinje1 = mapOf("key" to "abc".tilCharTidslinje(jan(2000)))
+ private val tidslinje2 = mapOf("key" to "fgh".tilCharTidslinje(feb(2000)))
+
+ @Test
+ fun join() {
+ val faktiskePerioder =
+ tidslinje1
+ .join(tidslinje2) { c1, c2 ->
+ when {
+ c1 == null && c2 == null -> null
+ c1 == null -> c2.toString()
+ c2 == null -> c1.toString()
+ else -> c1.toString() + c2.toString()
+ }
+ }["key"]!!
+
+ val forventedePerioder = listOf("a", "bf", "cg", "h").tilStringTidslinje(jan(2000))
+
+ assertThat(faktiskePerioder).isEqualTo(forventedePerioder)
+ }
+
+ @Test
+ fun joinIkkeNull() {
+ val faktiskePerioder =
+ tidslinje1
+ .joinIkkeNull(tidslinje2) { c1, c2 ->
+ c1.toString() + c2.toString()
+ }["key"]!!
+ .tilPerioder()
+
+ assertThat(faktiskePerioder).hasSize(4)
+
+ assertThat(faktiskePerioder[0].verdi).isNull()
+ assertThat(faktiskePerioder[0].fom).isEqualTo(1.jan(2000))
+ assertThat(faktiskePerioder[0].tom).isEqualTo(31.jan(2000))
+
+ assertThat(faktiskePerioder[1].verdi).isEqualTo("bf")
+ assertThat(faktiskePerioder[1].fom).isEqualTo(1.feb(2000))
+ assertThat(faktiskePerioder[1].tom).isEqualTo(29.feb(2000))
+
+ assertThat(faktiskePerioder[2].verdi).isEqualTo("cg")
+ assertThat(faktiskePerioder[2].fom).isEqualTo(1.mar(2000))
+ assertThat(faktiskePerioder[2].tom).isEqualTo(31.mar(2000))
+
+ assertThat(faktiskePerioder[3].verdi).isNull()
+ assertThat(faktiskePerioder[3].fom).isEqualTo(1.apr(2000))
+ assertThat(faktiskePerioder[3].tom).isEqualTo(30.apr(2000))
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeKombinatorTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeKombinatorTest.kt
new file mode 100644
index 0000000000..c4482e68c2
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeKombinatorTest.kt
@@ -0,0 +1,120 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilStringTidslinje
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.tomTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class TidslinjeKombinatorTest {
+ /*
+ Dager: | 1. januar | 2. januar | 3. januar | 4. januar | 5. januar |
+ Tidslinje 1: | a | a | a | a |
+ Tidslinje 2: | b | b | b |
+ Tidslinje 3: | c | null | null | c |
+ */
+ private val tidslinje1 = "aaaa".tilCharTidslinje(1.jan(2000))
+ private val tidslinje2 = "bbb".tilCharTidslinje(3.jan(2000))
+ private val tidslinje3 = "c c".tilCharTidslinje(1.jan(2000))
+
+ @Test
+ fun kombinerUtenNull() {
+ val faktisk = listOf(tidslinje1, tidslinje2, tidslinje3).kombinerUtenNull { it.joinToString("") }
+ val forventet = listOf("ac", "a", "ab", "abc", "b").tilStringTidslinje(1.jan(2000))
+
+ assertThat(faktisk).isEqualTo(forventet)
+ }
+
+ @Test
+ fun kombinerUtenNullOgIkkeTom() {
+ val faktisk = listOf(tidslinje1, tidslinje2, tidslinje3).kombinerUtenNullOgIkkeTom { it.joinToString("") }
+ val forventet = listOf("ac", "a", "ab", "abc", "b").tilStringTidslinje(1.jan(2000))
+
+ assertThat(faktisk).isEqualTo(forventet)
+ }
+
+ @Test
+ fun kombiner() {
+ val faktisk = listOf(tidslinje1, tidslinje2, tidslinje3).kombiner().tilPerioder()
+
+ assertThat(faktisk.size).isEqualTo(5)
+ assertThat(faktisk[0].verdi).containsExactly('a', 'c')
+ assertThat(faktisk[1].verdi).containsExactly('a')
+ assertThat(faktisk[2].verdi).containsExactly('a', 'b')
+ assertThat(faktisk[3].verdi).containsExactly('a', 'b', 'c')
+ assertThat(faktisk[4].verdi).containsExactly('b')
+ }
+
+ @Test
+ fun kombinerKunVerdiMedMap() {
+ val tidslinjeMap = mapOf("key1" to tidslinje1, "key2" to tidslinje2)
+
+ val faktisk =
+ tidslinjeMap.kombinerKunVerdiMed(tidslinje3) { c1, c2 ->
+ c1.toString() + c2.toString()
+ }
+
+ val forventet1 = listOf("ac", null, null, "ac").tilStringTidslinje(1.jan(2000))
+ val forventet2 = listOf(null, null, null, "bc", null).tilStringTidslinje(1.jan(2000))
+
+ assertThat(faktisk["key1"]).isEqualTo(forventet1)
+ assertThat(faktisk["key2"]).isEqualTo(forventet2)
+ }
+
+ @Test
+ fun kombinerKunVerdiMed() {
+ val faktisk =
+ tidslinje1.kombinerKunVerdiMed(tidslinje2, tidslinje3) { c1, c2, c3 ->
+ c1.toString() + c2.toString() + c3.toString()
+ }
+
+ val forventet = listOf(null, null, null, "abc", null).tilStringTidslinje(1.jan(2000))
+
+ assertThat(faktisk).isEqualTo(forventet)
+ }
+
+ @Test
+ fun erIkkeTom() {
+ assertThat(tomTidslinje().erIkkeTom()).isFalse()
+ assertThat("a".tilCharTidslinje(1.jan(2000)).erIkkeTom()).isTrue()
+ }
+
+ @Test
+ fun harOverlappMed() {
+ assertThat(tidslinje1.harOverlappMed(tidslinje2)).isTrue()
+
+ val tidslinjeUtenOverlappMedTidslinje1 = "a".tilCharTidslinje(1.feb(2000))
+ assertThat(tidslinje1.harOverlappMed(tidslinjeUtenOverlappMedTidslinje1)).isFalse()
+ }
+
+ @Test
+ fun harIkkeOverlappMed() {
+ assertThat(tidslinje1.harIkkeOverlappMed(tidslinje2)).isFalse()
+
+ val tidslinjeUtenOverlappMedTidslinje1 = "a".tilCharTidslinje(1.feb(2000))
+ assertThat(tidslinje1.harIkkeOverlappMed(tidslinjeUtenOverlappMedTidslinje1)).isTrue()
+ }
+
+ @Test
+ fun `kombinerMedNullable skal returnere this hvis den andre tidslinjen er null`() {
+ val nullTidslinje: Tidslinje? = null
+ val faktisk = tidslinje1.kombinerMedNullable(nullTidslinje) { c1, c2 -> c1 }
+ assertThat(faktisk).isEqualTo(tidslinje1)
+ }
+
+ @Test
+ fun kombinerMedNullable() {
+ val faktisk =
+ tidslinje1.kombinerMedNullable(tidslinje2) { c1, c2 ->
+ if (c1 == null || c2 == null) null else c1
+ }
+
+ val forventet = " aa ".tilCharTidslinje(1.jan(2000))
+
+ assertThat(faktisk).isEqualTo(forventet)
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeUtilTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeUtilTest.kt
new file mode 100644
index 0000000000..9f8ba2643f
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/komposisjon/TidslinjeUtilTest.kt
@@ -0,0 +1,69 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.komposisjon
+
+import no.nav.familie.ba.sak.datagenerator.lagPerson
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.des
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.mar
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class TidslinjeUtilTest {
+ private val person = lagPerson(fødselsdato = 15.jan(2020))
+
+ @Test
+ fun erUnder18ÅrVilkårTidslinje() {
+ val erUnder18ÅrVilkårTidslinje = erUnder18ÅrVilkårTidslinje(person.fødselsdato)
+ val perioder = erUnder18ÅrVilkårTidslinje.tilPerioder()
+
+ assertThat(perioder).hasSize(1)
+ assertThat(perioder[0].verdi).isTrue()
+ assertThat(perioder[0].fom).isEqualTo(1.feb(2020))
+ assertThat(perioder[0].tom).isEqualTo(31.des(2037))
+ }
+
+ @Test
+ fun erUnder6ÅrTidslinje() {
+ val erUnder6ÅrTidslinje = erUnder6ÅrTidslinje(person)
+ val perioder = erUnder6ÅrTidslinje.tilPerioder()
+
+ assertThat(perioder).hasSize(1)
+ assertThat(perioder[0].verdi).isTrue()
+ assertThat(perioder[0].fom).isEqualTo(1.jan(2020))
+ assertThat(perioder[0].tom).isEqualTo(31.des(2025))
+ }
+
+ @Test
+ fun erTilogMed3ÅrTidslinje() {
+ val erUnder6ÅrTidslinje = erTilogMed3ÅrTidslinje(person.fødselsdato)
+ val perioder = erUnder6ÅrTidslinje.tilPerioder()
+
+ assertThat(perioder).hasSize(1)
+ assertThat(perioder[0].verdi).isTrue()
+ assertThat(perioder[0].fom).isEqualTo(1.feb(2020))
+ assertThat(perioder[0].tom).isEqualTo(31.jan(2023))
+ }
+
+ @Test
+ fun opprettBooleanTidslinjeYearMonth() {
+ val tidslinje = opprettBooleanTidslinje(jan(2020), mar(2020))
+ val perioder = tidslinje.tilPerioder()
+
+ assertThat(perioder).hasSize(1)
+ assertThat(perioder[0].verdi).isTrue()
+ assertThat(perioder[0].fom).isEqualTo(1.jan(2020))
+ assertThat(perioder[0].tom).isEqualTo(31.mar(2020))
+ }
+
+ @Test
+ fun opprettBooleanTidslinjeLocalDate() {
+ val tidslinje = opprettBooleanTidslinje(15.jan(2020), 15.mar(2020))
+ val perioder = tidslinje.tilPerioder()
+
+ assertThat(perioder).hasSize(1)
+ assertThat(perioder[0].verdi).isTrue()
+ assertThat(perioder[0].fom).isEqualTo(15.jan(2020))
+ assertThat(perioder[0].tom).isEqualTo(15.mar(2020))
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/BigDecimalTidslinjeTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/BigDecimalTidslinjeTest.kt
new file mode 100644
index 0000000000..221b5ef520
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/BigDecimalTidslinjeTest.kt
@@ -0,0 +1,62 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.matematikk
+
+import no.nav.familie.ba.sak.datagenerator.randomAktør
+import no.nav.familie.tidslinje.Periode
+import no.nav.familie.tidslinje.tilTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import java.math.BigDecimal
+import java.time.LocalDate
+
+class BigDecimalTidslinjeTest {
+ private val tidslinje1 =
+ listOf(BigDecimal(1.5), BigDecimal(2.5), BigDecimal(4))
+ .mapIndexed { index, verdi ->
+ Periode(
+ verdi = verdi,
+ fom = LocalDate.now().plusDays(index.toLong()),
+ tom = LocalDate.now().plusDays(index.toLong()),
+ )
+ }.tilTidslinje()
+
+ private val tidslinje2 =
+ listOf(BigDecimal(10), BigDecimal(10), BigDecimal(10))
+ .mapIndexed { index, verdi ->
+ Periode(
+ verdi = verdi,
+ fom = LocalDate.now().plusDays(index.toLong()),
+ tom = LocalDate.now().plusDays(index.toLong()),
+ )
+ }.tilTidslinje()
+
+ private val tidslinjeMap =
+ mapOf(
+ randomAktør() to tidslinje1,
+ randomAktør() to tidslinje2,
+ )
+
+ @Test
+ fun minus() {
+ val aktør = randomAktør()
+ val tidslinje1AsMap = mapOf(aktør to tidslinje1)
+ val tidslinje2AsMap = mapOf(aktør to tidslinje2)
+ val subtrahertePerioder = tidslinje2AsMap.minus(tidslinje1AsMap)[aktør]!!.tilPerioder().map { it.verdi }
+
+ assertThat(subtrahertePerioder).containsExactly(BigDecimal(8.5), BigDecimal(7.5), BigDecimal(6))
+ }
+
+ @Test
+ fun sum() {
+ val summertePerioder = tidslinjeMap.sum().tilPerioder()
+
+ assertThat(summertePerioder.map { it.verdi }).containsExactly(BigDecimal(11.5), BigDecimal(12.5), BigDecimal(14))
+ }
+
+ @Test
+ fun rundAvTilHeleTall() {
+ val avrundedeTall = tidslinje1.rundAvTilHeltall().tilPerioder().map { it.verdi }
+
+ assertThat(avrundedeTall).containsExactly(BigDecimal(2), BigDecimal(3), BigDecimal(4))
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/SammenliknbarTidslinjeTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/SammenliknbarTidslinjeTest.kt
new file mode 100644
index 0000000000..69d7e458c0
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/matematikk/SammenliknbarTidslinjeTest.kt
@@ -0,0 +1,19 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.matematikk
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class SammenliknbarTidslinjeTest {
+ @Test
+ fun minsteAvHver() {
+ val tidslinje1 = mapOf(1 to "acegi".tilCharTidslinje(jan(2000)))
+ val tidslinje2 = mapOf(1 to "bbddi".tilCharTidslinje(jan(2000)))
+
+ val minsteAvHver = minsteAvHver(tidslinje1, tidslinje2)[1]!!
+ val forventetTidslinje = "abddi".tilCharTidslinje(jan(2000))
+
+ assertThat(minsteAvHver).isEqualTo(forventetTidslinje)
+ }
+}
diff --git "a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Beskj\303\246reTidslinjeTest.kt" "b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Beskj\303\246reTidslinjeTest.kt"
new file mode 100644
index 0000000000..2ad01abcaa
--- /dev/null
+++ "b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/Beskj\303\246reTidslinjeTest.kt"
@@ -0,0 +1,225 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.apr
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.des
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.mar
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.tidslinje.TidsEnhet.MÅNED
+import no.nav.familie.tidslinje.beskjærEtter
+import no.nav.familie.tidslinje.tomTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.junit.Assert.assertEquals
+import org.junit.jupiter.api.Test
+
+internal class BeskjæreTidslinjeTest {
+ @Test
+ fun `skal beskjære endelig tidslinje på begge sider`() {
+ val hovedlinje = "aaaaaa".tilCharTidslinje(des(2001))
+ val beskjæring = "bbb".tilCharTidslinje(feb(2002))
+
+ val faktiskePerioder = hovedlinje.beskjærEtter(beskjæring).tilPerioder()
+ val forventedePerioder = "aaa".tilCharTidslinje(feb(2002)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun `skal beholde tidslinje som allerede er innenfor beskjæring`() {
+ val hovedlinje = "aaa".tilCharTidslinje(feb(2002))
+ val beskjæring = "bbbbbbbbb".tilCharTidslinje(des(2001))
+
+ val faktiskePerioder = hovedlinje.beskjærEtter(beskjæring).tilPerioder()
+ val forventedePerioder = "aaa".tilCharTidslinje(feb(2002)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun `skal beholde tidslinje som er innenfor en uendelig beskjæring`() {
+ val hovedlinje = "aaa".tilCharTidslinje(feb(2002))
+ val beskjæring = "".tilCharTidslinje(mar(2002))
+
+ val faktiskePerioder = hovedlinje.beskjærEtter(beskjæring).tilPerioder()
+ val forventedePerioder = "aaa".tilCharTidslinje(feb(2002)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun `beskjæring utenfor tidslinjen skal gi tom tidslinje`() {
+ val hovedlinje = "aaaaaa".tilCharTidslinje(des(2001))
+ val beskjæring = "bbb".tilCharTidslinje(feb(2009))
+
+ val faktiskePerioder = hovedlinje.beskjærEtter(beskjæring).tilPerioder()
+ val forventedePerioder = tomTidslinje(beskjæring.startsTidspunkt, MÅNED).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun `skal beskjære uendelig tidslinje begge veier mot endelig tidsline`() {
+ val hovedlinje = "".tilCharTidslinje(des(2002))
+ val beskjæring = "bbb".tilCharTidslinje(feb(2002))
+
+ val faktiskePerioder = hovedlinje.beskjærEtter(beskjæring).tilPerioder()
+ val forventedePerioder = "aaa".tilCharTidslinje(feb(2002)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun `skal beskjære tidslinje som går fra uendelig lenge siden til et endelig tidspunkt i fremtiden`() {
+ val hovedlinje = "".tilCharTidslinje(feb(2002))
+
+ val faktiskePerioder = hovedlinje.beskjærEtter(beskjæring).tilPerioder()
+ val forventedePerioder = "a>".tilCharTidslinje(feb(2002)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun `skal beskjære uendelig fortid slik at den inneholder tidligste fra-og-med, beskjæring er tidligst`() {
+ val hovedlinje = "()).tilPerioder()
+ val forventedePerioder = tomTidslinje(tidsEnhet = MÅNED).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun beskjærTilOgMedEtterTomTidslinje() {
+ val hovedlinje = "aaaaa".tilCharTidslinje(jan(2000))
+ val beskjæring = tomTidslinje()
+
+ val faktiskePerioder = hovedlinje.beskjærTilOgMedEtter(beskjæring).tilPerioder()
+ val forventedePerioder = tomTidslinje().tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun beskjærTilOgMedEtter() {
+ val hovedlinje = "aaaaa".tilCharTidslinje(jan(2000))
+ val beskjæring = "bbb".tilCharTidslinje(feb(2000))
+
+ val faktiskePerioder = hovedlinje.beskjærTilOgMedEtter(beskjæring).tilPerioder()
+ val forventedePerioder = "aaaa".tilCharTidslinje(jan(2000)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun beskjær() {
+ val hovedlinje = "aaaaa".tilCharTidslinje(jan(2000))
+ val fom = 1.feb(2000)
+ val tom = 30.apr(2000)
+
+ val faktiskePerioder = hovedlinje.beskjær(fom, tom).tilPerioder()
+ val forventedePerioder = "aaa".tilCharTidslinje(feb(2000)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun beskjærFom() {
+ val hovedlinje = "aaaaa".tilCharTidslinje(jan(2000))
+ val fom = 1.feb(2000)
+
+ val faktiskePerioder = hovedlinje.beskjærFraOgMed(fom).tilPerioder()
+ val forventedePerioder = "aaaa".tilCharTidslinje(feb(2000)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun beskjærTom() {
+ val hovedlinje = "aaaaa".tilCharTidslinje(jan(2000))
+ val tom = 30.apr(2000)
+
+ val faktiskePerioder = hovedlinje.beskjærTilOgMed(tom).tilPerioder()
+ val forventedePerioder = "aaaa".tilCharTidslinje(jan(2000)).tilPerioder()
+
+ assertEquals(forventedePerioder, faktiskePerioder)
+ }
+
+ @Test
+ fun beskjærTomMap() {
+ val hovedlinje1 = 1 to "aaaaa".tilCharTidslinje(jan(2000))
+ val hovedlinje2 = 2 to "aaaaa".tilCharTidslinje(feb(2000))
+ val tidslinjeMap = mapOf(hovedlinje1, hovedlinje2)
+ val tom = 30.apr(2000)
+
+ val faktiskePerioder = tidslinjeMap.beskjærTilOgMed(tom)
+ val forventedePerioder1 = "aaaa".tilCharTidslinje(jan(2000)).tilPerioder()
+ val forventedePerioder2 = "aaa".tilCharTidslinje(feb(2000)).tilPerioder()
+
+ assertEquals(forventedePerioder1, faktiskePerioder[1]!!.tilPerioder())
+ assertEquals(forventedePerioder2, faktiskePerioder[2]!!.tilPerioder())
+ }
+
+ @Test
+ fun `beskjæring av tom tidslinje skal gi tom tidslinje`() {
+ val hovedlinje = tomTidslinje()
+ val beskjæring = "aaa".tilCharTidslinje(jan(2000))
+ val forventedePerioder = tomTidslinje().tilPerioder()
+
+ val faktiskePerioder =
+ listOf(
+ hovedlinje.beskjærEtter(beskjæring).tilPerioder(),
+ hovedlinje.beskjærTilOgMedEtter(beskjæring).tilPerioder(),
+ hovedlinje.beskjærTilOgMed(beskjæring.startsTidspunkt).tilPerioder(),
+ hovedlinje.beskjærFraOgMed(beskjæring.kalkulerSluttTidspunkt()).tilPerioder(),
+ hovedlinje.beskjær(beskjæring.startsTidspunkt, beskjæring.kalkulerSluttTidspunkt()).tilPerioder(),
+ )
+
+ faktiskePerioder.forEach { assertEquals(forventedePerioder, it) }
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/EndreTid.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/EndreTid.kt
new file mode 100644
index 0000000000..0655ea94fb
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/EndreTid.kt
@@ -0,0 +1,46 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class EndreTid {
+ @Test
+ fun tilMåned() {
+ val tidslinje = "abcdef".tilCharTidslinje(29.jan(2000)) // Månedskille mellom c og d
+ val månedPerioder = tidslinje.tilMåned { it.filterNotNull().max() }.tilPerioder()
+
+ assertThat(månedPerioder.size).isEqualTo(2)
+
+ assertThat(månedPerioder[0].verdi).isEqualTo('c')
+ assertThat(månedPerioder[0].fom).isEqualTo(1.jan(2000))
+ assertThat(månedPerioder[0].tom).isEqualTo(31.jan(2000))
+
+ assertThat(månedPerioder[1].verdi).isEqualTo('f')
+ assertThat(månedPerioder[1].fom).isEqualTo(1.feb(2000))
+ assertThat(månedPerioder[1].tom).isEqualTo(29.feb(2000))
+ }
+
+ @Test
+ fun tilMånedFraMånedskifte() {
+ val tidslinje = "abcdef".tilCharTidslinje(29.jan(2000)) // Månedskille mellom c og d
+ val månedPerioder =
+ tidslinje
+ .tilMånedFraMånedsskifte { innholdSisteDagForrigeMåned, innholdFørsteDagDenneMåned ->
+ innholdSisteDagForrigeMåned ?: innholdFørsteDagDenneMåned
+ }.tilPerioder()
+
+ assertThat(månedPerioder.size).isEqualTo(2)
+
+ assertThat(månedPerioder[0].verdi).isEqualTo('a')
+ assertThat(månedPerioder[0].fom).isEqualTo(1.jan(2000))
+ assertThat(månedPerioder[0].tom).isEqualTo(31.jan(2000))
+
+ assertThat(månedPerioder[1].verdi).isEqualTo('c')
+ assertThat(månedPerioder[1].fom).isEqualTo(1.feb(2000))
+ assertThat(månedPerioder[1].tom).isEqualTo(29.feb(2000))
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/FiltrerTidslinjeTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/FiltrerTidslinjeTest.kt
new file mode 100644
index 0000000000..2e33c48de8
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/FiltrerTidslinjeTest.kt
@@ -0,0 +1,57 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.apr
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jul
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jun
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.mai
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.mar
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.somBoolskTidslinje
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.tidslinje.Periode
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class FiltrerTidslinjeTest {
+ @Test
+ fun filtrerIkkeNull() {
+ val tidslinje = "aa cc".tilCharTidslinje(feb(2000))
+ val opprinneligePerioder = tidslinje.tilPerioder()
+
+ assertThat(opprinneligePerioder[0]).isEqualTo(Periode('a', 1.feb(2000), 31.mar(2000)))
+ assertThat(opprinneligePerioder[1]).isEqualTo(Periode(null, 1.apr(2000), 31.mai(2000)))
+ assertThat(opprinneligePerioder[2]).isEqualTo(Periode('c', 1.jun(2000), 31.jul(2000)))
+
+ val filtrertePerioder = tidslinje.filtrerIkkeNull { it == 'a' }.tilPerioder()
+
+ assertThat(filtrertePerioder[0]).isEqualTo(Periode('a', 1.feb(2000), 31.mar(2000)))
+ assertThat(filtrertePerioder[1]).isEqualTo(Periode(null, 1.apr(2000), 31.jul(2000)))
+ }
+
+ @Test
+ fun filtrerMed() {
+ val tidslinje = "aaaaa".tilCharTidslinje(feb(2000))
+ val boolskTidslinje = "ftftftf".somBoolskTidslinje(jan(2000))
+
+ val filtrertePerioder = tidslinje.filtrerMed(boolskTidslinje).tilPerioder()
+
+ assertThat(filtrertePerioder.map { it.verdi }).containsExactly('a', null, 'a', null, 'a')
+ }
+
+ @Test
+ fun filtrerHverKunVerdi() {
+ val tidslinje1 = "ababa".tilCharTidslinje(jan(2000))
+ val tidslinje2 = "babab".tilCharTidslinje(jan(2000))
+
+ val filtrerteTidslinjer =
+ mapOf(
+ "1" to tidslinje1,
+ "2" to tidslinje2,
+ ).filtrerHverKunVerdi { it == 'a' }
+
+ assertThat(filtrerteTidslinjer["1"]?.tilPerioder()?.map { it.verdi }).containsExactly('a', null, 'a', null, 'a')
+ assertThat(filtrerteTidslinjer["2"]?.tilPerioder()?.map { it.verdi }).containsExactly(null, 'a', null, 'a', null)
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/MapTidslinjeTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/MapTidslinjeTest.kt
new file mode 100644
index 0000000000..339c251f40
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/MapTidslinjeTest.kt
@@ -0,0 +1,22 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.tidslinje.mapVerdi
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+internal class MapTidslinjeTest {
+ @Test
+ fun `skal mappe innhold og fjerne null`() {
+ val tidslinje = " AAA BB CCC ".tilCharTidslinje(1.jan(2020))
+
+ val faktisk = tidslinje.mapIkkeNull { it.lowercase() }
+
+ val forventet = " aaa bb ccc ".tilCharTidslinje(1.jan(2020)).mapVerdi { it?.toString() }
+
+ assertEquals(forventet, faktisk)
+ assertEquals(1.jan(2020), faktisk.startsTidspunkt)
+ assertEquals(16.jan(2020), faktisk.kalkulerSluttTidspunkt())
+ }
+}
diff --git "a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/M\303\245nedFraM\303\245nedsskifteTest.kt" "b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/M\303\245nedFraM\303\245nedsskifteTest.kt"
new file mode 100644
index 0000000000..125ddb4a9e
--- /dev/null
+++ "b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/M\303\245nedFraM\303\245nedsskifteTest.kt"
@@ -0,0 +1,85 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.des
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.feb
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.jan
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.nov
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.tidslinje.TidsEnhet
+import no.nav.familie.tidslinje.tomTidslinje
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+
+internal class MånedFraMånedsskifteTest {
+ @Test
+ fun `skal gi tom tidslinje hvis alle dager er inni én måned`() {
+ val dagTidslinje = "aaaaaa".tilCharTidslinje(7.des(2021))
+ val månedTidslinje = dagTidslinje.tilMånedFraMånedsskifteIkkeNull { _, _ -> 'b' }
+
+ val forventet = tomTidslinje(1.jan(2022), tidsEnhet = TidsEnhet.MÅNED)
+
+ assertEquals(forventet, månedTidslinje)
+ }
+
+ @Test
+ fun `skal gi én måned ved ett månedsskifte`() {
+ val dagTidslinje = "abcdefg".tilCharTidslinje(28.nov(2021))
+ val månedTidslinje =
+ dagTidslinje
+ .tilMånedFraMånedsskifteIkkeNull { _, verdiFørsteDagDenneMåned ->
+ verdiFørsteDagDenneMåned
+ }
+
+ assertEquals("d".tilCharTidslinje(des(2021)), månedTidslinje)
+ }
+
+ @Test
+ fun `skal gi to måneder ved to månedsskifter`() {
+ val dagTidslinje = "abcdefghijklmnopqrstuvwxyzæøå0123456789".tilCharTidslinje(28.nov(2021))
+ val månedTidslinje =
+ dagTidslinje
+ .tilMånedFraMånedsskifteIkkeNull { verdiSisteDagForrigeMåned, _ ->
+ verdiSisteDagForrigeMåned
+ }
+
+ assertEquals("c4".tilCharTidslinje(des(2021)), månedTidslinje)
+ }
+
+ @Test
+ fun `skal gi tom tidslinje hvis månedsskiftet mangler verdi på begge sider`() {
+ val dagTidslinje =
+ "abcdefghijklmnopqrstuvwxyzæøå0123456789"
+ .tilCharTidslinje(28.nov(2021))
+ .mapIkkeNull {
+ when (it) {
+ 'c', 'd', '4', '5' -> null // 30/11, 1/12, 31/12 og 1/1 mangler verdi
+ else -> it
+ }
+ }
+
+ val månedTidslinje = dagTidslinje.tilMånedFraMånedsskifteIkkeNull { _, _ -> 'A' }
+
+ val forventet = tomTidslinje(1.feb(2022), tidsEnhet = TidsEnhet.MÅNED)
+
+ assertEquals(forventet, månedTidslinje)
+ }
+
+ @Test
+ fun `skal gi tom tidslinje hvis månedsskiftet mangler verdi på én av sidene`() {
+ val dagTidslinje =
+ "abcdefghijklmnopqrstuvwxyzæøå0123456789"
+ .tilCharTidslinje(28.nov(2021))
+ .mapIkkeNull {
+ when (it) {
+ 'c', '5' -> null // 30/11 og 1/1 mangler verdi
+ else -> it
+ }
+ }
+
+ val månedTidslinje = dagTidslinje.tilMånedFraMånedsskifteIkkeNull { _, _ -> 'A' }
+
+ val forventet = tomTidslinje(1.feb(2022), tidsEnhet = TidsEnhet.MÅNED)
+
+ assertEquals(forventet, månedTidslinje)
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/ZipTidslinjeTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/ZipTidslinjeTest.kt
new file mode 100644
index 0000000000..4812f3ac4f
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/transformasjon/ZipTidslinjeTest.kt
@@ -0,0 +1,48 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon
+
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util.tilCharTidslinje
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import java.time.YearMonth
+
+class ZipTidslinjeTest {
+ @Test
+ fun testZipMedNesteTidslinje() {
+ val aTilF = ('a'..'f').toList().joinToString("")
+ val bokstavTidslinje = aTilF.tilCharTidslinje(YearMonth.now())
+ val bokstavParTidslinje = bokstavTidslinje.zipMedNeste(ZipPadding.FØR)
+
+ assertThat(aTilF).isEqualTo("abcdef")
+
+ assertThat(bokstavParTidslinje.tilPerioder().map { it.verdi }).isEqualTo(
+ listOf(Pair(null, 'a'), Pair('a', 'b'), Pair('b', 'c'), Pair('c', 'd'), Pair('d', 'e'), Pair('e', 'f')),
+ )
+ }
+
+ @Test
+ fun testZipMedNesteTidslinjePaddingEtter() {
+ val aTilF = ('a'..'f').toList().joinToString("")
+ val bokstavTidslinje = aTilF.tilCharTidslinje(YearMonth.now())
+ val bokstavParTidslinje = bokstavTidslinje.zipMedNeste(ZipPadding.ETTER)
+
+ assertThat(aTilF).isEqualTo("abcdef")
+
+ assertThat(bokstavParTidslinje.tilPerioder().map { it.verdi }).isEqualTo(
+ listOf(Pair('a', 'b'), Pair('b', 'c'), Pair('c', 'd'), Pair('d', 'e'), Pair('e', 'f'), Pair('f', null)),
+ )
+ }
+
+ @Test
+ fun testZipMedNesteTidslinjeIngenPadding() {
+ val aTilF = ('a'..'f').toList().joinToString("")
+ val bokstavTidslinje = aTilF.tilCharTidslinje(YearMonth.now())
+ val bokstavParTidslinje = bokstavTidslinje.zipMedNeste(ZipPadding.INGEN_PADDING)
+
+ assertThat(aTilF).isEqualTo("abcdef")
+
+ assertThat(bokstavParTidslinje.tilPerioder().map { it.verdi }).isEqualTo(
+ listOf(Pair('a', 'b'), Pair('b', 'c'), Pair('c', 'd'), Pair('d', 'e'), Pair('e', 'f')),
+ )
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/BooleanCharTidslinje.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/BooleanCharTidslinje.kt
new file mode 100644
index 0000000000..6d176c9bec
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/BooleanCharTidslinje.kt
@@ -0,0 +1,16 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util
+
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.mapVerdi
+import java.time.YearMonth
+
+fun String.somBoolskTidslinje(startTidspunkt: YearMonth) = this.tilCharTidslinje(startTidspunkt).somBoolsk()
+
+fun Tidslinje.somBoolsk() =
+ this.mapVerdi {
+ when (it?.lowercaseChar()) {
+ 't' -> true
+ 'f' -> false
+ else -> null
+ }
+ }
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/CharTidslinje.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/CharTidslinje.kt
new file mode 100644
index 0000000000..7de58db41b
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/CharTidslinje.kt
@@ -0,0 +1,41 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util
+
+import no.nav.familie.ba.sak.common.førsteDagIInneværendeMåned
+import no.nav.familie.ba.sak.common.sisteDagIInneværendeMåned
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon.tilMåned
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.TidslinjePeriodeMedDato
+import no.nav.familie.tidslinje.tilTidslinje
+import java.time.LocalDate
+import java.time.YearMonth
+
+fun String.tilCharTidslinje(startTidspunkt: LocalDate): Tidslinje {
+ val erUendeligLengeSiden = firstOrNull() == '<'
+ val erUendeligLengeTil = lastOrNull() == '>'
+ val sisteIndeks = filter { it !in "<>" }.lastIndex
+ return this
+ .filter { it !in "<>" }
+ .mapIndexed { index, c ->
+ TidslinjePeriodeMedDato(
+ verdi = if (c == ' ') null else c,
+ fom = if (index == 0 && erUendeligLengeSiden) null else startTidspunkt.plusDays(index.toLong()),
+ tom = if (index == sisteIndeks && erUendeligLengeTil) null else startTidspunkt.plusDays(index.toLong()),
+ )
+ }.tilTidslinje()
+}
+
+fun String.tilCharTidslinje(startTidspunkt: YearMonth): Tidslinje {
+ val erUendeligLengeSiden = firstOrNull() == '<'
+ val erUendeligLengeTil = lastOrNull() == '>'
+ val sisteIndeks = filter { it !in "<>" }.lastIndex
+ return this
+ .filter { it !in "<>" }
+ .mapIndexed { index, c ->
+ TidslinjePeriodeMedDato(
+ verdi = if (c == ' ') null else c,
+ fom = if (index == 0 && erUendeligLengeSiden) null else startTidspunkt.plusMonths(index.toLong()).førsteDagIInneværendeMåned(),
+ tom = if (index == sisteIndeks && erUendeligLengeTil) null else startTidspunkt.plusMonths(index.toLong()).sisteDagIInneværendeMåned(),
+ )
+ }.tilTidslinje()
+ .tilMåned { it.first() }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/CharTidslinjeTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/CharTidslinjeTest.kt
new file mode 100644
index 0000000000..f8a6ab1118
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/CharTidslinjeTest.kt
@@ -0,0 +1,56 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util
+
+import no.nav.familie.ba.sak.common.rangeTo
+import no.nav.familie.tidslinje.PRAKTISK_TIDLIGSTE_DAG
+import no.nav.familie.tidslinje.utvidelser.tilPerioder
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.Test
+
+class CharTidslinjeTest {
+ @Test
+ fun testEnkelCharTidsline() {
+ val tegn = "---------------"
+ val charTidslinje = tegn.tilCharTidslinje(jan(2020))
+
+ assertEquals(1.jan(2020), charTidslinje.startsTidspunkt)
+ assertEquals(tegn.length, charTidslinje.innhold.sumOf { it.lengde })
+
+ val periode = charTidslinje.tilPerioder().single()
+
+ assertEquals(1.jan(2020), periode.fom)
+ assertEquals(31.mar(2021), periode.tom)
+ assertEquals('-', periode.verdi)
+ }
+
+ @Test
+ fun testUendeligCharTidslinje() {
+ val tegn = "<--->"
+ val charTidslinje = tegn.tilCharTidslinje(jan(2020))
+
+ assertEquals(PRAKTISK_TIDLIGSTE_DAG, charTidslinje.startsTidspunkt)
+
+ val periode = charTidslinje.tilPerioder().single()
+
+ assertNull(periode.fom)
+ assertNull(periode.tom)
+ assertEquals('-', periode.verdi)
+ }
+
+ @Test
+ fun testSammensattTidsline() {
+ val tegn = "aabbbbcdddddda"
+ val charTidslinje = tegn.tilCharTidslinje(jan(2020))
+
+ assertEquals(1.jan(2020), charTidslinje.startsTidspunkt)
+ assertEquals(tegn.length, charTidslinje.innhold.sumOf { it.lengde })
+
+ val perioder = charTidslinje.tilPerioder()
+ assertEquals(5, perioder.size)
+ assertEquals((jan(2020)..feb(2020)).med('a'), perioder[0])
+ assertEquals((mar(2020)..jun(2020)).med('b'), perioder[1])
+ assertEquals((jul(2020)..jul(2020)).med('c'), perioder[2])
+ assertEquals((aug(2020)..jan(2021)).med('d'), perioder[3])
+ assertEquals((feb(2021)..feb(2021)).med('a'), perioder[4])
+ }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/StringTidslinje.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/StringTidslinje.kt
new file mode 100644
index 0000000000..71b3331dc3
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/StringTidslinje.kt
@@ -0,0 +1,43 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util
+
+import no.nav.familie.ba.sak.common.førsteDagIInneværendeMåned
+import no.nav.familie.ba.sak.common.sisteDagIInneværendeMåned
+import no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.transformasjon.tilMåned
+import no.nav.familie.tidslinje.Tidslinje
+import no.nav.familie.tidslinje.TidslinjePeriodeMedDato
+import no.nav.familie.tidslinje.tilTidslinje
+import java.time.LocalDate
+import java.time.YearMonth
+
+fun List.tilStringTidslinje(startTidspunkt: LocalDate): Tidslinje {
+ val erUendeligLengeSiden = firstOrNull() == "<"
+ val erUendeligLengeTil = lastOrNull() == ">"
+ val sisteIndeks = filter { it != "<" && it != ">" }.lastIndex
+
+ return this
+ .filter { it != "<" && it != ">" }
+ .mapIndexed { index, s ->
+ TidslinjePeriodeMedDato(
+ verdi = s,
+ fom = if (index == 0 && erUendeligLengeSiden) null else startTidspunkt.plusDays(index.toLong()),
+ tom = if (index == sisteIndeks && erUendeligLengeTil) null else startTidspunkt.plusDays(index.toLong()),
+ )
+ }.tilTidslinje()
+}
+
+fun List.tilStringTidslinje(startTidspunkt: YearMonth): Tidslinje {
+ val erUendeligLengeSiden = firstOrNull() == "<"
+ val erUendeligLengeTil = lastOrNull() == ">"
+ val sisteIndeks = filter { it != "<" && it != ">" }.lastIndex
+
+ return this
+ .filter { it != "<" && it != ">" }
+ .mapIndexed { index, s ->
+ TidslinjePeriodeMedDato(
+ verdi = s,
+ fom = if (index == 0 && erUendeligLengeSiden) null else startTidspunkt.plusMonths(index.toLong()).førsteDagIInneværendeMåned(),
+ tom = if (index == sisteIndeks && erUendeligLengeTil) null else startTidspunkt.plusMonths(index.toLong()).sisteDagIInneværendeMåned(),
+ )
+ }.tilTidslinje()
+ .tilMåned { it.first() }
+}
diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/Tid.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/Tid.kt
new file mode 100644
index 0000000000..276b86569b
--- /dev/null
+++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/kjerne/tidslinjefamiliefelles/util/Tid.kt
@@ -0,0 +1,58 @@
+package no.nav.familie.ba.sak.kjerne.tidslinjefamiliefelles.util
+
+import no.nav.familie.ba.sak.common.YearMonthProgression
+import no.nav.familie.ba.sak.common.førsteDagIInneværendeMåned
+import no.nav.familie.ba.sak.common.sisteDagIInneværendeMåned
+import no.nav.familie.tidslinje.Periode
+import java.time.LocalDate
+import java.time.YearMonth
+
+fun jan(år: Int) = YearMonth.of(år, 1)
+
+fun feb(år: Int) = YearMonth.of(år, 2)
+
+fun mar(år: Int) = YearMonth.of(år, 3)
+
+fun apr(år: Int) = YearMonth.of(år, 4)
+
+fun mai(år: Int) = YearMonth.of(år, 5)
+
+fun jun(år: Int) = YearMonth.of(år, 6)
+
+fun jul(år: Int) = YearMonth.of(år, 7)
+
+fun aug(år: Int) = YearMonth.of(år, 8)
+
+fun sep(år: Int) = YearMonth.of(år, 9)
+
+fun okt(år: Int) = YearMonth.of(år, 10)
+
+fun nov(år: Int) = YearMonth.of(år, 11)
+
+fun des(år: Int) = YearMonth.of(år, 12)
+
+fun Int.jan(år: Int) = LocalDate.of(år, 1, this)
+
+fun Int.feb(år: Int) = LocalDate.of(år, 2, this)
+
+fun Int.mar(år: Int) = LocalDate.of(år, 3, this)
+
+fun Int.apr(år: Int) = LocalDate.of(år, 4, this)
+
+fun Int.mai(år: Int) = LocalDate.of(år, 5, this)
+
+fun Int.jun(år: Int) = LocalDate.of(år, 6, this)
+
+fun Int.jul(år: Int) = LocalDate.of(år, 7, this)
+
+fun Int.aug(år: Int) = LocalDate.of(år, 8, this)
+
+fun Int.sep(år: Int) = LocalDate.of(år, 9, this)
+
+fun Int.okt(år: Int) = LocalDate.of(år, 10, this)
+
+fun Int.nov(år: Int) = LocalDate.of(år, 11, this)
+
+fun Int.des(år: Int) = LocalDate.of(år, 12, this)
+
+fun YearMonthProgression.med(verdi: V) = Periode(verdi, this.start.førsteDagIInneværendeMåned(), this.endInclusive.sisteDagIInneværendeMåned())