2008年01月09日

日付入力のチェックその6(まとめ)

前回まででようやく日付入力チェッククラスが形になってきたので、今回はこのクラスの簡単なテストをした結果とそれを市町村合併データベースアプリに組み込んだ際のメモ、加えて日付入力チェックということに関して今回考えたところを述べてみたいと思います。

まずクラスのチェックとして、与えられた日付(のようなモノ?)に対する出力の一覧です。
基本形
from年のみ20052005.1.1〜2005.12.31
from年月のみ2005.32005.3.1〜2005.3.31
from年月日2005.3.212005.3.21〜2005.3.21
from年月日 + to日のみ2005.3.4-212005.3.4〜2005.3.21
from年月日 + to年月のみ2005.3.4-2006.42005.3.4〜2006.4.4
from年月日 + to月日のみ2005.3.4-10.212005.3.4〜2005.10.21
from年月日 + to年月日2005.3.4-2005.6.212005.3.4〜2005.6.21
不正な値を含むパターン
from年月
2005.142006.2.1〜2006.2.28
from年月日
2005.3.352005.4.4〜2005.4.4
2005.14.12006.2.1〜2006.2.1
月日2005.14.352006.3.7〜2006.3.7
from年月日 + to日
to日2005.3.4-502005.3.4〜2005.4.19
from月2005.14.4-212006.2.4〜2006.2.21
from日2005.3.35-212005.3.21〜2005.4.4
from月日2005.14.35-212006.2.21〜2006.3.7
from月, to日2005.14.21-502006.2.21〜2006.3.22
from日, to日2005.3.35-502005.4.4〜2005.4.19
from月日, to日2005.14.35-502006.3.7〜2006.3.22
from年月日 + to月日
to日2005.3.4-10.352005.3.4〜2005.11.4
to月2005.3.4-14.202005.3.4〜2006.2.20
to月日2005.3.4-14.352005.3.4〜2006.3.7
from日2005.3.50-10.212005.4.19〜2005.10.21
from日, to日2005.3.50-10.352005.4.19〜2005.11.4
from日, to月2005.3.50-14.202005.4.19〜2006.2.20
from日, to月日2005.3.50-14.352005.4.19〜2006.3.7
from月2005.14.4-10.212005.10.21〜2006.2.4
from月, to日2005.14.4-10.352005.11.4〜2006.2.4
from月, to月2005.14.4-14.202006.2.4〜2006.2.20
from月, to月日2005.14.4-14.352006.2.4〜2006.3.7
from月日2005.14.50-10.212005.10.21〜2006.3.22
from月日, to日2005.14.50-10.352005.11.4〜2006.3.22
from月日, to月2005.14.50-14.202006.2.20〜2006.3.22
from月日, to月日2005.14.50-14.352006.3.7〜2006.3.22
from年月日 + to年月
to月2005.3.4-2006.142005.3.4〜2007.2.4
from日2005.3.50-2006.42005.4.19〜2006.5.20
from月2005.14.21-2006.42006.2.21〜2006.4.21
from日,to月2005.3.50-2006.142005.4.19〜2007.3.22
from月,to月2005.14.21-2006.142006.2.21〜2007.2.21
from月日2005.14.35-2006.42006.3.7〜2006.5.5
from月日,to月2005.14.35-2006.162006.3.7〜2007.5.5
from年月日 + to年月日
to日2005.3.4-2006.10.352005.3.4〜2006.11.4
to月2005.3.4-2006.14.202005.3.4〜2007.2.20
to月日2005.3.4-2006.14.352005.3.4〜2007.3.7
from日2005.3.50-2006.10.212005.4.19〜2006.10.21
from日, to日2005.3.50-2006.10.352005.4.19〜2006.11.4
from日, to月2005.3.50-2006.14.202005.4.19〜2007.2.20
from日, to月日2005.3.50-2006.14.352005.4.19〜2007.3.7
from月2005.14.4-2006.10.212006.2.4〜2006.10.21
from月, to日2005.14.4-2006.10.352006.2.4〜2006.11.4
from月, to月2005.14.4-2006.14.202006.2.4〜2007.2.20
from月, to月日2005.14.4-2006.14.352006.2.4〜2007.3.7
from月日2005.14.50-2006.10.212006.3.22〜2006.10.21
from月日, to日2005.14.50-2006.10.352006.3.22〜2006.11.4
from月日, to月2005.14.50-2006.14.202006.3.22〜2007.2.20
from月日, to月日2005.14.50-2006.14.352006.3.22〜2007.3.7

