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) | 日記 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック