strtotimeとtimestampの素敵な関係

基本的には関数型言語推しですが、業務では諸事情でPHPを使っています。
色々と罠の多い言語であることは知っていたんですが、先日「これはあんまりだろ…」という挙動を踏み抜きました。
軽くググった限りでは同様の事例紹介が見当たらなかったのと、あまりにも衝撃的だったため記事として残しておきます。

前置き:MySQL DATETIMEとstrtotime

PHPerな諸氏におかれましては、strtotimeが色々と頑張りすぎるせいでハマったことは1度や2度ではないのではないでしょう。
例えばMySQLのDATETIMEで未定義を表す 0000-00-00 00:00:00を32bit環境でstrtotimeに渡すとfalseになります。
ドキュメントを見ると

返り値 ¶
成功時はタイムスタンプ、そうでなければ FALSE を返します。 PHP 5.1.0 以前ではこの関数は失敗時に -1 を返します。

と書かれており、「そうか、日付として不正だから解釈に失敗してfalseを返したんだな。0月とか存在しないし」と思ってしまいそうになるんですが、同じコードを64bit環境で実行すると以下のようになります。

$ php -r 'var_dump(strtotime("0000-00-00 00:00:00"));'
int(-62170016400)

謎の数値が出てきました。
これが一体いつを表しているのか、さらにdate関数を掛けて確認してみます。

$ php -r 'var_dump(date("Y-m-d H:i:s", strtotime("0000-00-00 00:00:00")));'
string(20) "-0001-11-30 00:00:00"

つまり、PHPはMySQLのDATETIMEの未定義値を「マイナス1年11月30日の0時0分0秒」とみなしていることがわかります。
実はPHPでは0月が「前年の12月」を、0日が「前月の最終日」を、それぞれ表すと解釈されるので、0年の1ヶ月前の更に1日前で-1年11月30日という日付が導かれるんですね。
32bit環境でfalseが返っていたのは、日付として不正だからでも何でもなく、単に「表現できる整数値の範囲を超えていたから」という理由だったわけです。

この挙動の違いは公式ドキュメントのコメント欄にさんざん書かれているので知っている人も多いと思います。
一方、今回踏んだ挙動は発生条件がかなり限られていることと、そもそも普通はそんなことしないだろう、という辺りであまり知られていないだろう挙動です。
あ、ここまでが前振りです。長くてすみません。

Continue reading