CTOの石田です。
来る2015年7月1日 AM9:00の直前にうるう秒が発生します。
その日に何かがあってからでは遅いので、Javaで書かれたシステムでうるう秒の発生時に何が起こるのかを調べました。
java.util.Date
まず、基本の java.util.Date クラスです。
時刻を指定してインスタンスを生成するコンストラクタにおいてはjavadocに以下の記述がありました。
秒は 0 〜 61 の整数で表されます。
値 60 および 61 は、うるう秒のためにだけ、そして実際にうるう秒を正確に追跡する Java の実装だけで発生します。
現在使われているうるう秒の算出法では、2 回のうるう秒が同じ分に発生することはきわめてまれですが、この仕様は ISO C による日付および時刻の規約に従います。
なるほど。やってみました。
1 2 |
Date d = new Date(2015 - 1900, 6, 1, 8, 59, 60); System.out.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(d)); |
結果は、 2015/07/01 09:00:00
うるう秒は自然と9:00に変換されるようです。
こういう時刻を指定するコンストラクタは、deprecated になって久しくあまり気にする必要はないでしょう。
では、1970年1月1日 00:00:00からの経過ミリ秒数を使うコンストラクタのケースはどうでしょうか?
この経過ミリ秒は、System.currentTimeMillis() を参照しますが、このメソッドはネイティブ実装になっていて、内部ではOSのクロックを取得しています。
我々のサーバーはLinuxですので、システムクロック自体はうるう秒を識別しません。
このとき、ntpdがSTEPモードで動いている時、23:59:59を2回刻むことになりそうです。
System.currentTimeMillis() が返す値をIDにしているようなやっつけ実装ではIDの衝突問題が起きそうですが、自分のコードにはそういうダサいコードは無いのでこれもOK。
でも一応 SLEW モードにしてジワジワと時計が合うようにしておいた方が良さそう。
VMwareなどの仮想化環境の場合、vmwaretoolsでの時刻あわせとかがどう動くのかは未確認です。
java.util.GregorianCalendar
次に、日付操作でよく使う Calendar クラス。
1 2 3 4 5 6 7 8 9 10 |
Calendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, 2015); cal.set(Calendar.MONTH, Calendar.JULY); cal.set(Calendar.DAY_OF_MONTH, 1); cal.set(Calendar.HOUR_OF_DAY, 8); cal.set(Calendar.MINUTE, 59); cal.set(Calendar.SECOND, 60); // うるう秒 08:59:60 cal.set(Calendar.MILLISECOND, 0); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(cal.getTime())); |
こちらの結果も、 2015-07-01 09:00:00
なるほどこちらも 08:59:60 を指定したところ、09:00:00 になるようなので大丈夫。
java.text.SimpleDateFormat
最後に、文字列の日付を受け取った時にそれをDateに変換する SimpleDateFormat クラス。
1 2 3 4 |
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); f.setTimeZone(TimeZone.getTimeZone("JST")); Date d = f.parse("2015-07-01 08:59:60"); // うるう秒 System.out.println(f.format(d)); |
こちらも結果は、 2015-07-01 09:00:00
これもうるう秒は存在しない。
ただし、setLenient(false) を呼んで厳密に日付チェックを行うように指定していると、parse(“2015-07-01 08:59:60”) は java.text.ParseException を発生させます。
joda-time とか、その子孫のJava8の新日付クラスがどうかは自分が使ってないので調べてないですが、基本のクラスが大丈夫ならおそらく大丈夫かと。
結論
Javaはうるう秒を認識しない。
うるう秒っぽい外部入力を受け付けても適当に変換される。
だからntpdがSLEWモードになっていれば問題ない!
#うるう秒を扱いたい時刻認証アプリとかどうやって実装してるんでしょうかね?