ソースコードで学ぶWebプログラミング

[Perl]カレンダーの作り方

カレンダーは、ブログの更新状況から営業スケジュールの表示、予約受付フォームの表示など用途は多く、広く使われています。今回はPerlで、実務でも必要になることが多いカレンダーを作成してみます。

Perlでカレンダーを作る方法

Webプログラムでカレンダーを表示するには、どのような機能が必要となるか洗い出してみます。まずカレンダーは、年と月が特定されて初めて表示できるものです。そこで年と月をブラウザから指定できる構成にします。

またカレンダー上には、第一週や最終週に前月や翌月の日付も表示されるものがあります。今回は、この機能も実装してみます。

1.データ「年、月」を受け取る ・GETリクエストで受け取る 2.データ「年、月」を表示データ「日付リスト」に変換する ・表示データは、曜日ごとのカレンダー形式で表示する ・表示データには、前月の日付、翌月の日付も含める 3.表示データを表示する ・カレンダーは、日曜から表示する ・土曜、日曜、祝祭日で色を変えて表示する

Perlで月の日付の配列を作成する

カレンダー表示で必要となるデータは、カレンダーに表示する「日付リスト」です。これは指定した月については、月の日数が分かれば、求めることが出来ます。また第一週に含まれる前月分の日付リストは、月初1日の曜日から算出できそうです。同様に最終週に含まれる翌月分の日付リストも、月末の曜日から算出できそうです。

処理するデータは「年、月」は、ブラウザから受け取る必要があります。デコード用のモジュール CgiDecode.pmを使うので、こちらからダウンロードして下さい。

#!/usr/bin/perl use strict; use warnings; use Date::Calc qw(Days_in_Month Day_of_Week); require 'CgiDecode.pm'; CgiDecode->new; # our()で使えるように our(%_GET,%_POST,%_COOKIE,@_FILES); # GET送信から受け取る関数 sub gGET { my($key,$def) = @_; $def = '' unless defined $def; return exists $_GET{$key} ? $_GET{$key}: $def; } # タイムゾーンの設定 $ENV{'TZ'} = 'JST-9'; # 現在日時 my($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime; # 年の指定 初期値を当年に my $y = gGET('year',$year+1900); # 月の指定 初期値を当月に my $m = gGET('month',$mon+1); # 指定年月の月の日数 my $t1 = Days_in_Month($y,$m); # 指定年月の月初曜日 my $w1 = Day_of_Week($y,$m,1); # 指定年月の前月の日数 my($_y,$_m) = $m < 2 ? ($y-1,12): ($y,$m-1); my $t0 = Days_in_Month($_y,$_m); # 指定年月の月末曜日 my $w2 = Day_of_Week($y,$m,$t1);

Perlでは、Date::CalcモジュールのDays_in_Month(年,月)で該当する月の日数を、Day_of_Week(年,月,日)で該当する日の曜日を取得することが出来ます。

曜日は1-7の数値になっていて、1が月曜で7が日曜になります。週は7日あり、月初1日の曜日が土曜(6)の時、第一週の前月分の日付は、日曜スタートのカレンダーでは6日あります。日曜(7)の場合は、0日になります。日曜が0であれば計算しやすいので、0にします。

また前月の月末日が31日だった場合、6日分のリストは、26,27,28,29,30,31になりますが、開始日を求める算出式としては 31-土曜(6) +1=26 で1足せば求めることが出来ます。

# 日曜の場合0に $w1 = 0 if $w1==7; $w2 = 0 if $w2==7; # 月の第一週に含まれる前月の開始日を算出 my $st = $t0-$w1 +1; # 前月の第一週分の日付を生成 my @days = $t0<$st ? (): ($st..$t0); # 指定年月の日付を生成 my @d1 = (1..$t1); # 月の最終週に含まれる翌月の最終日を算出 my $end = 7 -($w2+1); # 翌月の最終週分の日付を生成 my @d2 = 0<$end ? (1..$end): (); # 生成した日付を一つの配列に統合 @days = (@days,@d1,@d2);

Perlでは、(開始..終了)で指定した範囲の値の配列を作成できます。最終週に含まれる翌月の日付は、月末の曜日が土曜(6)の時、0日で、日曜(7→0)の時、6日あります。よって、7 - (曜日+1) で求めることが出来ます。

# 配列を分割して2次元配列にする関数 sub chunk { my $a = shift; my $n = shift; my @r = (); while (@$a) { # spliceは配列を変更する 破壊的 my @c = splice(@$a,0,$n); push @r, \@c; } return @r; } @days = chunk(\@days,7);