テストとしてはこれだけでは不十分で、0を明示的に与えた場合や7桁を超える数値、また年(西暦)に関しては上記ではテストしていません。

が、上記の一覧では期待通りの結果ですし、その他も一覧には載せていませんが手動テストの結果などから(今のところ)気期待通りに動いていることが確認できています。

ということでクラスは一応ちゃんと動いているということにして(^^;、これを本体に組み込みます。

まずcheckdate.phpというファイルをControllerフォルダの中に作成しそこに件のクラスを全部書きます。この時にこれまでのテストでは[〜]区切りで日付範囲を返していたperiodクラスのgetDateRange()メソッドを[-]区切りで返すように変更します。
public function getDateRange()
{
:
return $from."-".$to;
}

これは日付検索クラスと整合性をとるためです。そして本体?のgappei.phpでは頭でそれをrequireします。
require_once('checkdate.php');

つぎにsearch()メソッド内で以前は日付チェック関数を呼んでいた部分を次のように書き換えます。
$conditions['gdate'] = $this->_valid_date($conditions['gdate']);

$conditions['gdate'] = preg_replace("/[^0-9.-]/", "", $conditions['gdate']);

この部分ではその他のsearchコンディションのヴァリデートに加えて、数値・ドット・ハイフン以外を削除するだけにします。

そしてDecoratorパターンを用いて日付検索クラスをインスタンス化している部分で、日付チェッククラスを使用します。
if ($conditions['gdate'] <> '')
{
$vDate = new Period($conditions['gdate']);
$conditions['gdate'] = $vDate->getDateRange();

$this->cond = new GdateSearch($this->cond, $conditions);
}

これで一応組み込みは完了(のはず)です。でテストしてみました。
offset_error.png
「2005」とだけ入力してみたらいきなりエラー(--;。行数を確認するとlist()を使ってる部分でした。で、とりあえずブラウザで戻ってみると入力値の変換自体はきちんと行われているようだったので、とりあえずlist()の前に@をつけてエラーを抑制することにしておきました。

これでさしあたってエラーも出ず?日付で検索することが可能になりました。↑のエラーはlist()に渡す前に配列の数を数えるなりすればいいのかとおもいます。もしかしたらlist()の値と配列の要素数が異なる場合の書き方?とかがあるのかもしれませんがその辺はまだ調べていません。

で、6回に渡って書いてきた日付入力のチェックですが、一言で言えば「やはりメンドイ!」という感じです。単純に日付としての妥当性をチェックしてアラートでも出した方がスクリプト的にはすっきりすることでしょう。

この実装では不正な日付を極力自然に?解釈しようと試みましたが、果たしてこれが万人にとって自然かどうか?ということになるとかなり怪しい?というか、そもそも年月日の順で入力すること自体日本のローカルルールだったりもしますし、いずれにしろ日付入力に関しては決定打はないのかなぁ?と感じました。

ただ最終的にはクラスも作りGregorianToJDの代替関数もでっちあげたりで個人的にはいろいろと学ぶところは多かったようにおもいます。次回からはCodeIgniterに戻って(^^;、AJAXを組み込むことを考えていきます。
ラベル:codeigniter PHP
posted by ciallost at 21:42| Comment(36) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

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) | 日記 | このブログの読者になる | 更新情報をチェックする

2008年01月05日

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

前回、日付入力チェック用のクラスを書いている過程で、mktime()に依存している部分をなんとかしたいと考えていたところ、PHPのカレンダー関数なるものに到達?しました。

mktime()も含めたPHPのdatetime系の関数は、UNIXエポックの縛りがあるため、1970年を基点として前後およそ70年ぐらいまで(PHP5.1.0以前で環境がWindowsの場合は1970より過去は扱えない)しか扱えません。

これは要するに時間(時分秒)までを含めて32ビット整数の範囲で扱おうとしていることによる縛りで、今回の場合のように時間を扱わず年月日だけの場合は、カレンダー関数の方が広範囲の年月日を扱うことができるということです。

そこで早速これを使って書き直そうとしたのですが、いきなり壁?というか仕様だからしょうがない?というか、にぶち当たりました。

この日付入力チェックでは前述したとおり[2005.3.4-2006.10.21]のように範囲指定で入力されることを基本形として想定していますが、これが[2006.10.21-2005.3.4]のように前後の日付の関係が前のものより後ろの方が過去になる場合というのも考慮しています。

mktime()に依存した前のバージョンでは、これらの日付を一旦UNIXタイムスタンプに変換してから比較して、前後関係がおかしい場合(前のものより後ろの方が過去)場合は入替えています。

で、カレンダー関数で書き換える場合は、UNIXタイムスタンプに相当するものとしてユリウス日(PHPの日本語訳マニュアルではユリウス積算日)を使います。

ユリウス日(通日・積算日)というのは、紀元前4713年1月1日からの通算の日付ということらしいのですが、桁数が多くなりすぎるから修正ユリウス日という一定数を引いたものを使ったりとかいろいろあるみたいです。

んで、ここでは単に日付を比較できればいいわけで、あまり難しいことは考えずに、カレンダー関数のGregorianToJD()という日付をユリウス日に変換してくれる関数を使ってみました。
return GregorianToJD($this->getMonth(), $this->getDay(), $this->getYear());

ところが通常の入力値、つまり月が1〜12までで日が1〜31の場合はこれでよいのですが、例えば[2006.13.10]とか[2006.5.35]などの場合にエラーになってしまいます。

mktime()の場合は↑のような不正?な日付も勝手に変換してくれていたわけです。
2006.13.10 -> 2007.1.10
2006.5.35 -> 2006.6.4

このmktime()の機能?はかなり便利で、後述しますが0が入力された場合でもよきに取り計らってくれます。
2006.0.5 -> 2005.12.5
2006.5.0 -> 2005.4.30
2004.3.0 -> 2004.2.29

最後の例のように閏年もきちんと考慮してくれます。

ですが前述のとおりmktime()はUNIXエポックの縛りがあるためあまり使いたくありません。まあここでの市町村合併データベースでは範囲的には全く困らない(今のところ)のですが、2038年以降にはまずいわけで(^^;(あと30年もありますが…)まあとにかく今更mktime()に戻るわけにはいかないのです。

というわけでユリウス日変換の際に不正な日付を受け付けるようなModGregorianToJD()というメソッドをでっちあげることにしました。
function ModGregorianToJD($month, $day, $year)
{
/***** 6桁までに丸める(7桁の数値でエラーが出るため) *****/
$y = (INT)substr($year, 0, 6);
$m = (INT)substr($month, 0, 6);
$d = (INT)substr($day, 0, 6);

/***** 溢れた日数:初期化 *****/
$overDay = 0;

/***** 日が0の場合は先月(月が0,-1になる可能性がある) *****/
if ($d == 0) { $m = $m - 1; }

/***** 月が0,-1の場合は昨年(年が0になる可能性がある) *****/
if ($m <= 0) { $y = $y - 1; }

/***** 月が-1の場合は11月(これ以上戻る可能性はない:YYYY/0/0 => YYYY/11/30) *****/
if ($m < 0) { $m = 11; }

/***** 月が12より大きい場合12で割って切り捨てて年に足す(月の不正値:13月〜) *****/
$y = $y + ($m > 12 ? (INT)(floor($m / 12)) : 0);

/***** 12月に納まるように12の剰余を月に設定(0の場合は12) *****/
$m = ($m % 12 == 0) ? 12 : $m % 12;

/***** 年が0以下(紀元前または西暦0年(存在しない))の場合は強制的に西暦1年に設定 *****/
if ($y <= 0) { $y = 1; }

/***** 設定された年月からグレゴリオ暦での月の最後の日を取得 *****/
$lastDay = cal_days_in_month(CAL_GREGORIAN, $m, $y);

/***** 日が0の場合は上で求めた月の最後の日を日に設定 *****/
if ($d == 0) { $d = $lastDay; }

/***** 日が月の最後の日を超える場合 *****/
if ($d > $lastDay)
{
/***** 元の日から月の最後の日を引いて溢れた日数を取得 *****/
$overDay = $d - $lastDay;

/***** 日に月の最後の日を設定 *****/
$d = $lastDay;
}

/***** ユリウス積算日取得 *****/
$JD = GregorianToJD($m, $d, $y);

/***** 溢れた日数をユリウス積算日に足して返す *****/
$newJD = $JD + $overDay;

return $newJD;
}

基本方針としては元のGregorianToJD()に渡しても大丈夫なように年月日を修正してから変換し、溢れた日数は後から足しているというだけです。

一応0も考慮しています。また閏年はcal_days_in_month()という関数できちんと計算してくれます。

冒頭で入力値を6桁までに限定している部分は、テストしてみたら7桁の数値でGregorianToJDがエラーになった(マニュアルにはBC.4714〜AD.9999となっています)ので、さしあたって6桁にしておきました。ただこの部分はこのメソッド内に書くべきことではないような気もします。

次回はこれを日付入力クラスに組み込みます。
ラベル:codeigniter PHP
posted by ciallost at 18:45| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

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

前回、日付入力チェック用のクラス(の雛型?)を作成しましたが、今回はそれをマトモなものに修正していきます。

まずPeriodクラス,Ymdクラスのコンストラクタに入力チェック?というか整形?用のコードを付け足します。

Periodクラス function __construct($gdate)
$temp = preg_replace("/-+/", "-", $gdate);
$temp = preg_replace("(^-|-$)", "", $temp);

連続する[-]ハイフンを1個にして、行頭または行末のハイフンを削除します。

Ymd(日付抽象)クラス function __construct($date)
$temp = preg_replace("/[^0-9.]/", "", $date);
$temp = preg_replace("/\.+/", ".", $temp);
$temp = preg_replace("/(^\.|\.$)/", "", $temp);

数値と[.]ピリオド以外は削除して、連続ピリオドを1個に、行頭・行末のピリオドを削除します。

次に日付のtoクラスの方ですが、今までのところでは値がセットされていない場合はfromと同じように1を返していましたが、これを次のようなルールで適当な値を返すように書き換えます。

・年の値がない場合は、fromの年
・月の値がない場合は、fromの月があればそれ、なければ12
・日の値がない場合は、fromの日があればそれ、なければ月の最後の日

というようにしたいのですが、いずれにしろtoからfromに問い合わせる必要があるため、まずコンストラクタを書き換えてfromへの参照を保持するようにします。
class dateTo extends Ymd
{
private $from;

function __construct($date, Ymd $from)
{
$this->from = $from;
parent::__construct($date);
}
:
:
}

この変更に伴いPeriodクラスからdateToを生成している部分を書き換えます。
$this->from = new dateFrom($from);
$this->to = new dateTo($to, $this->from);

先にfromのインスタンスを生成して、toを生成するときにはそれを渡します。

尚、前回は端折っていましたが、Ymdクラス(dateFromとdateToの親)でメンバを初期化するinit()を定義し、それぞれの子クラスで実装します。
abstract class Ymd
{
protected $year;
protected $month;
protected $day;

function __construct($date)
{
$temp = preg_replace("/[^0-9.]/", "", $date);
$temp = preg_replace("/\.+/", ".", $temp);
$temp = preg_replace("/(^\.|\.$)/", "", $temp);
$temp = explode('.', $temp, 3);
:
:
$this->init($tempDate);
}

abstract function init(array $date);
:
:
}

dateFromクラス
function init(array $date)
{
list($this->year, $this->month, $this->day) = $date;
}

dateToクラス
function init(array $date)
{
switch (count($date))
{
case 1:
$this->day = $date[0];
break;
case 2:
if ($date[0] <= 1900)
{
list($this->month, $this->day) = $date;
} else {
list($this->year, $this->month) = $date;
}
break;
case 3:
list($this->year, $this->month, $this->day) = $date;
break;
}
}

dateFromの方は単純に配列をlist()でセットしているだけです。この場合値の数が足りない場合は単にセットされないだけです。つまり年月日の順で値がセットされていくということです。

dateToの方はdateFromとはほぼ逆で、値が1個の時はそれを「日」とみなします。値が3個全部揃っている場合はdateFromと同じですが、値が2個の時は1個目の値に閾値を設けて、それより大きな場合は「年月」とし小さい場合は「月日」とみなすことにします。閾値はとりあえず適当に1900としました。

そしてdateToの年月日それぞれのゲッターですが、↑のルールに則って次のように書きました。
function getYear()
{
if ($this->year <> '')
{
return $this->year;
} else {
return $this->from->getYear();
}
}

function getMonth()
{
if ($this->month <> '')
{
return $this->month;
} else {
if ($this->from->month <> '')
{
return $this->from->getMonth();
} else {
return 12;
}
}
}

function getDay()
{
if ($this->day <> '')
{
return $this->day;
} else {
if ($this->from->day <> '')
{
return $this->from->getDay();
} else {
return date('t', mktime(0,0,0,$this->getMonth,1,$this->getYear));
}
}
}

で、これでだんだんと希望のものに近づいてはきているのですが、一番の問題はmktime()に依存している部分で、これをどうにかできないか?と考えて…いやPHPのマニュアルを見ていたところ、カレンダー関数なるものを見つけました。

で、UNIXタイムスタンプの縛りがないということが判明。急遽そっちを使うことにしました。
ラベル:codeigniter PHP
posted by ciallost at 01:42| Comment(0) | TrackBack(1) | 日記 | このブログの読者になる | 更新情報をチェックする

2008年01月04日

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

前回に引き続き日付入力のチェックを考えてみます。今回は前回の課題?であった感覚的な入力(期待値)との差異という部分をなんとかしてみたいとおもいます。

まず前回定めたルールのうち1〜4まではそのままで問題ないとおもいますが、5.の年月日のどれかが省略された場合は1を補うというルールおよび6.の不正な値はmktime()に任せるという2つのルールをもう一度練り直してみます。

5.のルールは単一の年月日だけならばよいのですが、範囲指定した場合の後のほうの年月日にこれを適用してしまうと、感覚的にちょっと違和感があります。例えば…
2005.04.06-10.25

のような入力の場合、現状だと…
2005.04.06-2012.01.01

になります。

これは後のほうの年月日が10.25なので年が省略されたとは解釈されずに、10を西暦と解釈しようとします。ここで6.のルールのmktime()の仕様で、2桁の西暦は0-69の間の値は2000-2069に70-100は1970-2000にマップされ、従ってまず年は2010となります。

さらに次の25は月と解釈され25=12*2+1で2年繰り上がり年が2012年になり月は1月になります。そして5.のルールで日が空なので1日に設定され上記のような結果になります。

しかし感覚的には(個人的にはですが)
2005.04.06-2005.10.25

になって欲しい。おそらくほとんどの人がそう考えるだろうとおもいますが???(まあいろんな人がいるのでわかりませんが…)とにかく自分は↑のようになってくれたらとおもいました。

んで、ここには2つの問題があります。1つは10.25を自動的に頭から年月日と解釈しようとしていること、もう1つは仮に年月ではなく月日と解釈されたとしても、今度は年が空なので1が補われ結果的に2001.10.25のようになってしまうということです。

そこでまず年月日を頭から解釈していくという部分をなんとかしなければなりません。但しこれは範囲指定の後ろの方の年月日に関してということになります。前の方の年月日は頭から順に解釈していっても特に問題はないようにおもいます(今のところ)。

次に月日と解釈した後に年が空になりますが、ここで自動的に1を補ってしまうというのも考えものです。↑の例では2005、つまり前の方の年月日と同じ年に設定したいわけです。ただしこのルール?も適用したいのは後ろの方の年月日だけで、前の方の年月日は1を補う形で問題ないとおもわれます(今のところ×2)。

このあたりまで考えてみると、どうも前の方の年月日と後ろの方の年月日の処理は分けた方がよさそうな気がしてきます。さらに現状では範囲指定なしの、つまり単体の年月日指定というのも許可していますが、これは煩雑になりそうなので、たとえ年月日が一つしか入力されなくても結果として設定されるモノは範囲指定にした方がすっきりいくような気がします。そんなわけで、ここはやはりclassを作ることにしました。

基本設計?としては年月日オブジェクトというものを想定して、範囲指定なのでそれぞれdateFromクラス,dateToクラスのように範囲指定の[〜]の前をfrom、後をtoというように2つのインスタンスを作ります。さらに範囲指定オブジェクト?を作り年月日オブジェクトはそこから生成するようにします。またdate*クラスはfromとtoで微妙に挙動が違うことが予想されるので大元の抽象クラスを作りそこから継承するようにします。
class Period
{
private $from;
private $to;

function __construct($gdate)
{
$temp = explode('-', $gdate);

list($from, $to) = $temp;

$this->from = new dateFrom($from);
$this->to = new dateTo($to);
}

public function getDateRange()
{
$jd[] = $this->from->get();
$jd[] = $this->to->get();

sort($jd);

$from = date("Y.m.d", jd[0]);
$to = date("Y.m.d", jd[1]);

return $from."〜".$to;
}
}

abstract class Ymd
{
protected $year;
protected $month;
protected $day;

function __construct($date)
{
$temp = explode('.', $date);
}

function get()
{
return mktime(0, 0, 0, $this->getMonth(), $this->getDay(), $this->getYear());
}

abstract public function getYear();
abstract public function getMonth();
abstract public function getDay();
}

class dateFrom extends Ymd
{
function __construct($date)
{
parent::__construct($date);
}

function getYear()
{
if (!isset($this->year)) {return 1;}
return $this->year;
}

function getMonth()
{
if (!isset($this->month)) {return 1;}
return $this->month;
}

function getDay()
{
if (!isset($this->day)) {return 1;}
return $this->day;
}
}

class dateTo extends Ymd
{
function __construct($date)
{
parent::__construct($date);
}

function getYear()
{
if (!isset($this->year)) {return 1;}
return $this->year;
}

function getMonth()
{
if (!isset($this->month)) {return 1;}
return $this->month;
}

function getDay()
{
if (!isset($this->day)) {return 1;}
return $this->day;
}
}

だいたいの概観?は上記の通りです。呼び出し側からはPOSTで受取った値を適宜入力チェック&整形してperiodオブジェクトを生成し、getDateRange()を呼び出す、という流れです。

$gdate = trim(preg_replace("/[ \s]+/u", ' ', $_POST['gdate']));
$gdate = preg_replace("/[^0-9.-]/", "", $gdate);
$vDate = new Period($gdate);
$valid_date = $vDate->getDateRange();

[.]ドットと[-]ハイフン区切りの日付データを受取ったPeriodクラス(オブジェクト)は、ハイフンで範囲指定の前半の日付(from)と後半の日付(to)を分けて、Ymd(日付抽象クラス)を継承したそれぞれのクラスのインスタンスを生成します。

日付クラスではドットで年月日を分けてメンバに持つようにして、あとはgetメソッドで適宜返すようにします。上記では今のところ、設定されていない場合は1を返すように書いてあります。

PeriodクラスのgetDateRange()はそれぞれのdateクラスのget()を呼び出して、ソート&ドット区切りの日付に整形して返します。dateクラスのget()メソッドは親クラス(Ymd抽象クラス)で定義してあり、mktime()でUNIXタイムスタンプを返すようにしてあります。

とりあえずこんな感じで前回の動作を再現することができたので、次回からはこのクラスに手を入れて感覚的な入力との差異を解消していく方向で考えてみます。
ラベル:codeigniter PHP
posted by ciallost at 20:14| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする