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に対応できていない、というかこれバグのレベルなんじゃ。。。