mike-neckのブログ

Java or Groovy or Swift or Golang

Javaでの和暦の扱いについて

Date and Time APIでの和暦の扱いをちゃんと調べていなかったので、調べた。

なお、以下で用いるコードにおいては次のフォーマッタおよび、タイムゾーンを使った。

final ZoneId tokyo = ZoneId.of("Asia/Tokyo");

final DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendText(ChronoField.ERA)
        .appendText(ChronoField.YEAR_OF_ERA)
        .appendLiteral("年")
        .appendText(ChronoField.MONTH_OF_YEAR)
        .appendText(ChronoField.DAY_OF_MONTH)
        .appendLiteral("日")
        .toFormatter(Locale.JAPAN);

final DateTimeFormatter iso = DateTimeFormatter.ISO_DATE;

1.和暦を扱う場合は以下のクラスを使う。

  • java.time.chrono.JapaneseDate
  • java.time.chrono.JapaneseEra

2.日付インスタンスの作り方

  • 日付を指定する場合は JapaneseDate#of(JapaneseEra, int, int, int) あるいは JapaneseDate#of(int, int, int) を使う。
  • 現在時刻が欲しい場合は JapaneseDate#now または JapaneseDate#now(ZoneId) または JapaneseDate#now(Clock) を使う。
  • 他の日付型から変換したい場合は JapaneseDate#from(TemporalAccessor) を使う。

というわけで、試してみましょう。まず、東京の今の日付が欲しい場合は次のようになります。

final JapaneseDate now = JapaneseDate.now(tokyo);
System.out.println(now.format(formatter));

結果

平成29年3月23日

昭和最後の日付近辺

final JapaneseDate showa = JapaneseDate.of(1989, 1, 7);
System.out.println(showa.format(formatter));
final JapaneseDate heisei = JapaneseDate.of(1989, 1, 8);
System.out.println(heisei.format(formatter));

結果

昭和64年1月7日
平成1年1月8日

明治は6年以前というのがないそうです

try {
  final JapaneseDate meiji = JapaneseDate.of(1872, 12, 31);
  System.out.println(meiji.format(formatter));
} catch (Exception e) {
  System.out.println(e.getClass().getCanonicalName());
  System.out.println(e.getMessage());
  System.out.println("1872/12/31");
}

結果

java.time.DateTimeException
JapaneseDate before Meiji 6 is not supported
1872/12/31

というわけで、明治6年以前は出力されないので、 厳密 にJIS X 0301に対応している模様です。


同様に和暦を変換していきます。

final JapaneseDate h = JapaneseDate.of(平成, 29, 1, 1);
System.out.println(h.format(iso));
final JapaneseDate m = JapaneseDate.of(明治, 6, 1, 1);
System.out.println(m.format(iso));
try {
  JapaneseDate.of(明治, 5, 12, 31);
} catch (Exception e) {
  System.out.println(e.getClass().getCanonicalName());
  System.out.println(e.getMessage());
  System.out.println("明治5年12月31日");
}

結果

2017-01-01
1873-01-01
java.time.DateTimeException
JapaneseDate before Meiji 6 is not supported
明治5年12月31日

こちらも難しいことは特に必要なさそうです。


最後に別の型からの変換

final LocalDate ldm = LocalDate.of(1873, 1, 1);
System.out.println(JapaneseDate.from(ldm).format(formatter));
try {
  final LocalDate ldk = LocalDate.of(1872, 12, 31);
  System.out.println(JapaneseDate.from(ldk).format(formatter));
} catch (Exception e) {
  System.out.println(e.getClass().getCanonicalName());
  System.out.println(e.getMessage());
  System.out.println("明治5年12月31日");
}

結果

明治6年1月1日
java.time.DateTimeException
JapaneseDate before Meiji 6 is not supported
明治5年12月31日

最後に古の java.util.Date からの変換ですが、かなり面倒です。もう java.util.Date 使うなと言いたくなるくらいです。

final Date date = new Date();
final Instant instant = date.toInstant();
final ZonedDateTime zonedDateTime = instant.atZone(tokyo);
final JapaneseDate japaneseDate = JapaneseDate.from(zonedDateTime);
System.out.println(japaneseDate.format(formatter));

結果

平成29年3月23日

API的には JapaneseDate.from(instant) とやりたくなりますが、ゾーン情報を持たない Instant は変換できません(。逆もまた然り)。まあ、日付の計算も面倒ですし、 java.util.Date 使うのやめましょう。


java.util.Date 使うのやめましょう」と書くと、「これだからJava8の使えるサーバーの人間は…」と言われるので、紹介しておくと、これと同じAPIAndroidでも使えます。threetenbpというプロジェクトがあって、そちらがターゲットバージョン1.6で同じAPI(パッケージは異なる)のものを提供しています。したがってJava8が使えない環境の方はそちらを使うとよいです。

github.com

また、ThreeTenABP(Three Ten Android Back Port)というプロジェクトもあるようですが、ソースを見たところクラスの数が異様に少ないので同じAPIが使えるかどうかわかりません。どうやらメモリおよび速さの関係からバイナリファイルの中にゾーンの情報をすべて入れているようなのですが、JapaneseEraとかどうやってるんだろうという印象を持ったりします。

github.com

(追記:2017/03/24 1:03)教えてもらいました。ThreeTenABPはThreeTenに依存しているので同じAPIを使えるようです。


おわり