2008年01月06日

日付入力のチェックその5

前回、PHPのdatetime系関数からカレンダー関数に書換え、日付の計算にはユリウス日を使うことにしたところ、カレンダー関数のユリウス日を計算するGregorianToJD()はUNIXタイムスタンプを返すmktime()とは違って、不正な日付を修正?(吸収?)してくれませんでした。

そこでGregorianToJD()の替わりに、mktime()のように2005.13.21とか2005.3.35, 2004.3.0,…etc'のような不正な日付でも受け付けるようにModGregorianToJD()という関数を考えてみました。

今回はそのModGregorianToJD()を日付入力チェッククラスに組込んでテストした際のメモです。

組み込んだ先のクラスは日付抽象クラスのYmdクラスです。
abstract class Ymd
{
:
:
function get()
{
return $this->ModGregorianToJD($this->getMonth(), $this->getDay(), $this->getYear());
}

private function ModGregorianToJD($month, $day, $year)
{
:
:
return $JD + $overDay;
}

}

内部的にしか利用しなさそうなのでPrivate functionにして、get()でユリウス日を返している部分でGregorianToJD()を使っていた部分をModGregorianToJD()に書き換えます。

さらにdateToクラスのgetDay()メソッド内で月の最後の日を返している部分も書き換えます。
return date('t', mktime(0,0,0,$this->getMonth,1,$this->getYear));

return cal_days_in_month(CAL_GREGORIAN, $this->getMonth, $this->getYear);

これで不正な日付を渡しても概ね?は正しい範囲を返すようになりました。が!、まだエラーが出る場合がありました(--;。

原因は↑で書き換えたdateToクラスのgetDay()内で使用しているcal_days_in_month()関数で、この関数もGregorianToJDと同じく不正な日付はエラーになってしまいます。

この関数は前回作成したModGregorianToJD()内でも使用していますが、その場合は事前に不正な日付を修正してから渡していたのでエラーにはなりませんでした。

というわけでまずModGregorianToJD()内で月と年を正常な値に変換している部分を切り出してgetValidMonthYear()という別メソッドにしました。

abstract class Ymd
private function ModGregorianToJD(&$m, &$d, &$y)
{
if ($d == 0) { $m = $m - 1; }
if ($m <= 0) { $y = $y - 1; }
if ($m < 0) { $m = 11; }
if ($y <= 0) { $y = 1; }

$valid = $this->getValidMonthYear($m, $y);
$m = $valid['month'];
$y = $valid['year'];

$lastDay = cal_days_in_month(CAL_GREGORIAN, $m, $y);

$overDay = $d > $lastDay ? $d - $lastDay : 0;

if ($d == 0 || $d > $lastDay) { $d = $lastDay; }

$JD = GregorianToJD($m, $d, $y);

return $JD + $overDay;
}

protected function getValidMonthYear($month, $year)
{
$valid['year'] = $year + ($month > 12 ? (INT)(floor($month / 12)) : 0);
$valid['month'] = ($month % 12 == 0) ? 12 : $month % 12;

return $valid;
}

ついでにModGregorianToJD()でロジック的に冗長だった部分を書き直しています。引数は内部的に変更するので参照渡しにしました。また最大値を6桁に制限していた部分は外に出してYmdクラスのコンストラクタ内で処理するような形に変更しました。
function __construct($date)
{
$temp = preg_replace("/[^0-9.]/", "", $date);
$temp = preg_replace("/\.+/", ".", $temp);
$temp = preg_replace("/(^\.|\.$)/", "", $temp);
$temp = explode('.', $temp, 3);

foreach ($temp as $val)
{
$tempDate[] = (INT)substr($val, 0, 6);
}

$this->init($tempDate);
}

次にdateToクラスのgetDay()で年月の値を正常値にしてからcal_days_in_month()を呼ぶように修正しました。
function getDay()
{
if ($this->day == '')
{
if ($this->from->day <> '')
{
return $this->from->getDay();
} else {
return $this->getLastDay();
}
}

return $this->day;
}

private function getLastDay()
{
$valid = $this->getValidMonthYear($this->getMonth(), $this->getYear());
$lastDay = cal_days_in_month(CAL_GREGORIAN, $valid['month'], $valid['year']);

return $lastDay;
}

ここまでの修正でエラーは出なくなりましたが、明示的に0を入力した場合の挙動が微妙に期待通りではありませんでした。これは主にdateToクラス内で値がセットされてない場合の判定を以下のようにしていることが原因とおもわれます。
if ($this->month == '')

そこでいろいろ試行錯誤した結果下記のように書き直しました。
if ($this->month == '')
{
if ($this->month === 0)
:

これでようやく月の値がない場合や明示的に0の場合は期待通りになりましたが、まだ日の値がセットされていない場合が期待通りに動きません。調べてみるとどうやら空の配列を渡しているとおもっていた部分で実は空ではなくArray([0]=>'')のような空の要素一個の配列を渡していました。

そこでYmdクラスのコンストラクタを下記のように修正して、空の配列を渡すように変更しました。

Ymdコンストラクタ
        :
$temp = explode('.', $temp, 3);

if (count($temp) == 1 && $temp[0] == '')
{
$tempDate = array();
} else {
foreach ($temp as $val)
{
$tempDate[] = (INT)substr($val, 0, 6);
}
}
$this->init($tempDate);

これでようやくエラーもなくほぼ期待通りの動作をしているようです(自信はない…)。次回はこのクラスのテストとこれをやっと本体に組み込むところまでのメモを書きたいとおもいます。
ラベル:codeigniter PHP
posted by ciallost at 18:38| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする