Javaでの和暦対応の罠
ふと、Javaで和暦対応してたよなぁと思い出し、検索して見つけた以下のサイトのプログラムを元に、とあることを検証してみた。
それは、「Calendar#setで西暦年をセットして和暦でフォーマットして出力したら、西暦年に対応する元号が分かるじゃん」というもの。
早速書いてみる。
- Test1.java
import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; public class Test1 { public static void main(String[] args) { Locale locale = new Locale("ja", "JP", "JP"); Calendar cal = Calendar.getInstance(locale); System.out.println(cal.getClass().getName()); int[] years = { 1900, 1925, 1950, 1975, 2000 }; for (int year : years) { cal.set(Calendar.YEAR, year); DateFormat format = new SimpleDateFormat("GGGGy年M月d日", locale); System.out.println(year + ": " + format.format(cal.getTime())); } } }
いざ実行。
$ javac Test1.java && java Test java.util.JapaneseImperialCalendar 1900: 平成1900年3月11日 1925: 平成1925年3月11日 1950: 平成1950年3月11日 1975: 平成1975年3月11日 2000: 平成2000年3月11日 $
・・・えっ!?‥「cal.set(Calendar.YEAR, year)
」って「和暦での年数をセットしないといけないの?」‥
どうやらそうらしい。
というわけでプログラムをちょこちょこいじりながら調べてみたら、「Calendar cal = Calendar.getInstance(locale)
」の部分が良くないらしい。
書き直したプログラム。
- Test2.java
import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; public class Test2 { public static void main(String[] args) { Locale locale = new Locale("ja", "JP", "JP"); Calendar cal = Calendar.getInstance(); System.out.println(cal.getClass().getName()); int[] years = { 1900, 1925, 1950, 1975, 2000 }; for (int year : years) { cal.set(Calendar.YEAR, year); DateFormat format = new SimpleDateFormat("GGGGy年M月d日", locale); System.out.println(year + ": " + format.format(cal.getTime())); } } }
実行。
$ javac Test2.java && java Test2 java.util.GregorianCalendar 1900: 明治33年3月11日 1925: 大正14年3月11日 1950: 昭和25年3月11日 1975: 昭和50年3月11日 2000: 平成12年3月11日 $
おぉ、今度は期待通り。
ということはだよ、例えば「明治33年」の「33」という年数を素直にセットしたい場合はできないってことじゃん‥
ということで、最初のプログラム(Test1.java)の出力で得られた「java.util.JapaneseImperialCalendar
」のソースを見てみる。(CentOS 7のjava-1.8.0-openjdk-1.8.0.151-5.b12.el7_4.x86_64)
class JapaneseImperialCalendar extends Calendar { /* * Implementation Notes * * This implementation uses * sun.util.calendar.LocalGregorianCalendar to perform most of the * calendar calculations. LocalGregorianCalendar is configurable * and reads <JRE_HOME>/lib/calendars.properties at the start-up. */ /** * The ERA constant designating the era before Meiji. */ public static final int BEFORE_MEIJI = 0; /** * The ERA constant designating the Meiji era. */ public static final int MEIJI = 1; /** * The ERA constant designating the Taisho era. */ public static final int TAISHO = 2; /** * The ERA constant designating the Showa era. */ public static final int SHOWA = 3; /** * The ERA constant designating the Heisei era. */ public static final int HEISEI = 4;
パッケージプライベートなクラスなので直接の参照はできないが、明治、大正、昭和、平成に対応するCalendar.ERA
の定数らしきが定義されている。というわけで試してみる。
- Test3.java
import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.Locale; public class Test3 { public static void main(String[] args) { Locale locale = new Locale("ja", "JP", "JP"); Calendar cal = Calendar.getInstance(locale); System.out.println(cal.getClass().getName()); Map<Integer, Integer> years = new HashMap<>(); years.put(1, 33); // 明治33年 years.put(2, 14); // 大正14年 years.put(3, 25); // 昭和25年 years.put(4, 12); // 平成12年 for (Map.Entry<Integer, Integer> entry : years.entrySet()) { int era = entry.getKey(); int year = entry.getValue(); cal.set(Calendar.ERA, era); cal.set(Calendar.YEAR, year); DateFormat format = new SimpleDateFormat("GGGGy年M月d日", locale); System.out.println(year + ": " + format.format(cal.getTime())); } } }
実行してみる。
$ javac Test3.java && java Test3 java.util.JapaneseImperialCalendar 33: 明治33年3月11日 14: 大正14年3月11日 25: 昭和25年3月11日 12: 平成12年3月11日 $
おぉ、できた、できた、定数の値の実装が変わったらアウトだがなw‥