argius note

プログラミング関連

Java8のStreamとDateTime APIで日付のストリームを作る

今回は小ネタです。前回の続編と言えなくもないかもです。
Java8のStreamとDateTime APIを使って、Stream的に日付の順列を処理する方法です。



インターフェイス

fromDateからtoDateまでの日付の順列をStreamで返すようにします。
以降、Stream<LocalDate>のことを、ここでは「日付ストリーム」と呼びます。

// import java.time.*;
// import java.util.*;
// import java.util.function.*;
// import java.util.stream.*;

static Stream<LocalDate> dateStream(LocalDate fromDate, LocalDate toDate) {
    // TODO not impl yet
}

あとは、この日付ストリームを使って、forEachするなりflatMapするなりします。


実装1: 有限数列から日付ストリームを得る

有限の数列を作ってから、map操作によりそれぞれの数を日付に変換します。

  1. 日数を割り出す
  2. 日数分の数列を生成する (0, 1, 2, .... 30)
  3. 数列をfromDateに+n日したLocalDateに変換
// 日数
int days = fromDate.until(toDate).getDays() + 1;
// fromDateに加算する値の数列
IntStream numbers = IntStream.range(0, days);
// 数列→fromDateに日数を加算した日付ストリーム に変換
Stream<LocalDate> dateStream = numbers.mapToObj(x -> fromDate.plusDays(x));

return dateStream;

最後のコード部分3行は、通常は1行で書くところですが、意味をはっきりさせるために分割しています。


実装2: 無限の日付ストリーム+limit

fromDateから+1日を繰り返す無限の日付ストリームを生成し、日数で制限(limit)します。

  1. 日数を割り出す
  2. fromDateから+1日を繰り返す無限の日付ストリームを生成
  3. limitで日数と同じだけ取り出す(日数で打ち切る)


追記(2017-02-20): コメントで誤りをご指摘いただきました。これは実に酷いミスですね...

// 日数
long days = ChronoUnit.DAYS.between(fromDate, toDate);
// int days = fromDate.until(toDate).getDays() + 1;
// => 訂正 2017-02-20
//    誤り! fromDate.until(toDate) は java.time.Periodを返し、
//    getDays() は Period の日数しか返さない

// +1日を繰り返す無限日付シーケンスを生成
Stream<LocalDate> generator = Stream.iterate(fromDate, x -> x.plusDays(1));
// 日数で打ち切る
Stream<LocalDate> dateStream = generator.limit(days);

return dateStream;



(架空の)実装3: 無限の日付ストリーム+takeWhile

HaskellScalaなどのtakeWhileのように、Predicate<T>を取るメソッドがあれば、日数を取得する必要も無かったんですが...残念。

追記(2017-02-20): 訂正ついでに。Java9ではtakeWhileが実装されるようですね。使い方は、未確認ですが、たぶん下記のコードをそのまま使えると思います。

// コンパイルエラー: そんなメソッド(takeWhile)はありません!
return Stream.iterate(fromDate, x -> x.plusDays(1)).takeWhile(x -> !x.isAfter(toDate));
//

それっぽいことを実現するには、Spliterators.spliteratorUnknownSizeなどを使ってできないこともないですが。



おしまい。