Date and Time APIについて調べてみた その3

【前提条件】

[環境]
[参考資料]

Java8 APIドキュメント
http://docs.oracle.com/javase/jp/8/api/

【概要】

今回は日時の変更方法について調べてみました。

【今までの日時変更】

過去のバージョンではCalendarクラスで
addメソッドかsetメソッドを使って値を変更していました。

日付をマイナスする場合はcal.add(Calendar.YEAR, -1)など
マイナス値でaddするというやり方でした。

【プラス/マイナス】

Date and Time APIの場合はプラスとマイナスごとに
メソッドが用意されています。

[サンプルコード]
    public static void plusMinus() {

        final LocalDateTime now = LocalDateTime.now();

        final LocalDateTime yearPlus = now.plusYears(1);
        final LocalDateTime monthPlus = now.plus(2, ChronoUnit.MONTHS);
        final LocalDateTime dayPlus = now.plus(Duration.ofDays(3));

        final LocalDateTime hourMinus = now.minusHours(4);
        final LocalDateTime minuteMinus = now.minus(5, ChronoUnit.MINUTES);
        final LocalDateTime secondeMinu = now.minus(Duration.ofSeconds(6));

        System.out.println("now         : " + now);
        System.out.println("yearPlus    : " + yearPlus);
        System.out.println("monthPlus   : " + monthPlus);
        System.out.println("dayPlus     : " + dayPlus);

        System.out.println("hourMinus   : " + hourMinus);
        System.out.println("minuteMinus : " + minuteMinus);
        System.out.println("secondeMinu : " + secondeMinu);
    }

まずはplusYearsやplusMonthsなどのplusXXXメソッドと
minusYearsやplusMonthsなどのminusXXXメソッドです。
long型のパラメータを受け取り、設定された数値分値が変更されます。

次にplusメソッドとminusメソッドです。
それぞれメソッドシグネチャは2パターンです。

1つ目のパターンはlong型とTemporalUnitインターフェイスのオブジェクトを
パラメータとして受け取るパターンです。

TemporalUnitクラスは日付、時刻を示すためのインターフェイスです。
実装すれば独自の時刻の単位を作れますが、
通常使う単位はChronoUnitクラスで実装されています。

2つ目のパターンは
TemporalAmountインターフェイスのオブジェクトを
パラメータとして受け取るパターンです。

サンプルではDurationクラスのメソッドを使って
TemporalAmountインターフェイスのオブジェクトを生成しています。

[実行結果]
now         : 2014-06-14T09:54:20.779
yearPlus    : 2015-06-14T09:54:20.779
monthPlus   : 2014-08-14T09:54:20.779
dayPlus     : 2014-06-17T09:54:20.779
hourMinus   : 2014-06-14T05:54:20.779
minuteMinus : 2014-06-14T09:49:20.779
secondeMinu : 2014-06-14T09:54:14.779

それぞれ指定した項目の値が変更されていることがわかります。

Date and Time APIの時刻クラスはイミュータブルなので
plus/minusメソッドを実行しても
元のオブジェクトは変更されません。

[サンプルコード2]

plus/minusメソッドで数値型のパラメータに
マイナスの値を設定しても正常に動作します。

    public static void plusMinus02() {

        final LocalDateTime now = LocalDateTime.now();

        final LocalDateTime yearPlus = now.plusYears(-2);
        final LocalDateTime hourMinus = now.minusYears(-2);

        System.out.println("yearPlus    : " + yearPlus.getYear());
        System.out.println("hourMinus   : " + hourMinus.getYear());
    }
[実行結果2]
yearPlus    : 2012
hourMinus   : 2016

指定されたパラメータの値どおりにマイナス値が有効になっています。

動きとしては問題ないと思いますが、
plusは増やす時のみ、minusは減らす時のみに使うほうが
コードの意味は汲み取りやすいですね。

【with】

withはCalendar#setメソッドに相当するメソッドで、
日付の項目を指定した値に変更します。

[サンプルコード]
    public static void edit() {

        final LocalDateTime now = LocalDateTime.now();

        final LocalDateTime yearWith = now.withYear(2020);
        final LocalDateTime monthWith = now.with(ChronoField.MONTH_OF_YEAR, 11);
        final LocalDateTime dayWith = now.with(v -> v.plus(1, ChronoUnit.DAYS));

        System.out.println("yearWith    : " + yearWith);
        System.out.println("monthWith   : " + monthWith);
        System.out.println("dayWith     : " + dayWith);
    }

withYearなどのwithXXXメソッドはlong型のパラメータを受け取ります。
受け取った値に変更されます。

withメソッドはメソッドシグネチャを2つ持ちます。

1つめのパターンは
TemporalFieldインターフェイスのオブジェクトとlong型の値を
パラメータとして受け取るパターンです
TemporalFieldインターフェイスは日付項目をあらわすものです。
通常使う日付項目はChronoFieldクラスで定義されています。

2つめのパターンは関数型オブジェクトを
パラメータとして受け取るパターンです。
関数型オブジェクトの
パラメータと戻り値はTemporalオブジェクトです。

[実行結果]
yearWith    : 2020-06-14T10:19:44.256
monthWith   : 2014-11-14T10:19:44.256
dayWith     : 2014-06-15T10:19:44.256

各項目の値が変更されているのがわかります。

【まとめ】

Clandarクラスを使う時と比べて大きく変わったのは
イミュータブルになったことだと思います。

日付オブジェクトがミュータブルとして
扱わなければならないことは少ないと思うので
提供されるAPIとしては安全に扱うことが出来ます。