Ruby, MySQL のうるう秒の扱い

2015/7/1 にうるう秒が挿入されるということで、うるう秒の話題が盛り上がってるようなので自分も書いてみます。

Linux 上のプログラムが時刻で60秒を刻むには、うるう秒対応のタイムゾーンを使う必要があります。

通常はうるう秒を考慮していないタイムゾーンが使用されているので、60秒を含む時刻になることはありません。 60秒を含む時刻を扱うには、right/Japan のように right/ を前につけたタイムゾーンを指定します。

前回のうるう秒は 2012/7/1 08:59:60 (JST) だったので、これで試してみます。

% TZ=Japan date --date='2012-07-01 08:59:60'
date: `2012-07-01 08:59:60' は無効な日付です
% TZ=right/Japan date --date='2012-07-01 08:59:60'
2012年  7月  1日 日曜日 08:59:60 JST

Ruby

うるう秒なしのタイムゾーン:

% TZ=Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:59").to_i)'
1341100799
2012-07-01 08:59:59 +0900
% TZ=Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:60").to_i)'
1341100800
2012-07-01 09:00:00 +0900
% TZ=Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 09:00:00").to_i)'
1341100800
2012-07-01 09:00:00 +0900

08:59:60 を指定しても 09:00:00 として扱われています。

うるう秒ありのタイムゾーン:

% TZ=right/Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:59").to_i)'
1341100823
2012-07-01 08:59:59 +0900
% TZ=right/Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 08:59:60").to_i)'
1341100824
2012-07-01 08:59:60 +0900
% TZ=right/Japan ruby -rtime -e 'p Time.at(p Time.parse("2012-07-01 09:00:00").to_i)'
1341100825
2012-07-01 09:00:00 +0900

ちゃんと 08:59:60 を扱うことができています。

うるう秒を扱えるかどうかで時刻の内部表現(1970-01-01 00:00:00 UTC からの経過秒数)が25秒ずれていますが、これは今までに25回うるう秒が挿入されたためです。

MySQL

うるう秒なしのタイムゾーン(TZ=Japan で mysqld を起動):

mysql> select from_unixtime(1341100799), unix_timestamp('2012-07-01 08:59:59');
+---------------------------+---------------------------------------+
| from_unixtime(1341100799) | unix_timestamp('2012-07-01 08:59:59') |
+---------------------------+---------------------------------------+
| 2012-07-01 08:59:59       |                            1341100799 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100800), unix_timestamp('2012-07-01 08:59:60');
+---------------------------+---------------------------------------+
| from_unixtime(1341100800) | unix_timestamp('2012-07-01 08:59:60') |
+---------------------------+---------------------------------------+
| 2012-07-01 09:00:00       |                                     0 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100800), unix_timestamp('2012-07-01 09:00:00');
+---------------------------+---------------------------------------+
| from_unixtime(1341100800) | unix_timestamp('2012-07-01 09:00:00') |
+---------------------------+---------------------------------------+
| 2012-07-01 09:00:00       |                            1341100800 |
+---------------------------+---------------------------------------+
1 row in set (0.00 sec)

08:59:60 はパースできずに 0 を返しています。

うるう秒ありのタイムゾーン(TZ=right/Japan で mysqld を起動):

mysql> select from_unixtime(1341100823), unix_timestamp('2012-07-01 08:59:59');
+---------------------------+---------------------------------------+
| from_unixtime(1341100823) | unix_timestamp('2012-07-01 08:59:59') |
+---------------------------+---------------------------------------+
| 2012-07-01 08:59:59       |                            1341100823 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100824), unix_timestamp('2012-07-01 08:59:60');
+---------------------------+---------------------------------------+
| from_unixtime(1341100824) | unix_timestamp('2012-07-01 08:59:60') |
+---------------------------+---------------------------------------+
| 2012-07-01 08:59:59       |                                     0 |
+---------------------------+---------------------------------------+
mysql> select from_unixtime(1341100825), unix_timestamp('2012-07-01 09:00:00');
+---------------------------+---------------------------------------+
| from_unixtime(1341100825) | unix_timestamp('2012-07-01 09:00:00') |
+---------------------------+---------------------------------------+
| 2012-07-01 09:00:00       |                            1341100825 |
+---------------------------+---------------------------------------+

08:59:60 は 08:59:59 として扱われます。やはりパースはできません。

まとめ

  • Linux の場合は特別な設定をしない限り、通常は 60秒を含む時刻を返すことはありません。
  • 60秒を含む時刻をどのように扱うかはプログラム次第です。
  • Ruby は 60秒を含む時刻文字列をパースできますが、MySQL はできません。
  • うるう秒を扱えるタイムゾーンの場合、Ruby は 60秒を含む時刻を返すことがありますが、MySQL は 60秒になることはありません。