Javaには日付を扱う型にはさまざまあります。どれを使ったらいいのでしょう。
今回は使い分けを自分なりにまとめました。
なおkotlinx.datetimeについては今回扱いません。
こちらはKMPやシリアライズで使われるKotlin製の日付型でありますが、Javaの型をKotlinで使えるようにしたものですので、考え方は基本同じだと思っています。
またAndroidアプリの開発で使うことを前提としています。
java.util.Dateやjava.util.Calendarは基本使わない
Dateは一部のメソッドが非推奨となっています。
The corresponding methods in Date are deprecated.
DateやCalendarはレガシーとされており、APIも使いづらいです。
古いクラスとして、新規開発では基本使わないでおきましょう。
古いシステムではDateやCalendarが使われているので、互換性をもつために使う、というのに限定しましょう。
また、DateFormatにはSimpleDateFormatではなく、java.time.format.DateTimeFormatterやjava.time.format.DateTimeFormatterBuilderを使うとよいでしょう。
java.time.LocalDateTime(と関連API)、java.time.Instantを使う
こちらは新しく作られた使いやすいAPIです。こちらを使いましょう。
LocalDateTimeに関連するものとして、LocalTimeやLocalDateがあります。
これらは簡単で、単純にLocalDateTimeの時間の部分と、日付の部分です。
またZonedDateTimeや、OffsetDateTimeがあります。ZonedDateTimeとOffsetDateTimeに関しては、Instantとの違いを見ながら、説明していきます。
LocalDateTimeとInstantの違い
こちらのStack Overflowの回答がすごくわかりやすいです。
java - What's the difference between Instant and LocalDateTime? - Stack Overflow
回答の画像がわかりやすい。
Instantはタイムラインの一点を表します。タイムラインは絶対時間を表します。宇宙を流れる時間軸の一点を指します。(相対性理論とかは考えないでおきます。)例えば、聖徳太子が誕生した日はタイムラインの一点のはずです。Instantはクラス内部でエポック秒(1721964060742みたいなの)を保持しています。
一方LocalDateTimeは絶対時間を表せません。地球上のどこにいるかによって、別の時間を指すことになります。
年月日時刻を保持するクラスであり、タイムゾーンもありません。
java docにも明記されています。
A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.
LocalDateTime (Java Platform SE 8 )
これはどういうことかというと、カレンダーや時計を見たときに、表示されている年月日と、時刻を表すものになります。
イギリスと日本でカレンダーと時計を見る時、同じ値を見るには、時差があるのでイギリスは絶対時間で9時間経過後に見ないといけません。
逆に絶対時間で同時にイギリスと日本でLocalDateTime.now()をやると時差分だけ離れた別の値が出ます。
使い道として、日の出の時刻や、クリスマス、誕生日などを表すのに良いと思います。
クリスマス0時にイベントを起こしたい時、などに役に立つと思います。
Stack Overflowの回答を読んでみるとより詳しく理解できると思います。
LocalDateTimeとZonedDateTime、OffsetDateTimeの違い
ZonedDateTimeはLocalDateTimeにタイムゾーンを加えたものになります。タイムゾーンが含まれるので、絶対時間を表すことができます。
ZonedDateTimeは日本やイギリスなどのタイムゾーンを加えた概念で、OffsetDateTimeはUTC(標準時)からの時差を加えた概念です。OffsetDateTimeも絶対時間を表すことができます。
まとめると、
| クラス | 内容 |
|---|---|
| Instant | 宇宙を流れる時間軸の一点で、エポック秒を保持する |
| LocalDateTime | 年月日付時刻を単に保有するクラスで、タイムゾーンはなし。宇宙を時間軸の一点を表さない。 |
| ZonedDateTime、OffsetDateTime | LocalDateTimeにタイムゾーンや時差を加えたもので、宇宙を流れる時間軸の一点を表すことができる |
LocalDateTimeとInstantの使い分け
ここでは各クラスをどう使い分けていくかを見ていきます。
⚠ どれを使うかはプロジェクトや状況に応じて判断していくものになると思います。また以下は現時点での自分の考えであり、正直確たる自信もありません。ご指摘やご意見あればお願いします🙇
絶対時間を扱うビジネスロジックにはInstantを使う
例えば、タイマーアプリなど絶対時間をクライアントで扱う場合や、サーバーサイドで時間を処理する場合は、Instantを使うとスッキリすると思います。
wear osのサンプルアプリでもInstantが使われていました。
タイマーだと設定した後タイムゾーンを変更しても鳴動時間は変わらないはずなので、絶対時間を使うべきだと思います。
また記事を作成した時刻、を記録する際も、これは絶対時間で記録しておくべきです。OffsetDateTimeやZonedDateTimeも可能ですが、タイムゾーンの概念は必要ないのでInstantがスッキリするでしょう。
タイムゾーンのない年月日日付時刻を扱う場合はLocalDateTimeを使う
アラームを設定した後タイムゾーンを跨いだ場合、いつアラームを鳴らしたいか考えると良いと思います。
日本で7時にアラームを設定した後、イギリスのタイムゾーンに変更した場合、おそらく7時にアラームがなってほしいと思います。
ここで重要なのは絶対時間ではなく時刻なので、LocalDateTimeが適していると思います。
絶対時刻をDBに保存する場合
この場合はInstantやutcのZonedDateTime、OffsetDateTimeで保存すると良いと思います。(実際には文字列で保存するかもしれませんが、型を変換する前の持ち方という意味)
なぜutcかというと、後でユーザーがタイムゾーンを変更したときにややこしくなるからです。
DBに保存されるデータは、保存した地域に依存しないほうが良いと思います。
サーバーのレスポンスに入っている絶対時刻を処理する場合
クライアントアプリだとよくあるシチュエーションかと思います。記事を取得した時、その作成時刻が入っているような場合ですね。
サーバーはUTC時刻を保存しておくべきだと思うので、その前提で話します。
レスポンスを受けてシリアライズする場合、InstantかutcのZonedDateTime(またはOffsetDateTime)に変換すると良いと思います。しかし私としてはZonedDateTimeが良いと思います。理由としては、基本的にサーバーからのレスポンスにはISO8601形式の時刻(2014-10-10T04:50:40Zみたいなもの)が入っているからです。Instantを使うなら、サーバーはエポック秒を返すべきではないでしょうか?
サーバーが日付時刻を返しているのは人間にわかりやすいからだと思います。ログを出力する場合、エポック秒だとよくわかりません。ZonedDateTimeを使うことで、ログも出力しやすくなります。
現地のタイムゾーンでなくutcのZonedDateTimeに変換するのは、DBに保存することも念頭に置いているためです。
最後に表示する段階で、ZoneId.systemDefault()を指定することで、世界各地で使われるアプリでも時刻を出し分けます。
ただ、サンプルを見ると、now in androidやDroidKaigi203ではInstantにシリアライズしているようです。InstantなのかZonedDateTimeなのかは議論が分かれるところになりそうです。
まぁ、シリアライズせずに文字列で取り回しておいて、表示するときにZonedDateTimeを経由して現地の時刻を表示するのも良いと思います。
総じて、タイムゾーンをユーザーが変更した時、を考慮するとどれを使うかを考えるヒントになると思います。
また、変換の過程では2つが混在することもあると思います。
上記方針で使っていって、なにか気づきがあればお伝えしたいと思います。
補足
また今回詳しくは触れませんが、java.timeのパッケージにはClock、Duration、Period、ChronoUnitなどの便利なクラスがあります。
Clockは現在時刻を提供し、テスト用に固定した現在時刻を提供します。
Durationは時間、Periodは日付の期間を処理できます。
時刻の日とか分とかの部分のみを抽出したりもできます。
ChronoUnitでも期間を計算できます。
くわしくはこちらの記事様が参考になります。
