Javaで日付と時刻を扱う

抑えておくべき日付や時刻を表すクラス

以下の4つを抑えておきましょう。

  • java.time.LocalDateTime
    • 日付と時刻を表す、タイムゾーンを含まない
  • java.time.LocalDate
    • 日付を表す
  • java.time.LocalTime
    • 時刻を表す
  • java.time.ZonedDateTime
    • 日付と時刻を表す、タイムゾーンを含む

逆に、以下の3つは使用しないようにしましょう。

  • java.util.Date
  • java.util.Calendar
  • java.util.GregorianCalendar

現在のインスタンスを取得する方法

現在の日付や時刻を取得するにはスタティックメソッドのnowを使います。

1
2
3
4
System.out.println(ZonedDateTime.now());
System.out.println(LocalDateTime.now());
System.out.println(LocalDate.now());
System.out.println(LocalTime.now());

実行例

1
2
3
4
2019-09-23T20:06:44.903432+09:00[Asia/Tokyo]
2019-09-23T20:06:44.904434600
2019-09-23
20:06:44.904434600

指定した日時のインスタンスを取得する方法

LocalDateTime

ofメソッドを使います。

1
2
3
4
5
6
7
8
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);
assertThat(ldt.getYear()).isEqualTo(2020);
assertThat(ldt.getMonthValue()).isEqualTo(1);
assertThat(ldt.getDayOfMonth()).isEqualTo(2);
assertThat(ldt.getHour()).isEqualTo(3);
assertThat(ldt.getMinute()).isEqualTo(4);
assertThat(ldt.getSecond()).isEqualTo(5);
assertThat(ldt.getDayOfWeek()).isEqualTo(DayOfWeek.THURSDAY);

上記は、年、月、日、時、分、秒からLocalDateTimeのインスタンスを取得しています。
他にもいくつか種類があります。詳細はドキュメントを見てください。
https://docs.oracle.com/javase/jp/8/docs/api/java/time/LocalDateTime.html

LocalDateTime

ofメソッドを使います。

1
2
3
4
var ld = LocalDate.of(2020, 1, 2);
assertThat(ld.getYear()).isEqualTo(2020);
assertThat(ld.getMonthValue()).isEqualTo(1);
assertThat(ld.getDayOfMonth()).isEqualTo(2);

上記は、年、月、日からLocalDateのインスタンスを取得しています。
他にもいくつか種類があります。詳細はドキュメントを見てください。
https://docs.oracle.com/javase/jp/8/docs/api/java/time/LocalDate.html

LocalDate

ofメソッドを使います。

1
2
3
4
var lt = LocalTime.of(3, 4, 5);
assertThat(lt.getHour()).isEqualTo(3);
assertThat(lt.getMinute()).isEqualTo(4);
assertThat(lt.getSecond()).isEqualTo(5);

上記は、時、分、秒からLocalTimeのインスタンスを取得しています。
他にもいくつか種類があります。詳細はドキュメントを見てください。
https://docs.oracle.com/javase/jp/8/docs/api/java/time/LocalTime.html

ZonedDateTime

ofメソッドを使います。

1
2
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);
var zdt = ZonedDateTime.of(ldt, ZoneId.of("Asia/Tokyo"));

上記は、LocalDateTimeとZoneIdからZonedDateTimeのインスタンスを取得しています。
他にも、LocalDateとLocalTimeとZoneIdから取得するなどいくつか種類があります。詳細はドキュメントを見てください。
https://docs.oracle.com/javase/jp/8/docs/api/java/time/ZonedDateTime.html

このZoneIdとはタイムゾーンのIDのことです。
東京はUTCとの時差はプラス9時間です。

UTCは協定世界時といい、世界の時刻はこの時刻を基準としています。
UTCが6時なら、東京は9時間プラスの15時です。
UTCの説明はこちら。
https://ja.wikipedia.org/wiki/%E5%8D%94%E5%AE%9A%E4%B8%96%E7%95%8C%E6%99%82

世界の時差はこちらのサイトのように世界地図で表示されているとイメージしやすいと思います。
https://www.timeanddate.com/time/current-number-time-zones.html

東京はプラス9時間ですが、中国はプラス8時間です。
東京が6時なら、中国は5時です。この1時間後に中国は6時になります。

どのクラスを使えばいい?

