何番煎じかわからないけどPHPでカレンダー

　２番煎じどころか２０番煎じくらいはいくと思うのですが、適当なライブラリを探すより作った方が早いと思い、作っちゃいました。すみません。
　クラス数が多いですが、データ部分と描画部分（レンダラ）を分けたかったので、こうしました。祝日対応は敢えてしていません。calendar_day_rendererあたりに祝日のリストを持たせてrenderメソッド内で判定すればいいかなと。うるう年については多分strtotime神関数がなんとかしてくれるはず！←2038年問題により32bit環境でアウトでしたｗ

```php:calendar.php

<?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;

```

```:実行結果（HTMLは省略）

<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

補足：
ｓｔｒtotimeといえば冒頭で神関数とか言っちゃってますが、いろいろ問題もあるようです。

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の使用は、使用上の注意をよく読み、用法・用量を守って正しくお使いください！