CakePHP

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)のファイルについては後述します。

[default mark="9-11"]
├─[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
[/default]

(1) app/Vendor/include_path.php

PEAR Calendarへのパスを設定します。中身は以下の数行だけでOkです。

[php]
// Calendar
define('CALENDAR_ROOT', dirname(__FILE__) . DS .'PEAR' . DS . 'Calendar' . DS);
[/php]

祝日判定に必要なルーチンの作成

(2)(3)(4)は、日本の祝日判定ルーチンです。
戴いたか、どこかで公開されていたコードを修正とか追加とかしたような気がするのですが、全く思い出せません(えー
せっかくなので、知識の共有ということで公開してみます。
ちなみに、祝日法が改正されたり、天皇陛下が崩御されたりすると修正が必要になります。

ちと長いです。

(2) app/Vendor/HOLIDAY/holidays/ja/holidays.php

[php]
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; } } [/php]

(3) app/Vendor/HOLIDAY/holidays/abstract_holidays.php

祝日名取得呼び出しルーチン

[php]
abstract class AbstructHolidays extends Object
{
// @return array( day=>'name', .....)
// @example array( 10=>'hoge holiday1', 15=>'hoge holiday2')

abstract function getHolidayNames($year, $month);
}
[/php]

(4) app/Vendor/HOLIDAY/holiday.php

祝日名取得ルーチン

[php]
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; } } [/php] 以上で、必要な外部ファイルの設置が完了。

app/Controller/SchedulesController.php

ScheduleController を作り、とりあえずはカレンダーの表示をさせてみます。

app/Cntroller/SchedulesController.php

[php]
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);
}
}
[/php]

app/View/Schedules/index.ctp

最後にビューファイルを作成。

[php]
$Month = new Calendar_Month_Weekdays( $set_year, $set_month, 0 );
$Month->build();

//echo '';
echo '

';
echo $this->Html->link('≪前月', '/schedules/index/' . $last_year_month, array('title'=>'前月へ')) . "   ";
echo " ".'' . $this_year_month_jp . "    ";
echo $this->Html->link('次月≫', '/schedules/index/' . $next_year_month, array('title'=>'次月へ'));
echo '
' . $this->Html->link('今月に戻る', '/schedules/index/', array('title'=>'今月に戻る'));
echo '

'."\n\n";

//echo '';
echo '

'."\n";

echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";
echo '

'."\n";

echo '

'."\n";

// PEARカレンダー処理
while ( $Day = $Month->fetch() ) {

if ( $Day->isFirst() ) {
echo '

'."\n";
}

if ( $Day->isEmpty() ) {
echo '

'."\n";
} else {
// 曜日取得
$w = date('w', $Day->getTimestamp()); // 曜日(0:日曜?6:土曜)

echo '';
echo '

';
} elseif( $w == 6 ) {
echo ' class="sat">';
echo '

';
} else {
if ( isset($holidays[$Day->thisMonth()][$Day->thisDay()]) ) {
echo ' class="holiday">';
echo '

';
} else {
echo ' class="day">';
echo '

';
}
}

// 日付
echo sprintf("%02d", $Day->thisDay());
echo '

';

// 祝日
if ( isset($holidays[$Day->thisMonth()][$Day->thisDay()]) ) {
echo ' (' . $holidays[$Day->thisMonth()][$Day->thisDay()] . ')';
}

echo '

'."\n";

if ( $Day->isLast() ) {
echo '

'."\n\n";
}
}
} //while

echo '

'."\n";
echo '

 

'."\n";
echo '

'."\n";
//echo "\n\n";
[/php]

CSS

適当にCSSも作成します。
参考程度にどうぞ。

[css]
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;
}
[/css]

サンプル

以上で、このようなカレンダーが表示されます。

サンプルカレンダー 2013 05

サンプルカレンダー 2013 09

手順を覚えれば簡単にできそうですね。

[tgAmazonItemLookup asin="B0051R4ECM" related="1"]

-CakePHP
-, , ,