プログラマーの日を Date and Time API を使って調べてみた。

Java

プログラマーの日って知っていますか?

コンピュータのデータを扱う単位である8ビットに256通りの表現があることから、

1月1日から256日目にあたる9月13日(閏年の場合は9月12日)と定められたロシアの公式な祝日となっています。

ロシアのプログラマーと言えば topcoder で活躍している優秀な人が多くいることを何処かで見た覚えがあります。

おそらくロシアではプログラマーは尊敬され、羨望の職業なのかもしれません。

日本ではこんな洒落た祝日は無いのが残念です。

では、プログラマーの日が日曜日だったら日本のプログラマーもゆっくり過ごせるはずなのでその日がいつなのか調べてみることにします。

Java SE8 から新しく追加された Date and Time API を使うことができます。

新しい物好きの私はこれを使ってプログラマーの日が日曜である一番近い日を表示させるプログラムを組んでみました。

実はこのプログラムは Java Magazine の fix this に出題されたものを参考に(まるっといただいて)してます。

なので最新の Java Magazine の fix this を楽しみにしている人はこれ以上読み進めないでください。

Java Magazine の fix this と同じように TemporalAdjuster インタフェースを利用するプログラムを組んでみました。

「見せて貰おうか。連邦軍のモビルスーツの性能とやらを!」

あっ、違った(^_^;)

「見せて貰おうか。Java8 の Date and Time API の性能とやらを!」

このプログラムはまず public static LocalDate now() メソッドを使ってシステムクロックとデフォルトのタイムゾーンを使用した現在の日付オブジェクトの生成します。

そして public String format(DateTimeFormatter formatter) メソッドで指定されたフォーマッタを使用してこの日付を書式設定します。

public static DateTimeFormatter ofPattern(String pattern) メソッドでフォーマットパターンを設定します。

これでフォーマッタで指定されたパターンで現在の日付が表示されます。

ここからはプログラマーの日が日曜である一番近い日を求める処理をみていきましょう。

TemporalAdjuster インタフェースってどんな働きをするのでしょうか?

API ドキュメントには

時間的オブジェクトを調整するための方針です。

アジャスタは、時間的オブジェクトを変更するための主要なツールです。それらは、戦略デザイン・パターンのように、調整のプロセスを外部化して異なるアプローチを可能にするために存在します。例として、週末を避けて日付を設定するアジャスタや、日付を月の最後の日に設定するアジャスタなどがあります。

TemporalAdjusterには、2つの同等な使用方法があります。1つ目は、このインタフェース上でメソッドを直接呼び出す方法です。2つ目は、Temporal.with(TemporalAdjuster)を使用する方法です。
   // these two lines are equivalent, but the second approach is recommended
   temporal = thisAdjuster.adjustInto(temporal);
   temporal = temporal.with(thisAdjuster);
 
コード内での読みやすさが大幅に向上するため、2つ目のアプローチ(with(TemporalAdjuster))を使用することをお薦めします。
TemporalAdjustersクラスには、staticメソッドとして使用できるアジャスタの標準セットが含まれています。これらの機能を次に示します。
•月の最初または最後の日を見つける
•翌月の最初の日を見つける
•その年の最初または最後の日を見つける
•翌年の最初の日を見つける
•月内の最初または最後の曜日(「6月の最初の水曜日」など)を見つける
•次または前の曜日(「次の木曜日」など)を見つける
実装要件:このインタフェースは実装が可変であることを制限しませんが、不変にすることを強くお薦めします。

と記載されています。

なんだかいろいろ出来そうなことが書いてあります。

それではまず、現在の日時がプログラマーの日を過ぎていたらラムダ式で渡された LocalDate オブジェクトの temporal を1年進めます。

public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) メソッドを使って元日から現在日時が 256日以上か判定しています。

もし、256日以上過ぎていたら Temporal plus(long amountToAdd, TemporalUnit unit) メソッドで temporal を1年進めます。

このメソッドは、このオブジェクトと同じ型のオブジェクトに指定された期間を追加したものを返します。

