Javaで年号を追加する

POIを使って年号(元号、和暦)付き日付をExcelに出そうとすると少し面倒なことになった。
年号関連のセル書式判定がイマイチな処理になる上、Excelのバージョンが変わると仕様が変わるかもしれない。

そんなわけでJava側で処理した方がいいという考えになったが、年号をカスタマイズしたい時にどうするのか調べたのでメモ。

年号が変わるなんて先の話だと思っていたが、現実になりそうだよなぁ。
日本中のSEが狂喜乱舞するんだろうなぁ。  

・・・の前に、Java8で日付/時刻API(Date and Time API)が追加されているので、そちらでの年号の扱いも確認。
参考:日本人のためのDate and Time API Tips - Programming Studio

Java8 Date and Time API概要

Date and Time APIでは、日付のみ、時刻のみ、日付/時刻の3種クラスが用意されていて、用途によって使い分ける。

  • 日付のみ:通常はLocalDateクラスを使う。ChronoLocalDateを実装したクラスが当てはまる。
  • 時刻のみ:LocalTimeクラスを使う。元インターフェイスは特に無し?
  • 日付/時刻:通常はLocalDateTimeクラスを使う。ChronoLocalDateTimeを実装したクラスが当てはまる。

now / of

現在日時を取得する際はnow()、指定日時を取得する際はof()を使う。

LocalDate date = LocalDate.now();  // 今日
LocalTime time = LocalTime.now();  // 現在時刻
LocalDateTime dateTime = LocalDateTime.now();  // 現在日時

LocalDate date = LocalDate.of(2016, 1, 2);  // 2016年1月2日
LocalTime time = LocalTime.of(12, 34, 56);  // 12時34分56秒
LocalDateTime dateTime1 = LocalDateTime.of(2016, 1, 2, 12, 34, 56);  // 2016年1月2日 12時34分56秒
LocalDateTime dateTime2 = LocalDateTime.of(date, time);  // 2016年1月2日 12時34分56秒

at / to

クラス間の変換メソッドがat・・・、to・・・で用意されている。
atは情報を足すイメージ、toはそのまま変換するイメージ。

LocalDate date = LocalDate.of(2016, 1, 2);  // 2016年1月2日
LocalTime time = LocalTime.of(12, 34, 56);  // 12時34分56秒

// 全て2016年1月2日 12時34分56秒
LocalDateTime dateTime1 = date.atTime(time);
LocalDateTime dateTime2 = date.atTime(12, 34, 56);
LocalDateTime dateTime3 = time.atDate(date);
LocalDateTime dateTime = LocalDateTime.of(2016, 1, 2, 12, 34, 56);  // 2016年1月2日 12時34分56秒
LocalDate date = dateTime.toLocalDate();  // 2016年1月2日
LocalTime time = dateTime.toLocalTime;  // 12時34分56秒

format / parse

DateTimeFormatterを使う。DateTimeと付いているが日付/時刻専用ではなく、LocalDate, LocalTimeも同じ。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

LocalDate date1 = LocalDate.of(2016, 1, 2); 
System.out.println(date.format(formatter));  // 2016/01/02

LocalDate date2 = LocalDate.parse("2016/01/02", formatter);  // 2016-01-02のLocalDate


あと演算系のAPIも用意されているが、今回は本筋と逸れるので省略。

年号を扱う

年号を扱うにはChronoLocalDate実装(日付のみ系)のクラスJapaneseDateを使う。
now(), of()が用意されていて、かつ年号ベースでのof()もある。

JapaneseDate date1 = JapaneseDate.now();
JapaneseDate date2 = JapaneseDate.of(2016, 1, 2);
JapaneseDate date3 = JapaneseDate.of(JapaneseEra.HEISEI, 28, 1, 2);  // 平成28年1月2日 = 2016/01/02

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gyy年MM月dd日");
System.out.println(date2.format(formatter));  // 平成28年01月02日

JapaneseDateは出力用

JapaneseEraは明治以降の年号を扱うEnum型。おまけに各valueが直感的でない。
明治[-1]、大正[0]、昭和[1]、平成[2]になっている。
1970年が起点なので昭和が1ということらしい。 なのでプログラム上で使うならJapaneseEraを使ったof()は使わ(え)ない。

更にJapaneseDateにはparse()がない。ナンテコッタイ。
おそらく"平成28年1月2日"という文字列をparseするのが面倒なのだろう、気持ちは分かる。
ただそのおかげで、文字列"2016/01/02"からJapaneseDateを直接作る術も無い。
一度LocalDate(or LocalDateTime)にparse()して、from()で読み込むのが正解か?

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date1 = LocalDate.parse("2016/01/02", formatter);
JapaneseDate date2 = JapaneseDate.from(date1);

あと、JapaneseDateクラスはあるが、JapaneseDateTimeというのは無い。

そんなわけで、日付はLocalDateやLocalDateTimeで処理するのが基本で、表示などのformatにだけJapaneseDateを使うことになるだろう。

年号を追加する (!! Java8ではできません !!)

参考:Javaで新元号に対応する - Qiita
jre/lib/calendar.propertiesに年号行を追加する。
仮に「2017/10/01から新年号『現合』開始、短縮文字は『G』」だとしたら、

name=\u73fe\u5408,abbr=G,since=1506783600000

と書く。 \u73fe\u5408は「現合」をnative2asciiした文字、1506783600000は1970/01/01からのミリ秒。

テストする。

JapaneseDate date1 = JapaneseDate.of(2016, 1, 2);  // 2016/01/02 => 平成28年1月2日
JapaneseDate date2 = JapaneseDate.of(2020, 7, 24);  // 2020/07/24 => 現合4年7月24日 (東京オリンピック開会式)

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("Gyy年MM月dd日");
System.out.println(date1.format(formatter));  // 平成28年01月02日
System.out.println(date2.format(formatter));  // 304年07月24日 ?????

304年てなんぞや。。。
これ年号が「3」で「04年」と表示されている様子。たぶん3はJapaneseEraのvalue値だと思われる。

つまり、壮絶に時間を費やしてダメだったという。。。
できると書いてあるサイトがあったがエアプということか。まともなSEならエアプは恥ずかしいからやめましょう。

誤解の無いように言うと、参考:Javaで新元号に対応する - Qiitaに書いてあるcalendar.propertiesに年号追加する方法自体は間違っていない。
calendar.propertiesに追加してDateFormatでDate型をformatすれば、意図した通りに追加した年号が計算されて表示される。
Date and Time APIがcalendar.propertiesに対応できていない、というかこれバグのレベルなんじゃ。。。

結論

  • 年号追加はJavaのバージョンアップまで待つ、という人

    • Date and Time APIを使っておく
  • バージョンアップできない、先行で年号追加したい、という人

    • Date and Time APIを使いつつ、年号表示の処理だけDate(+ DateFormat)使う