特定の日付の曜日を計算する

結構これでハマる人は多いみたいです。
以下のようにtimelocal.plや、Time::Localを使う方法も
あります(以下参照)が、

	use Time::Local;

	$time = timelocal(0, 0, 0, 1, $mon, $year);
	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
		localtime($time);
	print ('日', '月', '火', '水', '木曜', '金', '土')[$wday];

○実行効率が悪い(遅い)
○計算できる範囲がOSやTime::Localの制限を受ける
○(FreeBSD/perl5.004_04では1970年〜2037年頃まで)

*余談だけど昔のperl4のtimelocalのyearのところ
*に大きすぎる値(1999とか)を渡すと、core dumpし
*たり帰ってこなかったりするバグがあった。

などの点で汎用性は低いです。60年代生まれの人の誕生日の
曜日なんかはうまく計算できない場合が多いでしょう。

こういう場合はツェラー(Zellar)の公式が便利です。

	sub getweek{
		local($day, $year, $month) = @_;
		# $year  = 年; # もちろん4桁で。
		# $month = 月; # 1-12で。1月は1。

		if ($month == 1 || $month == 2) {
			$year--;
			$month += 12;
		}

		int(
			  $year
			+ int($year/4)
			- int($year/100)
			+ int($year/400)
			+ int((13*$month+8)/5)
			+ $day
		) % 7;
	}	

こんな感じで。

この&getweekは引数に西暦(4桁)と月(1から12)、日にちを取り、0〜6
の数字を返します。0が日曜で6が土曜になっています。

というわけで今年の1月初日の曜日を表示するなら、

print ('Sun', 'Mon', ..略.., 'Sat')[&getweek(1999, 1, 1)];

の様に使えばうまく行きます。

カレンダーも作れます。
1752年10月 〜 9999年12月までの間ならcalコマン
ドと同じ結果を出すと思います。

#!/usr/local/bin/perl

# コマンドライン引数をもらう
die "usage: cal month year\n" unless @ARGV == 2;
($mon, $year) = @ARGV;

# その月の末日を計算(2行目は閏年の計算)
$lastday = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[$mon - 1]
         + ($mon == 2 && ($year % 4 == 0 && $year % 100 != 0 || $year % 400 == 0));

print "      $year/$mon\n S  M Tu  W Th  F  S\n";

foreach(("  ") x &getweek($year, $mon), 1 .. $lastday){
    printf ('%2.2s ' , $_);
    print "\n" unless ++$days % 7;
}

print "\n"; exit;

# 曜日を得る関数
sub getweek{
    local($year, $month) = @_;
    if($month == 1 || $month == 2) {$year--;$month += 12;}
    int($year + int($year/4) - int($year/100) + int($year/400) + int((13*$month+8)/5) + 1) % 7;
}