TemporalAdjuster programmersDayAduster = temporal -> {

    if (DAYS.between(LocalDate.of(today.getYear(), 1, 1), temporal) >= 256) {
        temporal = temporal.plus(1, ChronoUnit.YEARS);
    }

次に temporal の日時を Temporal with(TemporalField field, long newValue) メソッドを使って指定されたフィールドを変更して、このオブジェクトと同じ型のオブジェクトを返します。

            temporal = temporal.with(ChronoField.DAY_OF_YEAR, 256);

つまり、LocalDate オブジェクト temporal を元日から 256 日目のプログラマーの日を生成して置き換えたということです。

次は temporal オブジェクトの曜日を取得してそれが指定された曜日(日曜日)であるまで年を進めて処理を繰り返します。

ただし、うるう年の場合もあるのでそれも処理を分けます。

指定された曜日であれば temporal オブジェクトを返します。

while (temporal.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue()) {
    if (Year.isLeap(temporal.get(ChronoField.YEAR))) {
        temporal = temporal.plus(366, ChronoUnit.DAYS);
    } else {
        temporal = temporal.plus(365, ChronoUnit.DAYS);
    }
}

while ループの判定(temporal オブジェクトの曜日を取得してそれが指定された曜日(日曜日)) かどうかは

default int get(TemporalField field) メソッドで指定したフィールドの値を int として取得します。

そして、public int getValue() メソッドで曜日の int 値を取得し、比較判定をします。

temporal オブジェクトを次の年のプログラマーの日に進めるのにうるう年かどうかの判定を行います。

if (Year.isLeap(temporal.get(ChronoField.YEAR))) {
    temporal = temporal.plus(366, ChronoUnit.DAYS);
} else {
    temporal = temporal.plus(365, ChronoUnit.DAYS);
}

public static boolean isLeap(long year) メソッドで判定します。

このメソッドは、ISO先発グレゴリオ暦暦体系のルールに従って、年がうるう年であるかどうかをチェックします。

うるう年だった場合は 1年と 1日 (366 日) 進めます。

うるう年でなければ 1 年 (365 日) 進めます。

最後に計算されたプログラマーの日を LocalDate オブジェクトとして取得して表示します。

これには二つの等価な方法があります。

一つ目はコメントアウトされている方法として

処理が全て終わって指定の曜日のプログラマーの日が計算できたなら Temporal adjustInto(Temporal temporal) メソッドに

調整する時間的オブジェクト today を渡して、オブジェクトの調整済のコピーを返します。

LocalDate programmersDay = (LocalDate) programmersDayAduster.adjustInto(today);

あとは最初と同じように programmersDay をフォーマッタで指定されたパターンで表示するだけです。

二つ目の方法は、public LocalDate with(TemporalAdjuster adjuster) を使用する方法です。

このメソッドは日付の調整済のコピーを返します。

これは、日付を調整して、この日付に基づくLocalDateを返します。調整は、指定されたアジャスタ戦略オブジェクト ( TemporalAdjuster programmersDayAduster )を使用して行われます。

ちなみに主要な日付/時間クラス(MonthやMonthDayなど)は、TemporalAdjusterインタフェースを実装しています。

そしてアジャスタは、さまざまな長さの月やうるう年などの特別なケースの処理を担当しています。

LocalDate programmersDay = today.with(programmersDayAduster);

あとは最初と同じように programmersDay をフォーマッタで指定されたパターンで表示するだけです。

TemporalAdjuster インタフェースの API ドキュメントでは、コード内での読みやすさが大幅に向上するため、こちらをお勧めしていました。

以上で簡単にプログラムの解説を記述してみました。

なお、間違い等あるかもしれませんのであしからず(^_^;)

プログラムの実行結果は次のようになります。

今日は、2015年 4月13日 月曜日
(○・ω・)ノ————-end————-
プログラマーの日が日曜日である一番近い日は、2015年 9月13日 日曜日
(○・ω・)ノ————-end————-

使い勝手の良くない Calendar クラスと違って実に良くできています。

また、Date and Time API は ISO 8601 に準拠していて、イミュータブルでスレッドセーフとなっています。

Date and Time API はイミュータブルであるが故にオブジェクトを生成、置き換えなどのメソッドが用意されています。

つまり、セッターメソッドは無いということです。

ザクとは違うだよザクとは・・・」

違った(^_^;)

「Calendar とは違うだよ。Calendar とは・・・」

Date and Time API 使ったら Calendar クラスなんて使えなくなるよ。

さて、先ほどのプログラムですが本当にちゃんと動いて正しい値が出力されているのか確かめてみます。

プログラマーの日を10年間表示させてみます。

Stream API を利用してプログラムを組んでみました。

先ほどのプログラムに下記コードを追加します。

このプログラムの実行結果は次のようになります。

今日は、2015年 4月13日 月曜日
(○・ω・)ノ————-end————-
プログラマーの日が日曜日である一番近い日は、2015年 9月13日 日曜日
(○・ω・)ノ————-end————-
プログラマーの日を10年間表示させる。
2015年 9月13日 日曜日
2016年 9月12日 月曜日
2017年 9月13日 水曜日
2018年 9月13日 木曜日
2019年 9月13日 金曜日
2020年 9月12日 土曜日
2021年 9月13日 月曜日
2022年 9月13日 火曜日
2023年 9月13日 水曜日
2024年 9月12日 木曜日
(○・ω・)ノ————-end————-

追加したプログラムが正しく動いていれば TemporalAdjuster インタフェースを利用した最初のプログラムは正しく動作していると思われます。

ちょっと待てよ!

これ Stream API でシンプルに組めるんじゃないか?

プログラマーの日の設定を土曜日にして組んで追加してみました。

このプログラムの実行結果は次のようになります。

今日は、2015年 4月13日 月曜日
(○・ω・)ノ————-end————-
プログラマーの日が日曜日である一番近い日は、2015年 9月13日 日曜日
(○・ω・)ノ————-end————-
プログラマーの日を10年間表示させる。
2015年 9月13日 日曜日
2016年 9月12日 月曜日
2017年 9月13日 水曜日
2018年 9月13日 木曜日
2019年 9月13日 金曜日
2020年 9月12日 土曜日
2021年 9月13日 月曜日
2022年 9月13日 火曜日
2023年 9月13日 水曜日
2024年 9月12日 木曜日
(○・ω・)ノ————-end————-
プログラマーの日が土曜日である一番近い日は、2020年 9月12日 土曜日
(○・ω・)ノ————-end————-

Stream API のほうがシンプルで解りやすく綺麗に思える。(>_<。)

「認めたくないものだな。自分自身の、若さゆえの過ちというものを。」

違う!(^_^;)

「認めたくないものだな。自分自身の、未熟さゆえの過ちというものを。」

今回、Date and Time API を使って遊んだプログラムは下記 GitHub にあります。

https://github.com/Yucchi-1995/Programmers-Day

Java 楽しいね(^_^)

追記
Stream API を使って求めるプログラムを修正
プログラマーの日を過ぎていたときは次の年から計算するようにしました。

Hatena タグ: