2番煎じどころか20番煎じくらいはいくと思うのですが、適当なライブラリを探すより作った方が早いと思い、作っちゃいました。すみません。
クラス数が多いですが、データ部分と描画部分(レンダラ)を分けたかったので、こうしました。祝日対応は敢えてしていません。calendar_day_rendererあたりに祝日のリストを持たせてrenderメソッド内で判定すればいいかなと。うるう年については多分strtotime神関数がなんとかしてくれるはず!←2038年問題により32bit環境でアウトでしたw
<?php
class calendar_day
{
private $date;
private $day;
public function __construct( $date )
{
$this->date = date( 'Y-m-d', $date );
$this->day = date( 'j', $date );
}
public function getDate()
{
return $this->date;
}
public function getDay()
{
return $this->day;
}
}
interface calendar_day_renderer
{
public function render( calendar_day $day );
}
class calendar_day_renderer_html implements calendar_day_renderer
{
public function render( calendar_day $day )
{
echo '<td align="right">' . $day->getDay() . '</td>' . PHP_EOL;
}
}
class calendar_day_renderer_shell implements calendar_day_renderer
{
public function render( calendar_day $day )
{
echo str_pad($day->getDay(), 2, " ", STR_PAD_LEFT) . '|';
}
}
class calendar_week
{
private $no;
private $week_start_date;
private $days;
public function __construct( $no, $week_start_date )
{
$this->no = $no;
$this->week_start_date = date( 'Y-m-d', $week_start_date );
for($i=0; $i<7; $i++){
$date = ($i === 0) ? $week_start_date : strtotime( "+{$i} days", $week_start_date );
$this->days[$i] =new calendar_day( $date );
}
}
public function getNo()
{
return $this->no;
}
public function getDay( $weekday )
{
return isset($this->days[$weekday]) ? $this->days[$weekday] : NULL;
}
}
interface calendar_week_renderer
{
public function render( calendar_week $week, calendar_day_renderer $day_renderer );
}
class calendar_week_renderer_html implements calendar_week_renderer
{
public function render( calendar_week $week, calendar_day_renderer $day_renderer )
{
echo '<tr>' . PHP_EOL;
echo '<td align="center">' . $week->getNo() . ':</td>' . PHP_EOL;
for($i=0; $i<7; $i++ ){
$day_renderer->render( $week->getDay($i) );
}
echo '</tr>' . PHP_EOL;
}
}
class calendar_week_renderer_shell implements calendar_week_renderer
{
public function render( calendar_week $week, calendar_day_renderer $day_renderer )
{
echo '|' . $week->getNo() . ':|';
for($i=0; $i<7; $i++ ){
$day_renderer->render( $week->getDay($i) );
}
echo PHP_EOL;
}
}
class calendar
{
private $weeks;
private $title;
public function __construct( $year, $month, $title = NULL )
{
$this->title = $title ? $title : date('Y/m', strtotime("$year/$month/01"));
$month_first_day = strtotime("{$year}-{$month}-01");
$total_days = date( 't', $month_first_day );
$start_weekday = date( 'w', $month_first_day );
// 最初の週
$week_start_date = strtotime( "-{$start_weekday} days", $month_first_day );
// 月末までの週を作成
$week_no = 1;
$next_month_first_day = strtotime("first day of next month", $month_first_day);
while( $week_start_date < $next_month_first_day ){
$this->weeks[] = new calendar_week( $week_no, $week_start_date );
$week_no ++;
$week_start_date = strtotime( "+7 days", $week_start_date );
}
}
public function getWeek( $index )
{
return isset($this->weeks[$index]) ? $this->weeks[$index] : NULL;
}
public function countWeeks()
{
return count($this->weeks);
}
public function getTitle()
{
return $this->title;
}
}
interface calendar_renderer
{
public function render( calendar $cal );
}
abstract class abstract_calendar_renderer implements calendar_renderer
{
private $day_renderer;
private $week_renderer;
public function __construct( calendar_day_renderer $day_renderer, calendar_week_renderer $week_renderer )
{
$this->day_renderer = $day_renderer;
$this->week_renderer = $week_renderer;
}
public function render( calendar $cal )
{
$this->renderHeader( $cal );
$weeks = $cal->countWeeks();
for( $i=0; $i<$weeks; $i++ ){
$this->week_renderer->render( $cal->getWeek($i), $this->day_renderer );
}
$this->renderFooter( $cal );
}
abstract public function renderHeader( calendar $cal );
abstract public function renderFooter( calendar $cal );
}
class calendar_renderer_html extends abstract_calendar_renderer
{
public function renderHeader( calendar $cal )
{
echo '<table border="1">' . PHP_EOL;
echo '<caption>' . $cal->getTitle() . '</caption>' . PHP_EOL;
echo '<tr><th>No</th><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr>' . PHP_EOL;
}
public function renderFooter( calendar $cal )
{
echo '</table>' . PHP_EOL;
}
}
class calendar_renderer_shell extends abstract_calendar_renderer
{
public function renderHeader( calendar $cal )
{
echo '--' . $cal->getTitle() . '--' . PHP_EOL;
echo '|No|日|月|火|水|木|金|土|' . PHP_EOL;
echo '+--+--+--+--+--+--+--+--+' . PHP_EOL;
}
public function renderFooter( calendar $cal )
{
echo '+--+--+--+--+--+--+--+--+' . PHP_EOL;
}
}
// 以下、使用方法
// 2013年9月~11月のカレンダー
$cal_9 = new calendar( 2013, 9 );
$cal_10 = new calendar( 2013, 10 );
$cal_11 = new calendar( 2013, 11 );
// カレンダーをHTMLで描画
$cal_renderer_html = new calendar_renderer_html( new calendar_day_renderer_html, new calendar_week_renderer_html );
$cal_renderer_html->render( $cal_9 );
$cal_renderer_html->render( $cal_10 );
$cal_renderer_html->render( $cal_11 );
// カレンダーをCLIで描画
echo '<pre>' . PHP_EOL;
$cal_renderer_shell = new calendar_renderer_shell( new calendar_day_renderer_shell, new calendar_week_renderer_shell );
$cal_renderer_shell->render( $cal_9 );
$cal_renderer_shell->render( $cal_10 );
$cal_renderer_shell->render( $cal_11 );
echo '</pre>' . PHP_EOL;
// 閏年テスト
$cal_leap_2013 = new calendar( 2013, 2 ); // 今年は平年
$cal_leap_2012 = new calendar( 2012, 2 ); // 去年は閏年
$cal_leap_2100 = new calendar( 2100, 2 ); // 100年で割り切れる年は平年
$cal_leap_2000 = new calendar( 2000, 2 ); // 400で割り切れる年は閏年
echo '<pre>' . PHP_EOL;
$cal_renderer_shell->render( $cal_leap_2013 );
$cal_renderer_shell->render( $cal_leap_2012 );
$cal_renderer_shell->render( $cal_leap_2100 );
$cal_renderer_shell->render( $cal_leap_2000 );
echo '</pre>' . PHP_EOL;
<pre>
--2013/09--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:| 1| 2| 3| 4| 5| 6| 7|
|2:| 8| 9|10|11|12|13|14|
|3:|15|16|17|18|19|20|21|
|4:|22|23|24|25|26|27|28|
|5:|29|30| 1| 2| 3| 4| 5|
+--+--+--+--+--+--+--+--+
--2013/10--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:|29|30| 1| 2| 3| 4| 5|
|2:| 6| 7| 8| 9|10|11|12|
|3:|13|14|15|16|17|18|19|
|4:|20|21|22|23|24|25|26|
|5:|27|28|29|30|31| 1| 2|
+--+--+--+--+--+--+--+--+
--2013/11--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:|27|28|29|30|31| 1| 2|
|2:| 3| 4| 5| 6| 7| 8| 9|
|3:|10|11|12|13|14|15|16|
|4:|17|18|19|20|21|22|23|
|5:|24|25|26|27|28|29|30|
+--+--+--+--+--+--+--+--+
</pre>
<pre>
--2013/02--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:|27|28|29|30|31| 1| 2|
|2:| 3| 4| 5| 6| 7| 8| 9|
|3:|10|11|12|13|14|15|16|
|4:|17|18|19|20|21|22|23|
|5:|24|25|26|27|28| 1| 2|
+--+--+--+--+--+--+--+--+
--2012/02--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:|29|30|31| 1| 2| 3| 4|
|2:| 5| 6| 7| 8| 9|10|11|
|3:|12|13|14|15|16|17|18|
|4:|19|20|21|22|23|24|25|
|5:|26|27|28|29| 1| 2| 3|
+--+--+--+--+--+--+--+--+
--1970/01--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:|28|29|30|31| 1| 2| 3|
|2:| 4| 5| 6| 7| 8| 9|10|
|3:|11|12|13|14|15|16|17|
|4:|18|19|20|21|22|23|24|
|5:|25|26|27|28|29|30|31|
+--+--+--+--+--+--+--+--+
--2000/02--
|No|日|月|火|水|木|金|土|
+--+--+--+--+--+--+--+--+
|1:|30|31| 1| 2| 3| 4| 5|
|2:| 6| 7| 8| 9|10|11|12|
|3:|13|14|15|16|17|18|19|
|4:|20|21|22|23|24|25|26|
|5:|27|28|29| 1| 2| 3| 4|
+--+--+--+--+--+--+--+--+
</pre>
使い方はcalendarオブジェクトとrendererオブジェクトを作って、renderすればOKです。xxx_renderer_htmlはrenderするとHTMLを出力し、xxx_renderer_shellはrenderするとコンソールに文字として出力します。
実行結果ですが、32ビット版PHPでは閏年に対応してません!(笑
これは、strtotimeの制限によるもので、マニュアルの注意事項にひっそりと書かれています。
PHPマニュアルのstrtotimeの項:
http://www.php.net/manual/ja/function.strtotime.php
2038年以降も32bitマシンを現役で動かしちゃうよ!ってエコな方は、PHP5.2から使用できるDateTimeを使ってカスタマイズすればいけると思います。
参考:
http://blog.asial.co.jp/704
補足:
strtotimeといえば冒頭で神関数とか言っちゃってますが、いろいろ問題もあるようです。
http://itpro.nikkeibp.co.jp/article/COLUMN/20070131/260224/
http://www3441ui.sakura.ne.jp/wordpress/?p=177
http://www.ahsd.net/phpstrtotime%E3%81%A7%E3%81%AE%E5%89%8D%E6%9C%88%E3%81%AE%E6%97%A5%E4%BB%98%E5%8F%96%E5%BE%97%E3%81%AF%E4%B8%8A%E6%89%8B%E3%81%8F%E3%81%84%E3%81%8B%E3%81%AA%E3%81%84/
strtotimeの使用は、使用上の注意をよく読み、用法・用量を守って正しくお使いください!