Java Date Time APIでの和暦の扱い、ロケール、タイムゾーン - 「プロになるJava」 ボツ原稿

今回のボツ原稿は和暦を扱うJapaneseDateクラスと各地の時差を反映した時刻を扱うZonedDateTimeについてです。
P.89に5.1.8として続く想定です。

和暦の扱い

日付を扱えるようになると、元号を含んだ日付も扱いたいですよね。 java.time.chrono.JapaneseDateクラスで和暦を扱うことができます。
ではJapaneseDateクラスを使って、元号の付いた日付を表示してみましょう。

JapaneseDate.now()を実行してみます。このとき、新たなimportが必要になりますが、自分で入力せずにJShellの補完機能を使ってみます。
JapaneseDateと入力した状態で[Shift]+[Tab]を押したあと、[i]キーを押します。

jshell> JapaneseDate
0: 何もしない
1: import: java.time.chrono.JapaneseDate
選択:

選択肢が表示されるので、ここで[1]キーを押してみます。 「imported」と表示されて次のようになったはずです。

Imported: java.time.chrono.JapaneseDate
jshell> JapaneseDate

これはimport java.time.chrono.JapaneseDateを実行したのと同じ効果があります。ここで先ほどと違い*ではなくJapaneseDateクラスを指定しているので、java.time.chronoパッケージの中のJapaneseDateクラスだけパッケージ名を省略できるようになります。

続きの.now()を入力して実行してみましょう。

jshell> JapaneseDate.now()
$20 ==> Japanese Reiwa 3-06-18

「Reiwa」と表示されて和暦での年が表示されました。

サブパッケージ

ところで、JapaneseDateクラスが所属しているjava.time.chronoパッケージは、java.timeパッケージに対して「サブパッケージ」という関係になっています。
すでにjava.time.*に対してimportを行っているのでchrono.JapaneseDateと入力できそうですが、残念ながらそれはできません。サブパッケージは、プログラムから見たときの特別扱いは何もないので、利用するときは名前の前半がかぶってる全く別のパッケージだと考えましょう。

ロケール

ところで、曜日を表示するとどうなるでしょうか?%tAを使って曜日を表示してみます。

jshell> "%tA".formatted(today)
$6 ==> "日曜日"

日本語で「日曜日」と表示されました。もしかしたら「Sunday」と表示された人もいるかもしれません。これは、地域や言語によって表記を変える仕組みによるもので、このような仕組みを ロケール と言います。

言語の設定を変えて実行してみましょう。formattedメソッドでは言語の設定を変更できないので、同じ動きをするString.formatメソッドを使ってみます。

例えば"%tA".formatted(today)String.formatメソッドを使って書き換えると次のようになります。

jshell> String.format("%tA", today)
$10 ==> "日曜日"

formatメソッドの最初の引数にロケールを渡すことができます。英語を設定するときにはLocale.ENGLISHを指定します。

jshell> String.format(Locale.ENGLISH, "%tA", today)
$9 ==> "Sunday"

「Sunday」と表示されました。
日本語を指定する場合にはLocale.JAPANESEを指定します。

jshell> String.format(Locale.JAPANESE, "%tA", today)
$8 ==> "日曜日"

このように、ロケールとして言語を設定すると、それに従って表示がかわります。


練習 1. Locale.CHINESEを指定して曜日を表示してみましょう。


タイムゾーン付日付時刻

Java 17のリリースは14時30分だった」という話を聞いたとき、ちょっとおかしいと思いませんでしたか?Javaは世界中で使われています。どの地域でいう14時30分だったかは気になるところです。
日付時刻を使ってある瞬間を表すには、それがどの地域での日付時刻かを表す タイムゾーン の指定も必要になります。タイムゾーン付きの日付時刻としてZonedDateTimeクラスが用意されています。
ZonedDateTime.now()として現在時刻を得てみましょう。

jshell> ZonedDateTime.now()
$136 ==> 2021-09-19T06:05:21.549662100+09:00[Asia/Tokyo]

LocalDateTimeの場合と比べてみましょう。

jshell> LocalDateTime.now()
$137 ==> 2021-09-19T06:06:38.967300300

ZonedDateTimeの値には現在時刻のあとに時差を表す「+09:00」とタイムゾーンを表す「Asia/Tokyo」がついていることがわかります。
UTC(協定世界時) は世界の時間の基準になる時間です。世界の時間はUTCからの時差をつかって表現されます。日本ではUTCから9時間の差があるので「+09:00」が表示されていました。
日本人が多く訪問している国・地域のタイムゾーンには次のようなものがあります。

ZoneId 国・地域
Asia/Tokyo 日本
Asia/Seoul 韓国
Asia/Shanghai 中国
Asia/Taipei 台湾
Asia/Bangkok タイ
Asia/Singapore シンガポール
Asia/Ho_Chi_Minh ベトナム
Asia/Manila フィリピン
US/Pacific アメリカ西海岸
US/Eastern アメリ東海岸
Europe/Berlin ドイツ
Etc/UTC 協定世界時

それでは、タイムゾーンを指定して今の時刻を得てみましょう。アメリ東海岸でのいまの時刻を見てみます。アメリ東海岸タイムゾーンは「US/Eastern」で表します。

jshell> ZonedDateTime.now(ZoneId.of("US/Eastern"))
$130 ==> 2021-09-18T13:07:29.331452500-04:00[US/Eastern]

アメリ東海岸なのでニューヨークあたりの現在時刻がわかりました。
タイムゾーンZoneIdクラスの値として指定します。ZoneIdの値を得るにはZonedId.ofメソッドにタイムゾーンを指定して呼び出します。

それではJava 17リリース日時をタイムゾーン付きで表してみましょう。Java 17はUTCでいう14時30分にリリースされました。そこでタイムゾーンUTCを指定してみましょう。 「Etc/UTC」として指定します。

jshell> var java17 = ZonedDateTime.of(java17dateTime, ZoneId.of("Etc/UTC"))
java17 ==> 2021-09-14T14:30Z[Etc/UTC]

これを日本時間で表してみましょう。 withZoneSameInstantメソッドで、同じ瞬間の別のタイムゾーンでの時刻を表すことができます。

jshell> java17.withZoneSameInstant(ZoneId.of("Asia/Tokyo"))
$94 ==> 2021-09-14T23:30+09:00[Asia/Tokyo]

つまり、Java 17は日本時間では夜の23時半にリリースされたわけですね。
コンピュータのタイムゾーンの設定を日本時間にしている人も多いと思います。 ZoneId.systemDefaultメソッドで動かしてるコンピュータのタイムゾーンを得ることができます。

jshell> java17.withZoneSameInstant(ZoneId.systemDefault())
$92 ==> 2021-09-14T23:30+09:00[Asia/Tokyo]

タイムゾーン付きの日付時刻は、夏の間に1時間早くなる夏時間などを考えると、日付と時刻には密接な関係があるためZonedDateTimeだけが用意されていて、日付だけをあらわすZonedDateや時刻だけを表すZonedTimeはありません。
プログラムの勉強をしていると、日付時刻の表し方についても詳しくなっていきますね。


練習 1. いまの時刻がアメリカ西海岸では何時になっているか確認してみましょう。
2. 日本時間での2021年7月23日20時00分がシンガポールで何時だったか確認してみましょう。