1つの考え方として、ある特定の地域でのみ動作させるプログラムならLocalDateTimeで十分です。
複数の地域(ZoneIdが異なる地域)でも動作させるプログラムならZonedDateTimeを使えばいいと思います。

また、ZonedDateTimeを使う場合でも、DBなどに保存されている情報はレコードごとにタイムゾーンを持っていないと思います。
なのでマッピングにはLocalDateTimeを使用して、ビジネスロジック部分ではZonedDateTimeに変換すればいいと思います。

時間の加算、減算の方法

加算にはplusメソッドやplusMinutesメソッド、plusSecondsメソッドなどを使います。

1
2
3
4
5
6
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);

// 10分加算
assertThat(ldt.plus(Duration.ofMinutes(10)).getMinute()).isEqualTo(14);
assertThat(ldt.plus(10, ChronoUnit.MINUTES).getMinute()).isEqualTo(14);
assertThat(ldt.plusMinutes(10).getMinute()).isEqualTo(14);

この3つのパターンを覚えておきましょう。

  • plus(10分間)
  • plus(10, 分)
  • plusMinutes(10)

時間の切り捨ての方法

truncatedToを使います。

1
2
3
4
5
6
7
8
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);

// 秒未満を切り捨て
assertThat(ldt.truncatedTo(ChronoUnit.SECONDS).getSecond()).isEqualTo(5);

// 分未満を切り捨て
assertThat(ldt.truncatedTo(ChronoUnit.MINUTES).getSecond()).isEqualTo(0);
assertThat(ldt.truncatedTo(ChronoUnit.MINUTES).getMinute()).isEqualTo(4);

文字列への変換方法

formatメソッドを使います。引数に指定するのはDateTimeFormatterです。
DateTimeFormatterにはあらかじめいくつか用意されています。
詳細はドキュメントを見てください。
https://docs.oracle.com/javase/jp/8/docs/api/java/time/format/DateTimeFormatter.html

1
2
3
4
5
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);

assertThat(ldt.format(DateTimeFormatter.ISO_LOCAL_TIME)).isEqualTo("03:04:05");
assertThat(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE)).isEqualTo("2020-01-02");
assertThat(ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)).isEqualTo("2020-01-02T03:04:05");

独自のフォーマットを作るにはDateTimeFormatterofPatternメソッドを使います。
引数に指定するフォーマットは以下のとおりです(一部)。

1
2
3
4
5
6
7
8
9
10
y = year (yy or yyyy)
M = month (MM)
d = day in month (dd)
h = hour (0-12) (hh)
H = hour (0-23) (HH)
m = minute in hour (mm)
s = seconds (ss)
S = milliseconds (SSS)
z = time zone text
Z = time zone, time offset

詳細はドキュメントを見てください。
https://docs.oracle.com/javase/jp/8/docs/api/java/time/format/DateTimeFormatter.html

1
2
3
4
5
6
7
8
9
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);
{
var dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
assertThat(ldt.format(dtf)).isEqualTo("2020/01/02");
}
{
var dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
assertThat(ldt.format(dtf)).isEqualTo("2020/01/02 03:04:05");
}

タイムゾーンの変換方法

ユーザーに表示するときなど、この時刻は東京だと15時だけど、中国では何時?といった変換をしたいことがあります。
この場合はwithZoneSameInstantメソッドを使います。このメソッドは日時情報を変えずにタイムゾーンを変更します。

1
2
3
4
5
6
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);
var zdt = ZonedDateTime.of(ldt, ZoneId.of("Asia/Tokyo"));

assertThat(zdt.withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
.format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
.isEqualTo("2020-01-02T02:04:05+08:00[Asia/Shanghai]");

一方、日時情報を変えてタイムゾーンを変えることもできます。この場合はwithZoneSameLocalメソッドを使います。
(どのようなときに使用するのか思いつきませんでしたが)

1
2
3
4
5
6
var ldt = LocalDateTime.of(2020, 1, 2, 3, 4, 5);
var zdt = ZonedDateTime.of(ldt, ZoneId.of("Asia/Tokyo"));

assertThat(zdt.withZoneSameLocal(ZoneId.of("Asia/Shanghai"))
.format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
.isEqualTo("2020-01-02T03:04:05+08:00[Asia/Shanghai]");