日付リストはカレンダー表示の場合、7日毎に分けてあれば表示する際に処理しやすいです。Perlでは、配列を分割する関数は標準ではないため、作成しています。またPerlでは、配列の要素には文字列、数値、リファレンスしか代入できないため、配列のリファレンスを入れています。

Perlで祝祭日を設定する

祝祭日を算出するアルゴリズムを構築する方法もありますが、今回は休業日などに対応するため、配列に設定するかたちで実装します。

# 休日を判定する関数 sub is_holiday { my $date = shift; # 2019年分 my @set = ( # 祝祭日 '20190101', '20190114', '20190211', '20190321', '20190429', '20190503', '20190504', '20190505', '20190506', '20190715', '20190811', '20190812', '20190916', '20190923', '20191014', '20191103', '20191104', '20191123', '20191223', # 休業日 '20190102', '20191230', '20191231', ); return 0 < (grep {$_ eq $date} @set); }

Perlで日付リストをjsonデータとして返す

javascriptで動的に表示を切り替えたい場合は、jsonデータで渡すことになります。Perlでは、JSONモジュールのencode_jsonで配列や連想配列をjson形式に変換することが出来ます。

# JSONデータに変換して表示 use JSON qw(encode_json); print "Content-Type: application/json; charset=utf-8\n\n"; print encode_json \@days;

今回は、ブラウザで年月の指定も行いたいため、jsonデータをtextarea内に出力する構成で作成します。土曜は青色、日曜は赤色の文字色にし、前月、翌月の日付は灰色で表示する予定としてcssも作成しています。

# 表示に使用する変数 my($html,$r1,$r2) = ('','',''); # htmlデータで出力 if (gGET('type') eq 'html') { # jsonデータで出力 } else { $html = '<textarea>'. (encode_json \@days) .'</textarea>'; $r1 = ' checked'; $r2 = ''; } print <<HTML; <title>[Perl]カレンダーを表示</title> <form> <input type="number" name="year" min="1" max="3000" value="$y">年 <input type="number" name="month" min="1" max="12" value="$m">月 <label> <input type="radio" name="type" value="json"$r1>JSONデータ </label> <label> <input type="radio" name="type" value="html"$r2>HTMLデータ </label> <input type="submit" value="送信"> </form> HTML print $html; print <<HTML; <style> form,textarea,table { display: block; margin: 30px auto; width: 70%; } input { margin-right: 5px; padding: 5px; } th,td { text-align: center; } .gray { color: #aaa; } .red { color: #f00; } .blue { color: #00f; } </style> HTML

Perlで日付リストをHTMLデータとして返す

最後にHTMLの状態で表示できるようにしてみます。日付リストは、週ごとの配列になっているので、第一週の時に出現した1日を基準として、前月分の日付を判定できます。また最終週に含まれる翌月の日付も1日を基準にして判定可能です。

日付リストの曜日は、週ごとの配列の1番目が日曜で、配列の添字0、最後が土曜で配列の添字は6です。下記のコードでは、$i が週ごとの配列の添字、$j が日付の配列の添字になっています。

# htmlデータで出力 if (gGET('type') eq 'html') { $html = '<table> <tr> <th class="red">日</th> <th>月</th> <th>火</th> <th>水</th> <th>木</th> <th>金</th> <th class="blue">土</th> </tr>'; # 文字色の初期値を灰色に my $cls = 'gray'; foreach my $i (0..$#days){ $html .= '<tr>'; my @w = @{$days[$i]}; foreach my $j (0..$#w){ my $v = $w[$j]; # 第一週の時 if ($i==0) { # 日付が1日 if ($v==1) { $cls = ''; } } else { # 第二週以降で日付が1日 if ($v==1) { $cls = 'gray'; } } # 初期色以外の時 if ($cls ne 'gray') { if ($j==0) { $cls = 'red'; } elsif($j==6) { $cls = 'blue'; } else { $cls = ''; } } $html .= '<td'.($cls eq ''?'':' class="'.$cls.'"').'>'.$v.'</td>'; } $html .= '</tr>'; } $html .= '</table>'; $r1 = ''; $r2 = ' checked';

さらに作成した休日判定の関数を利用すれば、休業日だけ色を変えることも出来ます。注意点として、休日は年月日の8文字で設定したので、利用する場合は、sprintf()などで日付を整形してから引数として渡す必要があります。