hnwの日記

日付と時刻の豆知識 (2)うるう秒とコンピュータ

うるう秒とは、地球の自転と時刻とのずれを補正するために、特定の日を1秒だけ長くしたり短くしたりして調整するものです。私たちが日常的に利用している時刻は原子時計を基準にしていますが、地球の自転する速度は僅かながら変化し続けているので、観測に基づいて調整を入れる必要があるのです。これまで、基準値以上のずれが観測されるたびに実施されてきました。


このうるう秒は1972年から2010年までの間に24回実施されており、いずれも1秒長くする補正でした。一番最近では2008年12月31日が1秒長くなっており、UTCで23:59:60という時刻が挿入されました。

うるう秒UNIXタイムスタンプ

このうるう秒をコンピュータ上では無視することが多いようです。多くのOSにはうるう秒をサポートする機能があるのですが、デフォルトで無効になっているのが普通です。また、UNIXタイムスタンプについても、通常うるう秒を含めません。たいていのシステムでは、うるう秒を正確に扱うメリットよりも、うるう秒が原因でバグを引き起こすリスクのほうが大きいということなのでしょう。


このように、時刻としてのうるう秒については無視してしまうことが多いはずです。

うるう秒と日付文字列

UNIXタイムスタンプの場合と異なり、日付の文字列表記についてはうるう秒の考慮が必要です。というのも、紹介した通り「2008-12-31 23:59:60 UTC」は正しい日付文字列です。日付文字列を受け取るシステムであれば、これを受け入れる仕様にしておく方が事故は減ると考えられます。この場合、時刻として正しく扱うかどうかとは別問題であることに注意してください。


ISO 8601では23:59:60まで正しい時刻であることが仕様として明記されています。正しいうるう秒でないときに60秒を使った場合は不正な時刻となるようですが、未来のうるう秒については観測してみないと決まらないわけですから、厳密には実装が不可能です。


SQL92のTIMESTAMP型の定義を見ると、23:59:61までの時刻を受け入れることになっています。これは2秒のうるう秒まで考慮した仕様ですが、実際には2秒同時にうるう秒が入ることはありません。経緯などはわかりませんが、オーバースペックな仕様だといえます。


各種アプリケーションでも、これらの仕様が反映されています。例えばPosgreSQLは何時何分だろうと60秒を受け入れるようになっています。一方で、SQL92に反して61秒はエラーになります。当然60分もエラーになります。PostgreSQL 8.1.11での挙動は下記のようになります。

postgres=# select '2008-12-31 23:59:60'::timestamp;
timestamp

                                        • -

2009-01-01 00:00:00
(1 row)
postgres=# select '2008-12-31 23:59:61'::timestamp;
ERROR: date/time field value out of range: "2008-12-31 23:59:61"
postgres=# select '2008-12-31 23:60:59'::timestamp;
ERROR: date/time field value out of range: "2008-12-31 23:60:59"


PHPのDateTime型についても、仕様としては明記されていないものの同様の挙動のようで、60秒までを受け入れます。

<?php
$dt1=new DateTime("2008-12-31 23:59:60 UTC");
echo $dt1->format("c").PHP_EOL;
try {
  $dt2=new DateTime("2008-12-31 23:59:61 UTC");
} catch(Exception $e) {
  // 例外が発生します
  echo $e->getMessage().PHP_EOL;
}
try {
  $dt3=new DateTime("2008-12-31 23:60:59 UTC");
} catch(Exception $e) {
  // 例外が発生します
  echo $e->getMessage().PHP_EOL;
}


これを実行すると次のようになります。

2009-01-01T00:00:00+00:00
DateTime::__construct(): Failed to parse time string (2008-12-31 23:59:61 UTC) at position 18 (1): Unexpected character
DateTime::__construct(): Failed to parse time string (2008-12-31 23:60:59 UTC) at position 15 (0): Double time specification


普段はうるう秒を意識する必要がないけど、少しだけプログラミングとも関係があるよ、という話題でした。