PHPの日時に関連する関数の制限

PHP

問題意識

PHPが表現できる日付に制限があり、1800年といった表記をdate関数から
出力できないため、1800年から2500年程度の時間表現を行う方法を導く。

実験内容

まず、制限値の詳細を知るために
1.date関数の第2引数に与えられるタイムスタンプの範囲を調べる。
2.strtotime関数の引数に指定できる時間の範囲を調べる。

実験結果

1.date関数の第2引数にタイムスタンプとして指定した場合:
最小値:-2147483648(1901-12-14 05:45:52)
最大値: 2147483647 (2038-01-19 12:14:07)

2.strtotime関数の引数に時間を指定した場合
最小値: 1901-12-14 05:45:52
最大値: 2038-01-19 12:14:07

まとめ

32ビット環境において、
PHPで整数を扱う場合は-2^31から2^31-1しか扱えない。

次の問題意識

pearのDateクラスは有効か調査する。

実験内容

2.1 コンストラクタに2147483647を指定し、
秒を追加してゆき、getDateを表示してみて
想定する結果との差異を検証。

2.2
コンストラクタに-2147483648を指定し、
秒を減らしてゆき、getDateを表示してみて
想定する結果との差異を検証。

実験結果

2.1
9999-12-31 23:59:59 まで表示可能その後に1秒を足すと現在時刻に初期化される。

Date.phpのソースコードを追ってみると、年・月・日や時・分・秒はそれぞれ変数になっている。
なぜ、999年までしかいけないかというと、addSpanメソッドで無理矢理4桁に処理されているため。

2.2
999-12-31 23:59:59 まで表示可能。その後に1秒を引くと9909-12-31 23:59:58となる

まとめ

西暦が4桁ならば正常に動作。

次の問題意識

3.1 mktimeのサポートする範囲

実験結果

3.1
年の入力に制限がある。
年の入力範囲:0-38,70-110,1903-2038

まとめ

mktimeは1903年から2038年まで。

次の問題意識

4.checkdateのサポートする範囲

実験結果

4.西暦1年1月1日から、西暦32767年12月31日

まとめ

西暦は16ビット分。チェックには利用可能。

次の問題意識

mktime,strtotimeが表現できる日時に制限があるため、目的の範囲を
サポートする日時のvalidation方法はあるのか。

調査

・symphonyはmktime,strtotimeを利用してチェック。
・zend frameworkはcheckdateにて日付をチェック。
・Mapleはcheckdateにて日付のみチェック。

結論

PHPのネイティブ関数でサポートする日時表記の積集合部分は
1901-12-14 05:45:52から2038-01-19 12:14:07である。

日付のチェックはcheckdateを使えば実用に問題はない。

時間のチェックは自分で書く。

ネイティブでサポートされていない日時表記のためにはPearのDateクラスを利用すれば
1000年から9999年まで表現できる。

<?php
/**
 * 実験1
 * date関数の第2引数がサポートするタイムスタンプの最大値、最小値を求める。
 *
 */

require_once('Date.php');

print 'result:'.getMax(2147558400 - 60*60*24, 1);

function getMax($from = 0 , $step = 1){

  $i = $from;
  $last_i = 0;

  while(true){

    $current =  date('Y-m-d H:i:s', $i);

    if($current == $last){
      return $last_i;
    }

    if(($i % $step) == 0){
      print sprintf("%s %d\n", $current, $i);
    }

    $last_i = $i;
    $last = $current;

    // increment
    $i += $step;

  }

}

<?php
/**
 * 2.Dateクラスの範囲測定
 *
 */

require_once('Date.php');

//$date = new Date(2147483647);
$date = new Date(-2147483647);

for($i=0;$i < 902;$i++){
$date->addSeconds(-60*60*24*365);
}
$date->addSeconds(-60*60*24*200);

$last = '';
while(true){
  $current = $date->getDate();
  if($current == $last){

    var_dump($current);
    var_dump($date);
    break;
  }

  var_dump($current);

  $date->addSeconds(-1);
  $last = $current;

}


<?php
/**
 * 実験3
 * mktimeがサポートする範囲
 *
 */

for($i=0;$i<10000;$i++){

  var_dump(mktime(0,0,0,0,0,$i));
  print $i."\n";
}


<?php
/**
 * 実験4
 * checkdateがサポートする範囲
 *
 */

var_dump(checkdate(1,1,-100));
var_dump(checkdate(1,1,1));
var_dump(checkdate(1,1,10));
var_dump(checkdate(1,1,1969));
var_dump(checkdate(1,1,2039));
var_dump(checkdate(1,1,9999));
var_dump(checkdate(1,1,10000));
var_dump(checkdate(12,31,32767));

コメント