CakePHP2.xで、PEARのCalendarを使ってカレンダーを作成してみる。
2015/01/11
カレンダーを利用した、スケジュール管理系のページが必要になったので、PEARのCalendarを使って作成することにしました。
以前(5年ぐらい前)既に作ったことがあるので、そのソースを蔵出ししつつ、CakePHP2.x系(2.3.6)にて作成する方法を忘備録を兼ねたポストで紹介したいと思います。
ライブラリのダウンロードと設置
本体、マニュアル
Package Information: Calendar
http://pear.php.net/package/Calendar/download
Version: 0.5.5。
Release date: 2010-06-24 13:46 UTC
手元にあったのは0.5.4。
あんまりというか全然アップデートされていませんな。
マニュアルはコチラ。
日本語で読みやすいです。
Manual :: Calendar
http://pear.php.net/manual/ja/package.datetime.calendar.php
祝日判定
せっかくなので、PEAR の Date_Holidays_Japan を使い、日本の祝日判定もするようにしてみます。
Date_Holidays_Japan
http://pear.php.net/package/Date_Holidays_Japan
ダウンロードはコチラから
http://pear.php.net/package/Date_Holidays_Japan/download
設置
まずは、ダウンロードした Calendar-0.5.5.tgz 及び、Date_Holidays_Japan-0.1.2.tgz を、app/Vendorに展開。
Vendor/PEAR、Vendor/HOLIDAY ディレクトリは新規作成して下さい。
展開後はこんな感じで。
(2)(3)(4)のファイルについては後述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
├─[app] ├─[Controller] │ └─SchedulesController.php │ ├─[Vendor] │ ├─[HOLIDAY] │ │ ├─[holidays] │ │ │ ├─[ja] │ │ │ │ └─holidays.php←(2) │ │ │ └─abstract_holidays.php←(3) │ │ └─holiday.php←(4) │ │ │ ├─[PEAR] │ │ ├─[Calendar] │ │ │ ├─略 │ │ │ │ │ └─[Date_Holidays_Japan] │ │ ├─略 │ │ │ └─include_path.php←(1) │ └─[View] └─[Schedules] └─index.ctp |
(1) app/Vendor/include_path.php
PEAR Calendarへのパスを設定します。中身は以下の数行だけでOkです。
1 2 |
// Calendar define('CALENDAR_ROOT', dirname(__FILE__) . DS .'PEAR' . DS . 'Calendar' . DS); |
祝日判定に必要なルーチンの作成
(2)(3)(4)は、日本の祝日判定ルーチンです。
戴いたか、どこかで公開されていたコードを修正とか追加とかしたような気がするのですが、全く思い出せません(えー
せっかくなので、知識の共有ということで公開してみます。
ちなみに、祝日法が改正されたり、天皇陛下が崩御されたりすると修正が必要になります。
ちと長いです。
(2) app/Vendor/HOLIDAY/holidays/ja/holidays.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
App::import('Vendor', 'AbstructHolidays', array('file' => 'HOLIDAY'.DS.'holidays'.DS.'abstract_holidays.php')); class Holidays extends AbstructHolidays { var $names = array(); function getHolidayNames($year, $month) { $_this = new Holidays(); $_this->setHolidays($year, $month); if(!empty($_this->names)) { for($day = 1; $day < date('t',mktime(0,0,0,$month,1,$year)); $day++) { $time = mktime(0,0,0,$month,$day,$year); if (date('w', $time) == 0 ) { // 日曜以外は振替休日判定不要 if(!empty($_this->names[$day]) && empty($_this->names[$day+1])) { // 振替休日施行 if ($time >= mktime(0,0,0,4,12,1973)) { $_this->names[$day+1] = "振替休日"; } } } } } return $_this->names; } private function setHolidays($year, $month) { $mon2 = $this->secondMonday($year, $month); $mon3 = $this->thirdMonday($year, $month); if(($year <= 1948) && ($month <= 7)) { return $this->names; // 祝日法施行(1948/7/20 )以前 } else { switch ( $month ) { case 1: $this->names[1] = "元日"; if ( $year >= 2000 ) { $this->names[$mon2] = "成人の日"; } else { $this->names[15] = "成人の日"; } break; case 2: if ( $year >= 1967 ) { $this->names[11] = "建国記念の日"; } if ( $year == 1989 ) { $this->names[24] = "昭和天皇の大喪の礼"; } break; case 3: $day = $this->prvDayOfSpringEquinox( $year ); if ( $day ) { $this->names[$day] = "春分の日"; } break; case 4: if ($year >= 2007) { $this->names[29] = "昭和の日"; } else { if ( $year >= 1989 ) { $this->names[29] = "みどりの日"; } else { $this->names[29] = "天皇誕生日"; } } if ( $year == 1959 ) { $this->names[10] = "皇太子明仁親王の結婚の儀"; // ( =1959/4/10 ) } break; case 5: $this->names[3] = "憲法記念日"; if ($year >= 2007) { $this->names[4] = "みどりの日"; } else { if ($year >= 1986) { if (date('w', mktime(0,0,0,5,4,$year)) > 1) { // 5/4が日曜日は『只の日曜』、月曜日は『憲法記念日の振替休日』(?2006年) $this->names[4] = "国民の休日"; } } } $this->names[5] = "こどもの日"; if ($year >= 2007) { if (date('w',mktime(0,0,0,5,6,$year)) == 2 || date('w',mktime(0,0,0,5,6,$year)) == 3) { $this->names[6] = "振替休日"; // [5/3,5/4が日曜]ケースのみ、ここで判定 } } break; case 6: if ( $year == 1993 ) { $this->names[9] = "皇太子徳仁親王の結婚の儀"; } break; case 7: if ( $year >= 2003 ) { $this->names[$mon3] = "海の日"; } else { if ( $year >= 1996 ) { $this->names[20] = "海の日"; } } break; case 9: //第3月曜日( 15?21 )と秋分日(22?24 )が重なる事はない $day = $this->prvDayOfAutumnEquinox( $year ); if ( $day ) { $this->names[$day] = "秋分の日"; } if ( $year >= 2003 ) { $this->names[$mon3] = "敬老の日"; if (date('w',mktime(0,0,0,9,$day,$year)) == 3) { $this->names[$day-1] = "国民の休日"; } } else { if ( $year >= 1966 ) { $this->names[15] = "敬老の日"; } } break; case 10: if ( $year >= 2000 ) { $this->names[$mon2] = "体育の日"; } else { if ( $year >= 1966 ) { $this->names[10] = "体育の日"; } } break; case 11: $this->names[3] = "文化の日"; $this->names[23] = "勤労感謝の日"; if ( $year == 1990 ) { $this->names[12] = "即位礼正殿の儀"; } break; case 12: if ( $year >= 1989 ) { $this->names[23] = "天皇誕生日"; } break; } return $this->names; } } private function secondMonday($year, $month) { $w = date('N', mktime(0,0,0,$month,1,$year)); switch($w) { case 1 : return 8; default : return 14 - ($w - 2); // 9?14 } } private function thirdMonday($year, $month) { $w = date('N', mktime(0,0,0,$month,1,$year)); switch($w) { case 1 : return 15; default : return 21 - ($w - 2); // 16?21 } } // 春分/秋分日の略算式は // 『海上保安庁水路部 暦計算研究会編 新こよみ便利帳』 // で紹介されている式です。 private function prvDayOfSpringEquinox($year) { $springEquinox_ret = false; if ($year <= 1947) { $springEquinox_ret = false; // 祝日法施行前 } else { if ($year <= 1979) { $springEquinox_ret = intval(20.8357 + (0.242194 * ($year - 1980)) - intval(($year - 1983) / 4)); } else { if ($year <= 2099) { $springEquinox_ret = intval(20.8431 + (0.242194 * ($year - 1980)) - intval(($year - 1980) / 4)); } else { if ($year <= 2150) { $springEquinox_ret = intval(21.851 + (0.242194 * ($year - 1980)) - intval(($year - 1980) / 4)); } else { $springEquinox_ret = false; // 2151年以降は略算式が無いので不明 } } } } return $springEquinox_ret; } private function prvDayOfAutumnEquinox($year) { $autumnEquinox_ret = false; if ($year <= 1947) { $autumnEquinox_ret = false; // 祝日法施行前 } else { if ($year <= 1979) { $autumnEquinox_ret = intval(23.2588 + (0.242194 * ($year - 1980)) - intval(($year - 1983) / 4)); } else { if ($year <= 2099) { $autumnEquinox_ret = intval(23.2488 + (0.242194 * ($year - 1980)) - intval(($year - 1980) / 4)); } else { if ($year <= 2150) { $autumnEquinox_ret = intval(24.2488 + (0.242194 * ($year - 1980)) - (int) (($year - 1980) / 4)); } else { $autumnEquinox_ret = false; // 2151年以降は略算式が無いので不明 } } } } return $autumnEquinox_ret; } } |
(3) app/Vendor/HOLIDAY/holidays/abstract_holidays.php
祝日名取得呼び出しルーチン
1 2 3 4 5 6 7 |
abstract class AbstructHolidays extends Object { // @return array( day=>'name', .....) // @example array( 10=>'hoge holiday1', 15=>'hoge holiday2') abstract function getHolidayNames($year, $month); } |
(4) app/Vendor/HOLIDAY/holiday.php
祝日名取得ルーチン
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class HolidayUtil extends Object { function getHolidayNames($from, $to) { $locale = 'ja'; $find = App::import('Vendor', 'Holidays', array('file' => 'HOLIDAY' . DS . 'holidays' . DS . $locale . DS . 'holidays.php')); $holidays = array(); if ( $find ) { $time = $from; while (date('Ym',$time) <= date('Ym',$to)) { $year = date('Y', $time); $month = date('n', $time); $holidays[$month] = Holidays::getHolidayNames($year, $month); $time = mktime(0,0,0,$month+1,1,$year); } } else { $holidays = array(); } return $holidays; } } |
以上で、必要な外部ファイルの設置が完了。
app/Controller/SchedulesController.php
ScheduleController を作り、とりあえずはカレンダーの表示をさせてみます。
app/Cntroller/SchedulesController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
App::uses('Sanitize', 'Utility'); App::import('Vendor', 'include_path'); App::import('Vendor', 'Calendar_Month_Weekdays', array('file' => 'PEAR' . DS . 'Calendar' . DS . 'Month' . DS . 'Weekdays.php')); App::import('Vendor', 'HolidayUtil', array('file' => 'HOLIDAY' . DS . 'holiday.php')); class SchedulesController extends AppController { /** * */ public function beforeFilter() { // 親クラスのbeforeFilterの読み込み parent::beforeFilter(); } /** * カレンダー表示 * */ public function index( $set_year=null, $set_month=null ) { // titleタグ $this->set('title_for_layout', 'カレンダー表示'); //------------------------------------------------------------ // 年月の指定 //------------------------------------------------------------ // 指定なしの場合は本日の年月を取得 if ( $set_year == null || $set_month == null ) { $set_year = date('Y'); $set_month = date('m'); } if ( !is_numeric($set_year) || !is_numeric($set_month) ) { $error_message = '指定の年月が不正です。'; throw new InternalErrorException($error_message); // 500 Internal Server Error を返す } $this->set('set_year', $set_year); $this->set('set_month', $set_month); //------------------------------------------------------------ // 指定月の各タイムスタンプ作成・祝日取得 //------------------------------------------------------------ // 指定月の初日タイムスタンプ $times['from_time'] = strtotime($set_year . "-" . $set_month . "-1 0:0:0"); // 指定月の最終日タイムスタンプ $times['to_time'] = strtotime($set_year . "-" . $set_month . "-" . date("t", $times['from_time']) . " 23:59:59"); // 祝日取得 $holidays = HolidayUtil::getHolidayNames($times['from_time'], $times['to_time']); $this->set('times', $times); $this->set('holidays', $holidays); //------------------------------------------------------------ // 指定月の日付配列 //------------------------------------------------------------ $days = array(); for ( $i=1; $i<=date("d", $times['to_time']); $i++ ) { $days[$i] = $i; } //------------------------------------------------------------ // 取得した年月、次月、前月をYYYY/MM形式で変数にセット //------------------------------------------------------------ $next_year_month = date("Y/m", mktime(0, 0, 0, $set_month+1, 1, $set_year)); $this_year_month = date("Y/m", mktime(0, 0, 0, $set_month, 1, $set_year)); $this_year_month_jp = date("Y年m月", mktime(0, 0, 0, $set_month, 1, $set_year)); $last_year_month = date("Y/m", mktime(0, 0, 0, $set_month-1, 1, $set_year)); // 月 $this_month = date("n", mktime(0, 0, 0, $set_month, 1, $set_year)); // 本日 $today = date("Y-m-d", time()); $this->set('next_year_month', $next_year_month); $this->set('this_year_month', $this_year_month); $this->set('this_year_month_jp', $this_year_month_jp); $this->set('last_year_month', $last_year_month); $this->set('last_year_month', $last_year_month); $this->set('today', $today); } } |
app/View/Schedules/index.ctp
最後にビューファイルを作成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
$Month = new Calendar_Month_Weekdays( $set_year, $set_month, 0 ); $Month->build(); //echo '<!-- ▼表示月、前・次月リンク -->'; echo '<div class="calendar_header">'; echo $this->Html->link('≪前月', '/schedules/index/' . $last_year_month, array('title'=>'前月へ')) . " "; echo " ".'<span class="this_month">' . $this_year_month_jp . "</span> "; echo $this->Html->link('次月≫', '/schedules/index/' . $next_year_month, array('title'=>'次月へ')); echo '<br />' . $this->Html->link('今月に戻る', '/schedules/index/', array('title'=>'今月に戻る')); echo '</div>'."\n\n"; //echo '<!-- ▼calendar -->'; echo '<div id="normal_calendar">'."\n"; echo '<table border="0" id="scheduleTable">'."\n"; echo '<thead>'."\n"; echo '<tr>'."\n"; echo '<th>日</th>'."\n"; echo '<th>月</th>'."\n"; echo '<th>火</th>'."\n"; echo '<th>水</th>'."\n"; echo '<th>木</th>'."\n"; echo '<th>金</th>'."\n"; echo '<th>土</th>'."\n"; echo '</tr>'."\n"; echo '</thead>'."\n"; echo '<tbody>'."\n"; // PEARカレンダー処理 while ( $Day = $Month->fetch() ) { if ( $Day->isFirst() ) { echo '<tr>'."\n"; } if ( $Day->isEmpty() ) { echo '<td class="empty"> </td>'."\n"; } else { // 曜日取得 $w = date('w', $Day->getTimestamp()); // 曜日(0:日曜?6:土曜) echo '<td'; if ( $w == 0 ) { echo ' class="sun">'; echo '<p class="sun">'; } elseif( $w == 6 ) { echo ' class="sat">'; echo '<p class="sat">'; } else { if ( isset($holidays[$Day->thisMonth()][$Day->thisDay()]) ) { echo ' class="holiday">'; echo '<p class="sun">'; } else { echo ' class="day">'; echo '<p class="date">'; } } // 日付 echo sprintf("%02d", $Day->thisDay()); echo '</p>'; // 祝日 if ( isset($holidays[$Day->thisMonth()][$Day->thisDay()]) ) { echo ' (' . $holidays[$Day->thisMonth()][$Day->thisDay()] . ')'; } echo '</td>'."\n"; if ( $Day->isLast() ) { echo '</tr>'."\n\n"; } } } //while echo '</tbody>'."\n"; echo '</table>'."\n"; echo '</div>'."\n"; //echo "<!-- ▲calendar -->\n\n"; |
CSS
適当にCSSも作成します。
参考程度にどうぞ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
div#normal_calendar td{ padding: 2px; } /* 曜日ヘッダ */ div#normal_calendar thead th{ border-bottom: 2px solid #333; background-color: #e1e9e9; font-size: 12px; text-align: center; width: 14%; } /* 空白日セル */ div#normal_calendar td.empty{ background-color: #f8f8f8; } /* 日付セル */ div#normal_calendar td.day, div#normal_calendar td.sat, div#normal_calendar td.sun{ vertical-align: top; } /* 日付セル 曜日(日・祝) */ div#normal_calendar .sun, div#normal_calendar .holiday{ color: #fc0000; background-color: #ffebeb; } /* 日付セル 曜日(土曜) */ div#normal_calendar .sat{ color: #2a5fc9; background-color: #cdf0f0; } /* 日付帯 */ div#normal_calendar p.date{ margin-bottom: 5px; background-color: #eaeaea; border: 1px solid #ccc; text-align: center; } /* 日付帯(土曜) */ div#normal_calendar p.sat{ margin-bottom: 5px; background-color: #b1dce8; border: 1px solid #9fcae8; text-align: center; } /* 日付帯(日・祝) */ div#normal_calendar p.sun{ margin-bottom: 5px; background-color: #ffd2d1; border: 1px solid #fc9688; text-align: center; } div#normal_calendar p.name{ padding: 0 5px; font-size: 90%; border-bottom: 1px solid #ccc; } div#normal_calendar p.editicon{ margin-top: 10px; } |
サンプル
以上で、このようなカレンダーが表示されます。
手順を覚えれば簡単にできそうですね。
[tgAmazonItemLookup asin="B0051R4ECM" related="1"]