休日のチェック
仕事をしていると、特定の暦日が休日かどうかをチェックしなければならないこと が多いのですが、単なる曜日のチェックなら、「Zeller の公式」(注1)を使うの が簡単で、awk で書くと、次のようになります。
awk '
BEGIN {
  split("日 月 火 水 木 金 土", w)
  prinf "年 月 日 >"
  while (getline) {
	y = $1; m = $2; d = $3	# y, m, d は、年月日

	# Zeller の公式(変形)
	if (m < 3) {
		y--; m += 12
	}
	d = y + int(y/4) - int(y/100) + int(y/400) + int(2.6*m + 1.6) + d

	print "\t", w[(d % 7) + 1], "曜日"
	prinf "年 月 日 >"
  }
  exit 0
}
'
例えば、このプログラムを起動して、「1996 12 7」と入力すると、土曜日である ことがわかりますが、通算日付を計算するための「Zeller の公式」のアイデアは、 月の日数の不規則性に対して、
1) 30 日と 31 日の差は浮動小数点演算を整数化することで吸収する。
2) 2 月の例外については、1 年が 3 月から始まると見なすことで対処する。
の2点にあります。つまり、「1 月と 2 月は、それぞれ、前年の 13, 14 月と見 なす!」というひらめきです。

後は、「グレオリオ暦」の原理である、地球の公転周期(日数)の有理数近似、

365.2422 ≒ 365 + 1/4 - 1/100 + 1/400
つまり、「閏年」(注2)の規則と組み合わせるだけで、なんとも綺麗な解が得られ ます。

しかし、自動化などでよく必要になる、休日かどうかの判定では、稼働日と休業日 が不規則に配置されますから、「式」による対処は無理で、どこかで「表」を引く 操作が必要になります。

プログラミングの場合は、この表のデータ構造と表を引くアルゴリズムをどう実現 するかが鍵になりますが、私が今までに気づいたものの中では、次のような sh ス クリプトがベストではないかと思います。

if grep `date +%m%d` <<!
0101 0102 0103 0105 0112 0119 0126
0202 0209 0216 0223
..
1207 1214 1221 1228
!
then
  休日にしなければならない仕事
else
  稼働日にしなければならない仕事
fi
表を引く操作を「grep」による検索として実現したのと、表を「here document」と して実現したのがミソですが、「here document」のリダイレクトを「if」文全体に 対して行うことも可能で、その場合は、次のようになります。
if grep `date +%m%d`
then
  休日にいなければならない仕事
else
  稼働日にしなければならない仕事
fi <<!
0101 0102 0103 0105 0112 0119 0126
0202 0209 0216 0223
..
1207 1214 1221 1228
!

なお、暦日の計算では、曜日だけでなく、年を越えた通算日が必要になることが多く、 その場合は天文学分野で使われている 「Julian Day」(ユリウス日 - 紀元前 4713 年 1 月 1 日 12 時 UT からの日数) あるいは、「Modified Julian Day」(準ユリウス日 = Julian Day - 2400000.5、 つまり、1858 年 11 月 17 日 0 時 UT が起点) が便利です。

この Modified Julian Day を年月日からもとめる方法を、 2000 年前後で使うという前提にもとに、 awk の関数で書けば、次のようになります。

function mjd(y, m, d) {	# y = 年, m = 月, d = 日
  return d+int(1461*(y+4800+int((m-14)/12))/4) \
	+int(367*(m-2-int((m-14)/12)*12)/12) \
	-int(3*(int((y+4900+int((m-14)/12))/100))/4) \
	-2432075
}
この逆演算、すなわち Modified Julian Day から年月日に戻す場合は、例えば、 こんな方法でも間に合います。

BEGIN {
  split("31 28 31 30 31 30 31 31 30 31 30 31", md, " ")
  ..
}
..
function ymd(n, y, m, d) {      # n = MJD, local y, m, d
  y = int(2.73791e-3 * n + 1858.878)
  n -= mjd(y, 1, 0)
  if (n == 0)
	return sprintf("%04d1231", y - 1)
  for (m = 1; m < 12; m++) {
	d = md[m]
	if (m == 2 && (y%4 == 0 && y%100 != 0 || y%400 == 0))
		++d
	if (n <= d)
		break
	n -= d
  }
  d = n;
  return sprintf("%04d%02d%02d", y, m, d)
}

昔の Hewlett Packard 社の関数電卓の銘機 HP-65 等には、 プログラミング例として Julian Day を求めるサンプルが添付されていました。

注 1) 「グレゴリオ暦」のスタートは 1582 年 10 月 15 日 (金曜日) ですから、 この日以降でしか、この公式が使えないことに注意してください。これ以前はシ ーザの「ユリウス暦」で、この前日が 1582 年 10 月 4 日 (木曜日) になってい て、地球の公転周期も 365 + 1/4 という計算でしたから、考古学には適用できな い公式です。

注 2) 地球の公転周期を

365.2422 ≒ 365 + 1/4 - 1/100 + 1/400
で近似した結果が、
1) 4 で割り切れれば閏年
2) ただし、100 で割り切れ、400 では割り切れない年は平年
という閏年の規則です。

注 3) 「Zeller の公式」は、グレゴリオ暦のある時点からの通算日数求めていま すから、事務計算でよく必要になる、「日数間隔」の計算に使えることに注意して ください。

注 4) 天体の運動そのものがカオス的ですから、暦の計算は本質的に難しいもので、 本格的に勉強する場合は、例えば、 http://emr.cs.iit.edu/home/reingold/calendar-book/index.shtml などを参照してください。

平林 浩一