PHPによるカレンダーの作成 :【単月】
2014.12.22
PHPで枡目タイプのカレンダーを作成します。
PHPに限らずカレンダーの作成(生成)には色々な方法が存在し人によって使い慣れた書式があるので『コレがベスト!』ってのは無いと思いますが、汎用性や可読性を考えてコードを書いてみました。
WEBサービス中でカレンダーを表示する場合には単に日付が判ればOKというケースは希で、実際には日付に紐付けられた別の情報を表示するためのユーザー・インターフェイスとしてカレンダーを設置する事が多いかと思います。
どんな情報と日付を関連付けるかは、各サービースによって異なるので適宜アレンジして欲しいのですが、一応<a>タグを使って日付をクリック可能としています。
仕様
最初にカレンダーに必要な条件・制限を考えます。
・暦は西暦とします
・各月は1日から始まります(当たり前かw)
・1週間は日曜日から始まります
・その月は何曜日から始まりますか?
・12ヶ月の内、最少の第4週(日数:28)で終わる場合があります
・12ヶ月の内、最大で第6週(日数:31)まで必要となる月があります
一見、当たり前の事なのですが、今回カレンダーを作成するにあたってのポイントは、『その月は何曜日から始まり、第何週まで存在するか?』です。
図解すると↓こんな感じです。
『1週間は日曜日から・・・』 ってのは、日付と曜日の関係性をチェックする際に非常に重要になってきます。(詳細後述)
枡目タイプのカレンダーだから、7日×6週で42日分の枡目を作ってぇ・・・ と、考えるのもアリですが、第4週までしか存在しない月に、第5・第6週の2週間分の空白を作るのは、あまりスマートとは言えません。なので今回は、各月に第何週まで存在するかプログラムでチェックしてカレンダーを生成します。
構造
次にカレンダーを各要素に分解し、どのようにプログラムを組み立てるか考えてみます。
上記の様に各週を配列として考え、曜日をキーとする事で、上図のカレンダーでは $week2[0] ならば 2日 、 $week5[5] ならば28日、 $week6[4]ならば該当日無しという具合に、その月の任意の日付と曜日を特定する事が可能となります。
配列(ここでは各週)のキー(ここでは曜日)ですが、日曜日を[0](ゼロ)としたのは慣例だからではなく、PHPのDate関数で任意の日付の曜日を取得すると、日曜日は[0]となり、後は順に土曜日まで各曜日に対応した[1]~[6]までのキーが返されるからです。なので先に述べたように 『1週間は日曜日で始まる』 とした方が具合が良いのです。
勿論、『1週間は月曜日で始まる』とする事も出来ますが、その際は配列を並びを変える処理が必要になります。
PHP.net 公式マニュアル
- date - ローカルの日付/時刻を書式化する
- mktime - 日付を Unix のタイムスタンプとして取得する
- checkdate - グレゴリオ暦の日付/時刻の妥当性を確認します
- array_key_exists - 指定したキーまたは添字が配列にあるかどうかを調べる
- ksort - 配列をキーでソートする
sample_calendar1.php : Sample Code
<?php $yy = date('Y'); $mm = date('m'); $dd = date('d'); $mi = date(w,mktime(0,0,0,$mm,1,$yy)); //対象月:1日の曜日取得 //6週間分の配列初期化 $week1 = array(); $week2 = array(); $week3 = array(); $week4 = array(); $week5 = array(); $week6 = array(); //各週配列に値(日付)を格納 //--------------------------------第1週 $dx = 0; for($i=$mi; $i<=6; $i++){ //先に取得した対象月の1日の曜日をカウンタにセット $dx++; $week1[$i] = $dx; } //--------------------------------第2週 $dx = $week1[6]; //前週の土曜日( key=[6])の日付 for($i=0; $i<=6; $i++){ $dx++; $week2[$i] = $dx; } //--------------------------------第3週 $dx = $week2[6]; for($i=0; $i<=6; $i++){ $dx++; $week3[$i] = $dx; } //--------------------------------第4週 $dx = $week3[6]; for($i=0; $i<=6; $i++){ $dx++; $week4[$i] = $dx; } //--------------------------------第5週 $dx = $week4[6]; for($i=0; $i<=6; $i++){ $dx++; $week5[$i] = $dx; } //--------------------------------第6週(最大週) $dx = $week5[6]; if($dx < 31){ //前週の最後が最大日数の31より少ない場合に第6週の配列に値を格納 for($i=0; $i<=6; $i++){ $dx++; $week6[$i] = $dx; } } //第1・第5・第6週について日付の正当性をチェック //存在し得ない日付は[・]記号に置換 for($i=0; $i<=6; $i++){ if ( !array_key_exists($i, $week1) ){ $week1[$i] = "・"; } if ( !checkdate( $mm, $week5[$i], $yy )){ $week5[$i] = "・"; } if(!empty($week6)){ if ( !checkdate( $mm, $week6[$i], $yy )){ $week6[$i] = "・"; } } } ksort($week1); //第1週配列をkeyの順番で整列させる if(!empty($week5) && $week5[0] == "・"){ //第5週の日曜日が[・]ならば配列を初期化 $week5 = array(); } if(!empty($week6) && $week6[0] == "・"){ //第6週の日曜日が[・]ならば配列を初期化 $week6 = array(); } //配列を画面に出力するユーザー関数 function cal_week_echo($arr){ if( !empty($arr) ){ echo "<tr>\n"; foreach($arr as $k => $d){ $date = strval($d); if($date != "・"){ if($k == 0){ echo "<td class=\"sun\"><a href=\"#\">".$date."</a></td>\n"; }else if($k == 6){ echo "<td class=\"sat\"><a href=\"#\">".$date."</a></td>\n"; }else{ echo "<td><a href=\"#\">".$date."</a></td>\n"; } }else{ echo "<td>".$date."</td>\n"; } } echo "</tr>\n"; } } ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>roomX.jp | PHP | カレンダー Sample.1</title> <style> table { border: 1px solid #999999; border-collapse:collapse; } th { border: 1px solid #999999; padding-top: 10px; padding-bottom: 10px; padding-right: 15px; padding-left: 15px; } td { border: 1px solid #999999; text-align: center; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; } .sun { background-color: #FFAAAA; } .sat { background-color: #E6E6FF; } </style> </head> <body> <h1>PHP:Calendar - sample.1</h1> <table summary="<?php echo("Calendar".$yy." - ". $mm); ?>"> <tr> <th colspan="7"><?php echo($yy." - ". $mm); ?></th> </tr> <tr> <th>Sun</th> <th>Mon</th> <th>Tue</th> <th>Wed</th> <th>Thu</th> <th>Fri</th> <th>Sat</th> </tr> <?php cal_week_echo($week1); cal_week_echo($week2); cal_week_echo($week3); cal_week_echo($week4); cal_week_echo($week5); cal_week_echo($week6); ?> </table> </body> </html>
上記のプログラムで何をしてるかと云うと、要はその月の第1週の1日を押さえたら、後は各週毎に最大6週まで配列に日付を日曜日[0]を頭にして放り込んでしまうワケです。(実際にDate関数は最初に1度しか使っていません)
そうすると、例えば 2月35日 など、絶対に有り得ない日付も配列に入ってしまうのですが、最終的にcheckdate関数で日付の正当性をチェックし、不当な日付は全部[・]記号で置換、もし[・]記号が日曜日に入ったらその週(主に第5・第6週)は初期化して空の配列に戻しています。(こうしないと閏年の2月29日をスルーしてしまう)
無駄な処理をしているように思えますが、空の配列に戻す事でユーザー定義関数に於ける条件分岐が簡単になり、HTML内に記述した6行のPHPコードも固定にする事ができます。
今回は最終的に<table>タグを使っていますが、配列の形で日付と曜日の情報を保持する事で最終出力の自由度も上がると思います。
実はこのコードは、元々ガラケーのブラウザ用に書いたモノで、ガラケーでは各週を <center> タグを使ってレイアウトしていたので各週を配列化した方が都合が良かったのです。
本来は、配列に放り込む前に有り得ない日付をチェックして除外する方が良いのかもしれませんが、コードが複雑になりそうなので自分はこうしてみました。