【Kotlin】連続した期間を結合する

https://qiita.com/akinosora/items/21501fa98252f570a293

↑これをやってみた。

## 本体

```Kotlin
import java.time.LocalDate

/**
 * すべての要素の期間を結合した期間を返す。
 * 期間が連続していない場合は null を返す。
 */
fun Iterable<ClosedRange<LocalDate>>.joinOrNull(): ClosedRange<LocalDate>? =
    sortedBy { it.start }
        .takeIf { it.isNotEmpty() }
        ?.reduce { acc, range ->
            joinOrNull(acc, range)
                ?: return null
        }

/**
 * 2つの期間を結合する。
 * 期間が連続していない場合は null を返す。
 *
 * @param startEarly 開始日が早い方の期間。
 * @param startLate 開始日が遅い方の期間。
 */
private fun joinOrNull(
    startEarly: ClosedRange<LocalDate>,
    startLate: ClosedRange<LocalDate>
): ClosedRange<LocalDate>? {
    if (startEarly.endInclusive < LocalDate.MAX) {
        if (startEarly.endInclusive.plusDays(1) < startLate.start) {
            return null
        }
    } else {
        if (startEarly.endInclusive < startLate.start.minusDays(1)) {
            return null
        }
    }

    return startEarly.start..maxOf(
        startEarly.endInclusive,
        startLate.endInclusive
    )
}
```

`plusDays(1)` と `minusDays(1)` が美しくない…。

## 動作確認

```Kotlin
import java.time.LocalDate

fun String.toLocalDate(): LocalDate =
    LocalDate.parse(this)

fun main() {
    // 隣接する範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
            "2000-01-01".toLocalDate().."2000-01-03".toLocalDate(),
        ).joinOrNull() ==
                "1999-12-29".toLocalDate().."2000-01-03".toLocalDate()
    )
    // 離れた範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
            "2000-01-02".toLocalDate().."2000-01-03".toLocalDate(),
        ).joinOrNull() == null
    )
    // 一部重なった範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
            "1999-12-31".toLocalDate().."2000-01-03".toLocalDate(),
        ).joinOrNull() ==
                "1999-12-29".toLocalDate().."2000-01-03".toLocalDate()
    )
    // 包含した範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
            "1999-12-28".toLocalDate().."2000-01-03".toLocalDate(),
        ).joinOrNull() ==
                "1999-12-28".toLocalDate().."2000-01-03".toLocalDate()
    )
    // 一致した範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
        ).joinOrNull() ==
                "1999-12-29".toLocalDate().."1999-12-31".toLocalDate()
    )
    // 単一の範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-31".toLocalDate(),
        ).joinOrNull() ==
                "1999-12-29".toLocalDate().."1999-12-31".toLocalDate()
    )
    // 空の範囲
    check(
        listOf<ClosedRange<LocalDate>>().joinOrNull() == null
    )
    // 2つより多い範囲
    check(
        listOf(
            "1999-12-29".toLocalDate().."1999-12-30".toLocalDate(),
            "1999-12-31".toLocalDate().."2000-01-01".toLocalDate(),
            "2000-01-02".toLocalDate().."2000-01-03".toLocalDate(),
        ).joinOrNull() ==
                "1999-12-29".toLocalDate().."2000-01-03".toLocalDate()
    )

    println("OK") // > OK
}
```

／以上