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 などを参照してください。
平林 浩一