strtotime関数との付き合い方
PHPで日付・時刻の処理を書く際、「strtotime()関数を使うと可読性が高くなって良い」と僕は思っていたのですが、全面的に信用するのは危険だと感じてきました。
strtotimeというのは、文字列をunix timeに変換する関数です。「2008-12-24 23:59:59」のような文字列はもちろん、様々なフォーマットの文字列を解釈してくれます。中でも特徴的なのは下記のような文字列を理解することでしょう。
- now
- +1 day
- +1 week
- +1 week 2 days 4 hours 2 seconds
- next Thursday
- last Monday
これを解釈してくれること自体はいいんですが、与えられた文字列をどう解釈するかについてはどこにもドキュメントがありません。下記のような文字列も解釈してくれるんですけど、どう解釈するのが正解なのかは僕にもわかりません(実際に試すとカオスな結果になります)。
- twelfth day Nov 2008
- 2008-11 +12 days
- third week 2008
- Thu 2008 VIII
- 2008-03-01 yesterday previous month
ここまで実験してみて、strtotime関数には下記のような問題があると感じるようになりました。
- 複雑で仕様決定が難しい問題なのに、仕様がどこにもない
- そもそも作者の頭の中にも統一的な仕様が無いように見える
- そのため、未来のバージョンのPHPで同じ動作をする保証が無い
利用する基準
とはいえ、strtotime関数を使わないというのは無理な話です。strtotime()は様々な形式の文字列を理解するわけですが、どの形式なら与えても安心かを考えてみました。
GNUのDate input formatsの表現のうち、下記の4つは安心だと思います。DBのdatetime型やtimestamp型の返す値もこれに含まれます。
- Calendar date items: 19 Dec 1994.
- Time of day items: 9:20pm.
- Time zone items: est, pdt, gmt.
- Pure numbers in date strings: 19931219, 1440.
残りが2つあります。これらはあまり使うべきでないと現時点では考えています。
- Day of week items: Monday and others.
- Relative items in date strings: next tuesday, 2 years ago.
調べているうちに、まずstrtotime関数で曜日を扱うのが危険に思えてきました。また、firstなどの序数や、next/lastなどの英単語も使いたくない気分です。というのも、これらは仕様が固まり切っておらず、いつ挙動が変わるかわからないように思えるためです(本記事末尾のbugs一覧参照のこと)。
実験した範囲では、「+3 hour」などの「数字+単位(年月日時分秒)」による相対指定は仕様のブレやバグの入る余地が無い気がします。このあたりまでが許容範囲かもしれません。
とりあえずGNUのdateコマンドと同一の状態を目指してくれれば十分な気がするんですが、現状で結構異なっているので何が何やらという感じですね。
気になるstrtotime関連のbugs一覧
- PHP Bugs: #43452: strtotime returns wrong date when requested day is same as first day of the mon
- PHP5.2.7から'+1 Monday'など「日付+曜日」の挙動が変わっています。今までどういう仕様だと思って実装してたのかが疑問ですが、今までバグってたというならそうなんでしょう。
- PHP Bugs: #45822: Invalid value passed to strtotime() causes endless loop
- 引数によっては無限ループに入るようです。最新版を含め絶賛放置中。
- PHP Bugs: #46930: 5.3.0alpha3's strtotime() returns inconsistent result with some relative items
- 'last day'や'next week'などについて、たしかに5.3.0から仕様が変わりましたが何か?ということらしいですね。カオスに見えますが、ドキュメントを書くつもりはあるようなので、待ってればいまに全容がわかるのかもしれません。
- PHP Bugs: #46932: strtotime() has inconsistency between 'next Monday' and '+1 Monday'
- 5.2.7から'next Monday'と'+1 Monday'の挙動が異なっていますが、documentation problemということらしいです。となると、少なくともドキュメントが出るまでは使えないことになりそうですね。