カレンダーを作る

お仕事でカレンダープログラムを作ることになって、『簡単CGI: カレンダーを作ろう』なんかを参考にとりあえずクラスを作る。(長いので)一部削除しているが、公開されているものを参考にさせていただいたので、感謝を込めて肝のところだけ私も公開。

package Calendar;
# Copyright(c) 2005, Tetsuya TOYODA.
# mailto: altair@dream.big.jp
#
# This program is free software;
# you may redistribute it and/or modify it
# under the same terms as Perl itself.

use strict;

sub new {
	my $obj = shift;
	my %hash = @_;
	
	my( $year, $month ) = ( gmtime ( time + 9 * 60 * 60 ) )[5,4];
	$year += 1900;
	$month += 1;
	
	my $start_wday = 0;
	my $blank = '';
	
	$year = $hash{'year'} if $hash{'year'};
	$month = $hash{'month'} if $hash{'month'};
	$start_wday = $hash{'start_wday'} if $hash{'start_wday'};
	$blank = $hash{'blank'} if $hash{'blank'};
	
	bless {
		year	=> $year,
		month	=> $month,
		start_wday	=> $start_wday,
		blank	=> $blank
		}, $obj;
}

sub output {
	my $self = shift;
	
	my $table;
	
	my @last_days = (31,28,31,30,31,30,31,31,30,31,30,31);
	my $year = $self->{year};
	my $month = $self->{month};
	
	if(
		( ($year % 4 == 0 ) && ( $year % 100 != 0 ) )
		|| ( $year % 400 == 0 )
		){
	    $last_days[1]=29;
	}
	
	my $start = _wday($year,$month,1);
	my $last_day = $last_days[$month-1];
	
	my $col = 0;
	my $row = 0;
	my $day = 0;
	
	$table->[0]->{num} = 1;
	
	while( $start-- ){
		$table->[$row]->{week}->[$col++] = {
			day	=> $self->{blank}
			};
	}
	
	while( $last_day-- ){
		$table->[$row]->{week}->[$col++]->{day} = ++$day;
		
		if ( $col >= 7 ){
			$row++;
			$col = 0;
		}
	}
	
	if( $col ){
		my $tail = 7 - $col;
		while( $tail-- ){
			$table->[$row]->{week}->[$col++] = {
				day	=> $self->{blank}
				};
		}
	}
	
	return $table;
}

sub year {
	my $self = shift;
	my $year = shift;
	
	$self->{year} = $year if $year;
	
	return $self->{year};
}

sub month {
	my $self = shift;
	my $month = shift;
	
	$self->{month} = $month if $month;
	
	return $self->{month};
}

sub start_wday {
	my $self = shift;
	my $start_wday = shift;
	
	$self->{start_wday} = $start_wday if $start_wday;
	
	return $self->{year};
}

sub blank {
	my $self = shift;
	my $blank = shift;
	
	$self->{blank} = $blank if $blank;
	
	return $self->{blank};
}

### ### ### ### ###

sub _wday {
	my ( $year, $month, $day ) = @_;
	if ( $month < 3 ){ $month += 12; $year--; }
	return (
		$year + int ( $year / 4 ) - int( $year / 100 )
		 + int ( $year / 400 )
		 + int ( ( 13 * $month + 8 ) / 5 ) + $day
		) % 7;
}

1;

outputメソッドを実行すると、カレンダーを構造体として得ることが出来る。そのままテンプレートシステムに渡せばテーブルになる。例えば、HTML::Templateなら以下のようなテンプレートだろうか。

<table>
	<tr>
		<th>SUN</th>
		<th>MON</th>
		<th>TUE</th>
		<th>WED</th>
		<th>THU</th>
		<th>FRI</th>
		<th>SAT</th>
	</tr>
	<TMPL_LOOP month>
	<tr>
		<TMPL_LOOP week>
		<td>
			<div class="day"><TMPL_VAR day></div>
		</td>
		</TMPL_LOOP>
	</tr>
	</TMPL_LOOP>
</table>

例えば次のように使う。

use Calendar;
use HTML::Template;

# Calendarのインスタンスを得る
my $cal = new Calendar;

# HTML::Templateのインスタンスを得る
# テンプレートはcal.tmpl
my $tmpl = new HTML::Template
	filename => 'cal.tmpl';

# カレンダーの空欄部に入れる文字列を設定
$cal->blank('&nbsp;');

# テンプレートに構造体をセット
$tmpl->param( month => $cal->output );

# 出力
print "Content-Type: text/html;charset=UTF-8?n?n";
print $tmpl->output;

exit;