hnwの日記

Undocumented strtotime()

さて、この日記も記念すべき30回目になりました。だからと言って大ネタがあるわけでもありません。今回はPHPの日付関数strtotimeに関して、マニュアルに載っていない機能を紹介してみます。


strtotimeは、日付っぽく解釈できそうな文字列を引数に取ってunix timeを返すPHPの関数です。strtotimeのマニュアルによれば、strtotimeはGNU Date Input Formatsに書いてあるフォーマット以外は対応していないかのようですが、実はこれ以外の形式にも対応しています。


僕が気になったものは下記の2つです。

  • 年と、年初から何日目かの指定(YYYY.DDD)
  • 年と、今年の第何週かと、曜日番号の指定(YYYY-Www-D)


手元のPHP 5.2.5で実験してみました。まずは最初の形式、年初からの経過日数の指定をしてみます。

$ php -r 'var_dump(date("Y-m-d", strtotime("2007.256")));'
string(10) "2007-09-13"
$ php -r 'var_dump(date("Y-m-d", strtotime("2007256")));'
string(10) "2007-09-13"


2007年の256日目は9月13日ですから、正しく動作しています。2番目の実行結果の通り、ドットを外して7桁の数字にしても認識します。この機能はPHP 5.1.0以降で実装されています。


さて、次に週番号と曜日番号を指定する形式について見ていきます。これはISO 8601に従っています。(ソースコード内でもisoweekdayなどというトークン名になっています)

$ php -r 'var_dump(date("Y-m-d", strtotime("2007W466")));'
string(10) "2007-11-17"
$ php -r 'var_dump(date("Y-m-d", strtotime("2007-W46-6")));'
string(10) "2007-11-17"


2007年の第46週の土曜日(曜日の指定は月曜=1から日曜=7までで指定します)が11月17日だとわかります。


ただし、PHP 5.1.0〜5.1.6は「-」を含む形式に対応していませんので注意してください。また、PHP5.0.0〜PHP5.0.5にはISO 8601の週番号の計算に関してバグがありますので注意した方がいいでしょう。以下、バグの内容を確認していきます。

$ php-5.0.5 -r 'var_dump(date("Y-m-d", strtotime("2000-W02-2")));'
string(10) "2000-01-04"
$ php-5.2.5 -r 'var_dump(date("Y-m-d", strtotime("2000-W02-2")));'
string(10) "2000-01-11"


PHP5.0.5とPHP5.2.5とで2000年の第2週の火曜日の計算結果が異なっています。実は、これはPHP 5.2.5が正解です。

$ cal -m 1 2000
    January 2000
Mo Tu We Th Fr Sa Su 
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31


カレンダーを見ると1月4日を含む週が第2週のように思えますが、ISO 8601の週番号の定義では1月4日を含む週が第1週になります。ISO 8601では、年をまたぐ週については日数が多い方の年に所属するので、2000年の1月1日と2日は1999年の最終週に所属します。


これ、どちらがバグなのか一見わからなくて面白いですよね。「んなもんわかるか」って感想を持つのが普通かもしれませんけど。


どちらの形式も誰も使わないとは思いますし、攻撃者に悪用されるなどの被害も考えにくいと思いますけど、楽しい利用方法を思いついた方は教えてください。