当初からこれはかなり複雑になることが予想されていましたが、そもそも何故そうなるのか?というと、これは個人的なUIの趣味?というか、要するに日付の入力を単体のフィールドでしかも範囲指定までするという仕様が一番の原因です。
個人的な趣味というのはどういうことか?というと、煩いUIが嫌なんです。入力チェックというと範囲外の入力があった場合にアラートがドンッ!と出てきて「違うぞゴルァ!・・・[OK]」みたいなのがありがちですが、アレほど使い勝手の悪いUIもないとおもいます。
Webアプリとかだと大抵はjavascriptのalert()かなんかで、上記のようなエラーメッセージ?が表示されたりするわけですが、こういうのはまあ百歩譲ったとしてもせいぜいデータベースにデータを入力(INSERT)するような、つまりバックエンドのRDBのデータが変更されるような時ぐらいはまあヨシとして、ここでやろうとしてるようなたかだか検索程度のことでアラートが出てきた日にはもう二度とそのアプリは使いたくなくなるでしょう。
そんなわけで極力そういうキモイUIは避けたく、入力チェックといってもスクリプトに渡す値が適切かどうか判断して、範囲外だった場合は適宜修正してから当該スクリプト(のメソッドとか)に渡す、という方式が好みだったりします。
ところがこれが単純な数値とか五十音パッドのような平仮名だけのように比較的限定範囲が明確なものならいいのですが、日付(しかも範囲指定)となるとなかなか容易ではありません。
容易ではなくなる要因のひとつには、ルールを単純化してしまうと感覚的な入力との差異が際立ってしまうというのがあります。例えば今回のように範囲指定を入力させる場合、2007.03.21-2007.04.15のようにフォーマットに則って正しく入力されればいいのですが、2007.03.21-04.15のように西暦部分が省略された場合など、人間が見れば「あ〜これは後半部分の2007が省略されているな」とすぐわかりますが、プログラム的には非常に面倒くさいことになります。
また日付というのは数字を使って表現されていて一見プログラミングと相性がいいような気もしますが、ご承知のとおりこれがかなりアナログチックな代物で、定型的に?処理しようとすると恐ろしく複雑になっていきます。そもそも0という概念がないし…。
で、愚痴はともかく、普段ならテキトーに誤魔化すところですが、今回はこうした複雑な部分をできるだけスクリプトで実現してみたい?などと考えてしまいました。といってもいきなり複雑なモノを作ろうとすると難しいので、まずはたたき台的に単純なルールでやってみました。
ルール(単純版)
1.日付は年月日(西暦)を[.]ドット区切りで表現する 2007.12.08
2.月日が一桁の場合は桁合わせの0は省略可
3.範囲指定は[-]ハイフンで繋げる 2007.3.21-2007.12.8
4.範囲指定は省略可(2個目の年月日はなくてもよい)
5.年月日のどれかが省略された場合は1を補う 2007.10->2007.10.1
6.不正な値はmktime()に任せる 2007.10.32->2007.11.1
たったこれだけですがスクリプト的にはなにやら複雑な感じになっています(--;。最後のmktime()や桁合わせ0省略ルールはPHPに100%依存しています。んで、とりあえずできあがったのがこれ↓
function _validDate($gdate)
{
//[.]ドットは年月日の区切り、[-]ハイフンは範囲指定
$Ymds = preg_replace("/[^0-9.-]/", "", $gdate);
//2個以上の[-]ハイフンは無視して配列に展開
$Ymd = explode('-', $Ymds, 2);
foreach ($Ymd as $date)
{
$tmpD = preg_replace("/[^0-9.]/", "", $date);
$tmpD = preg_replace("/\.+/", ".", $tmpD);
if ($tmpD <> '')
{
$tmpD = explode('.', $tmpD);
}
while (count($tmpD) < 3)
{
$tmpD[] = 1;
}
list($year, $month, $day) = $tmpD;
$timestamp[] = mktime(0, 0, 0, $month, $day, $year);
}
sort($timestamp);
foreach ($timestamp as $time)
{
$formattedDate[] = date("Y.m.d", $time);
}
$fDates = implode('-', $formattedDate);
return $fDates;
}
まず数値・ドット・ハイフン以外を問答無用で削除、ハイフンでexplodeします。explodeの3個目のパラメータは生成された配列の最大個数です。つまり3個以上にはなりません、また行頭にハイフンがあった場合は1個目の要素が空になります。
次のforeachブロックの最初のpreg_replaceはたぶん余計ですね、なんとなく心配性?(現実世界では全然心配性ではないが…)的に書いちゃいました。その次のpreg_replaceで連続ドットを1個にしています。これは「月」にあたる要素が空白だった場合「日」を前に詰めてしまうことになりますが、[..............]のような極端な場合を想定して、一応これも書いておきます。↓のlist()で4個目以降の要素があっても弾かれるので、この行もたぶん要りません。
次のif文とwhileブロックで空だろうとなんだろうととにかく要素3個(以上)の$tmpD配列を作ります。要素が空の場合は1で埋めています。そしてlist()で年月日変数に割り当てます。ここで4個目以降の要素があった場合でも無視されます。
次にそれをmktime()に渡してUNIXタイムスタンプ配列を得ます。この時点で13月25日とか5月48日のような不正な日付が解消されます。で、2個の要素のタイムスタンプ配列をソートしていますが、これは2個目が1個目より過去の場合を想定していて、SQL生成の際におかしなことになりそうなので必ず「古い日付〜新しい日付」になるようにします。
最後にタイムスタンプを再びdate()でドット区切りの日付に戻し、ハイフンで繋げて返しています。
これで一応最初に作ったルールには合致しているようです(もしかした穴があるかも???)。ルール2はmktime()を通している時点で勝手にIntegerにキャスト?されているとおもいます。
んで実際動かしてみると、まあ書式に則って入力した場合はいいとして、エラーはでないものの↑の感覚的な入力との差異という部分が気になってきます。例えば…
2007.5.21-6.30 =>
2007.05.21-2007.06.30 となって欲しいところですが、
2007.05.21-2008.06.01 となりますし、
2007.05-09 =>
2007.05.01-2007.09.30 か、せめて
2007.05.01-2007.09.01 と期待したいところですが、
2007.05.01-2009.01.01 となってしまいます。
もちろんこれはプログラム的には正しい動作です。しかしどうしても感覚的には前者の方がしっくりきます。また…
2005 とだけ入力した場合は、
2005.01.01-2005.12.31 になって欲しかったり、
2004.02 なら
2004.02.01-2004.02.29のように閏年も考慮した上で範囲を返してくれたらいいなぁ?…ともおもいます。
さらにmktime()を使用したことでいわゆるUnixエポック(1970年1月1日 00:00:00 GMT)の縛り?が生じてしまいます。このアプリでは実際使われている日付データの範囲は2001年〜2007年まででしかないので、問題ないと言えばそうなのですが、範囲外の入力値があった場合に1970.01.01などとなるのは若干気持ち悪い気もしないでもありません。
2005.06.30-50.22 => 1970.01.01-2005.06.30
50.22という値はもはや日付というより謎の数値ですが、これを「月」とみなして、
2005.06.30-2009.02.22
のように変換されてもいいのではないか?…と。
まあそんなわけでとりあえず単純版のルールではなんとか実装できたっぽいので、次回は後半述べたような感覚的な入力との差異の吸収ということに関して考えてみたいとおもいます。
ラベル:codeigniter PHP