PHPによるカレンダーの作成 :【年間カレンダー】
2014.12.25
前回は単月のカレンダーをPHPで作成しましたが、今回は任意の年(西暦)の年間カレンダーを生成してみます。
単月のカレンダーは少々特殊な生成方法を採りましたが、今回は正攻法(?)で攻めたいと思います。
12ヶ月分(365日、閏年は366日)の日付と曜日を一つの配列に格納し、最終的にブラウザに出力する段階で比較的レイアウトが自由になるようにしてみました。
一つの配列に多くの情報を格納するので今回は多次元配列を使いますが、多次元配列を使うと配列から値(情報)を取り出す際に少々コツが要ります。これは配列の次元数が増えれば増える程、値を取り出す処理も増えるので、便利だから…と云って階層化もホドホドにしないと逆に後で苦労する事になります。(※経験談)
無理に多次元配列を使わずとも年間カレンダーを作成・表示する事は可能ですが、必要な情報(月・日・曜日)を得るのに逐次配列を作成するよりも、一回で年間の日付情報を取得して一つの配列に全ての情報を格納した方が最終的なレイアウトや他の情報との連携に於いて自由度が増すと考えました。
一つの多次元配列に年間全部の日付情報が入っているので、どれほどレイアウトが複雑になったとしても、その配列の中から必要な情報をだけを取り出せして表示することが可能になります。
但し、単月毎に配列を生成するオーバーヘッドとメモリの使用量とのトレードオフになるのでサーバー環境やアクセス数に応じて、どちらを採用するか検討する必要はあるかもしれません。
仕様
最初にカレンダーに必要な条件・制限を考えます。
- 暦は西暦とします
- カレンダーは枡目タイプのカレンダーとします
- 任意の年の12ヶ月分の暦を1画面に表示します
- 四半期(3ヶ月)を1行として表示します(3列×4行)
- 現在の年をデフォルトとし、±1年を指定可能にします
構造
生成する多次元配列は、一次のキーを月(1~12月)とし、二次のキーをそれぞれの月の日付とします。
曜日を値として、前回と同じく日曜日~土曜日までを【0】~【6】の文字列(※数値では無い)で配列に格納します。
今回は当年だけでなく前年・翌年を指定し、指定された年の年間カレンダーを表示可能とするので $_GET を使います。(※仕様により西暦:1902~2037年までのカレンダーとなります)
今回の多次元配列の中身(?)は↓こんな感じになります。
PHP.net 公式マニュアル
- date - ローカルの日付/時刻を書式化する
- mktime - 日付を Unix のタイムスタンプとして取得する
- count - 変数に含まれるすべての要素、 あるいはオブジェクトに含まれる何かの数を数える
- intval - 変数の整数としての値を取得する
sample_calendar2.php : Sample Code
<?php $now_yy = intval(trim($_GET['year'])); if ( $now_yy < 1902 || $now_yy > 2037 ){ $now_yy = date('Y'); }else if( empty($now_yy) ){ $now_yy = date('Y'); } $prev_yy = $now_yy - 1; $next_yy = $now_yy + 1; $month = array(); //配列初期化 for($i=1; $i<=12; $i++){ //1-12月 $m_last = date('j',mktime(0,0,0,$i+1,0,$now_yy)); //対象月:最終日・日付取得 for($j=1; $j<=$m_last; $j++){ //各月日数カウント $d = date('w',mktime(0,0,0,$i,$j,$now_yy)) ; //曜日取得 $month[$i][$j] = $d; } } //echo $now_yy."\n"; //var_dump($month); //exit; function cal_month_echo($arr,$y,$m){ $arr_m = $arr[$m]; echo <<<EOF <div class="calendar"> <table summary="Calendar{$y}/{$m}"> <tr><th colspan="7">{$m}</th></tr> <tr><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr>\n EOF; $num = count($arr_m); $wd_last = $arr_m[$num]; //月末曜日取得 foreach($arr_m as $k => $wd){ if($k == 1 && $wd != 0){ //余剰セル処理 echo "<tr>"; for($i=0; $i<$wd; $i++){ echo "<td>・</td>"; } } if($wd == 0){ echo "<tr>"; echo "<td class=\"sun\">".$k."</td>"; //sunday } if($wd != 0 && $wd != 6){ echo "<td>".$k."</td>"; //weekday } if($wd == 6){ echo "<td class=\"sat\">".$k."</td>"; //saturday echo "</tr>\n"; } } if($wd_last != 6){ for($j=$wd_last; $j<6; $j++){ //余剰セル処理 echo "<td>・</td>"; if($j == 5){ echo "</tr>\n"; } } } echo "</table>\n"; echo "</div>\n"; } ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>roomX.jp | PHP | カレンダー Sample.2</title> <style> table { border: 1px solid #999999; border-collapse:collapse; text-align: center; margin-right: auto; margin-left: auto; margin-top: 20px; margin-bottom: 20px; } th { border: 1px solid #999999; padding-top: 5px; padding-bottom: 5px; padding-right: 10px; padding-left: 10px; } td { border: 1px solid #999999; padding-top: 5px; padding-bottom: 5px; } .sun { color: #FF0000; } .sat { color: #0000FF; } .calendar { float: left; width: 33%; } .setyear { width: 100%; text-align: center; padding-top: 20px; padding-bottom: 10px; } .setyear a{ width: 100%; text-align: center; margin-right: 30px; margin-left: 30px; } .quarter { clear: left; width: 100%; margin-right: 0px; margin-left: 0px; } </style> </head> <body> <h1>PHP:Calendar - sample.2</h1> <div class="setyear"> <a href="sample_calendar2.php?year=<? echo($prev_yy); ?>">← <? echo($prev_yy); ?></a> <b><? echo($now_yy); ?></b> <a href="sample_calendar2.php?year=<? echo($next_yy); ?>"><? echo($next_yy); ?> →</a> </div> <div class="quarter"> <?php cal_month_echo($month,$now_yy,1); cal_month_echo($month,$now_yy,2); cal_month_echo($month,$now_yy,3); ?> </div> <div class="quarter"> <?php cal_month_echo($month,$now_yy,4); cal_month_echo($month,$now_yy,5); cal_month_echo($month,$now_yy,6); ?> </div> <div class="quarter"> <?php cal_month_echo($month,$now_yy,7); cal_month_echo($month,$now_yy,8); cal_month_echo($month,$now_yy,9); ?> </div> <div class="quarter"> <?php cal_month_echo($month,$now_yy,10); cal_month_echo($month,$now_yy,11); cal_month_echo($month,$now_yy,12); ?> </div> </body> </html>
解説
今回は素直にコードを書いたつもりですが、一応プログラムでポイントとなる点を・・・
対象年(西暦)
$now_yy = intval(trim($_GET['year']));
if ( $now_yy < 1902 || $now_yy > 2037 ){
$now_yy = date(Y);
}else if( empty($now_yy) ){
$now_yy = date(Y);
}
$prev_yy = $now_yy - 1;
$next_yy = $now_yy + 1;
プログラムの先頭で前年・当年・翌年の情報を取得しています。カレンダーを表示した直後は現在の年が当年となり、ブラウザから$_GETを介して指定された場合は指定された年の暦を表示するようになっています。
※今回は$_GETで得られる情報(西暦)について、適正な値で在るか否かのチェックはしていません。単に1902年~2037年の西暦で在るか否か、値が空かの判定をしていますがセキュリティ的には正規表現などでチェックした方が良いでしょう。
年間日付情報の取得
$month = array(); //配列初期化 for($i=1; $i<=12; $i++){ //1-12月 $m_last = date(j,mktime(0,0,0,$i+1,0,$now_yy)); //対象月:最終日・日付取得 for($j=1; $j<=$m_last; $j++){ //各月日数カウント $d = date(w,mktime(0,0,0,$i,$j,$now_yy)) ; //曜日取得 $month[$i][$j] = $d; } }
上記の部分だけで1年分の日付情報を多次元配列に格納しています。
年間、1~12月までの各月の月末日の日付と、各月1日の曜日が重要であり、その情報さえあれば後は for()文 で残りの日付情報を得る事ができます。
なので今回は、checkdate関数で正当な日付で在るか否かのチェックは不要となります。
多次元配列の確認
//echo $now_yy."\n";
//var_dump($month);
//exit;
この部分は多次元配列($month)に正しく値が格納されているか?確認の為に書いたモノです。3行のコメント指定を外せば生成した配列の中身を確認する事が出来ます。(直接プログラムの動作に影響しないので削除しても問題ありません)
配列処理:表示(ユーザー関数)
function cal_month_echo($arr,$y,$m){ $arr_m = $arr[$m]; echo <<<EOF <div class="calendar"> <table summary="Calendar{$y}/{$m}"> <tr><th colspan="7">{$m}</th></tr> <tr><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr>\n EOF; $num = count($arr_m); $wd_last = $arr_m[$num]; //月末曜日取得 foreach($arr_m as $k => $wd){ if($k == 1 && $wd != 0){ //余剰セル処理 echo "<tr>"; for($i=0; $i<$wd; $i++){ echo "<td>・</td>"; } } if($wd == 0){ echo "<tr>"; echo "<td class=\"sun\">".$k."</td>"; //sunday } if($wd != 0 && $wd != 6){ echo "<td>".$k."</td>"; //weekday } if($wd == 6){ echo "<td class=\"sat\">".$k."</td>"; //saturday echo "</tr>\n"; } } if($wd_last != 6){ for($j=$wd_last; $j<6; $j++){ //余剰セル処理 echo "<td>・</td>"; if($j == 5){ echo "</tr>\n"; } } } echo "</table>\n"; echo "</div>\n"; }
多次元配列から各月毎の情報を取り出して表示するユーザー関数の部分です。
配列から情報を取り出すのに必要なのは、対象となる配列($arr)と対象月の指定($m)だけで、西暦の情報($y)は不要なのですが、今回<table>タグの summary に年の情報を挿入したかったのでココで指定しています。別に変数:$now_yy をグローバル宣言しても良いのですが、自分は相当重要な変数で無い限り、なるべくグローバル宣言はしないようにしています。
多次元配列の一次配列キーがそのまま【月】になっているので、対象月を指定するだけで当該月が単純な一次配列($arr_m)として返されるカラクリです。
後は<table>の表示位置を考慮し<div>で括ってスタイルシートで見栄えを調整しています。
取り出した各月の配列を枡目のカレンダーに<table>タグを使って表示するのが少々面倒ですが、必須となる各条件を一つ一つ洗い出して行けばさほど難しくは無いと思います。
また、将来的にはHTML5が主流となるのは間違いないので<table>タグを使わない方法でカレンダーのレイアウト行えばスッキリとしたコードになると思います。
上記のユーザー関数では、その月の1日が日曜日以外なら余剰部分を【・】で埋めて、同様に月末が土曜日以外ならば余剰部分を【・】で埋めるようにしています。後は日曜日を示す[0]と土曜日を示す[6]が出たら<tr>および</tr>タグを発行(?)しています。PHPで用意された関数を使えばもっとスマートにできるかもしれませんが、後は各個の努力でゴニョゴニョ・・・
えっ?土曜日を青字にしたらリンクと間違える?それぐらいは自分でアレンジしておくんなまし・・・(´・ω・`)