師匠の散歩

きままにPerlでも

ツェラーの式で曜日を求める

グレゴリウス暦の年月日から曜日を求める / 床関数を使っているので、紀元前の計算もできます。(紀元前1年は 0 を代入)/ subZeller.cgi

年月日を入力してください。

ボタンを押すと実行します
訪問された時刻です

ボタンを押すと実行します

師匠の解説

ネットで見つけた資料からツェラーの式への取り組み内容を紹介します。

0.曜日は7日で循環する
7日で循環するので、西暦1年1月1日からY年M月D日までの日数DAYSを7で割った余りが曜日となる。 ここで条件がある (1)西暦1年1月1日を月曜日とする (2)Y年が4で割り切れればうるう年であるが、Y年が100で割り切れるときは平年となり、Y年が400で割り切れるときはうるう年とする。 (3)使用する暦はグレゴリオ暦である (1)の 「西暦1年1月1日が月曜日であるとする」については諸説があるが、明確な制定がいつどこで行われたのかは不明である。そのため、ISO8600で「西暦2000年1月1日を土曜日とする」と正式に制定された。ごく最近のことである。 (2)と(3)では日数計算を行うためには暦のシステムが重要である。地球の公転時間は365日ちょうどではなく少し長く「うるう年」で調整が行われる。このうるう年を定義した暦としてグレゴリオ暦を使うという意味である。うるう年の定義ははるかな過去・未来永劫同じであるとは限らない。そのため、ツェラーの式はグレゴリオ暦が使えない過去・未来では使用できない。
1.西暦Y年M月D日までの日数を求める
西暦1年1月1日からY年3月1日までの日数を求める うるう年の計算方法が重要になる Days_year = 31+28+365*(Y-1)+1+int(Y/4)-int(Y/100)+int(Y/400) ・・・(1) 1月と2月を前年の13月と14月であるとすると、3月1日からM月1日の前日までの日数を求める ここで、int(306*(M+1)/10)-122の計算式の解説があるのかどうか不明である。ただし実計算の結果が合致しているのは間違いない。 Days_month = int(306*(M+1)/10)-122              ・・・(2) 1日からD日までの日数は Days_day = D-1                        ・・・(3) (1),(2),(3)を足せば、1年1月1日からY年M月D日までの日数となる。 Days = 31+28+365*(Y-1)+1+int(Y/4)-int(Y/100)+int(Y/400) + int(306*(M+1)/10)-122 + (D-1) ・・・(4)
2.ツェラーの式を求める
上記(4)を7で割った余りを求めることで曜日を求めることができる。 Wday = { 31+28+365*(Y-1)+1+int(Y/4)-int(Y/100)+int(Y/400) + int(306*(M+1)/10)-122 + (D-1) } % 7 ・・・(5) 簡略化を行う Wday = { 31+28+365*(Y-1)+1+int(Y/4)-int(Y/100)+int(Y/400) + int(306*(M+1)/10)-122 + (D-1) } % 7 = { 31+28+ 365*Y-365+1+int(Y/4)-int(Y/100)+int(Y/400) + int(306*(M+1)/10)-122 + (D-1) } % 7 = { 365*Y + int(Y/4)-int(Y/100)+int(Y/400) + int(153*M(+1)/5) -428 } % 7 ここで、 { 365*Y } %7 = { 7*52*Y + Y } % 7 = { Y } % 7 { -428 } %7 ={ 6 } % 7 で変形すると Wday = { Y + int(Y/4)-int(Y/100)+int(Y/400) + int(153*M(+1)/5) +6 } % 7 さらに次の変形を行う { int(153*(M+1)/5)+6 } % 7 = { int(153*(M+1)/5 +6) } % 7 = { int(153*M/5 + 183/5) } % 7 = { int(7*(4*M/5) + 13*M/5 + 7*25 + 8/5) } % 7 7の倍数はゼロになるので、 = { int(13 * M/5 + 8/5) } % 7 これで、式(5)はツェラーの式となる Wday = { Y + int(Y/4)-int(Y/100)+int(Y/400) + int((13*M+8)/5) } % 7 ・・・(6)
3.曜日を求める
前述したとおり、曜日が正式に決められたのがいつかはわからない。西暦1年1月1日ではないことだけははっきりしている。そのため、現在は、国際基準(ISO8601)で決められた「西暦2000年1月1日を土曜日とする」と式(6)から、西暦1年1月1日の曜日は月曜日だとわかる。

実際のプログラム

Perl,JavaScript,Excelでの実現方法を紹介する。特に少数点以下の切り捨て方法が各言語・関数によって異なるため、西暦元年1月1日より以前では保障はしない。

Perl
# ------------------------- # 曜日を求めるツェラーの式 # ------------------------- sub Zeller{ my ($year,$month,$day) = @_; if($month<3){ $year --; $month += 12; } my $week = ($year + floor($year/4) - floor($year/100) + floor($year/400) + floor((13*$month+8)/5) + $day) % 7; my @youbi = ("日","月","火","水","木","金","土"); return $youbi[$week]; }
JavaScript
// ------------------------- // 曜日を求めるツェラーの式 // ------------------------- function zeller(year,month,day) { // 変数を数値に強制変換 year *= 1.0; month *= 1.0; day *= 1.0; if(month<3) { // year -- ; month += 12 ; } var wday = (year + Math.floor(year/4) - Math.floor(year/100) + Math.floor(year/400) + Math.floor((13*month+8)/5) + day) % 7; var youbi = new Array("日", "月", "火", "水", "木", "金", "土"); return youbi[wday]; }
エクセルは非常に残念なことながら、床関数という概念を持たない稀有な言語です。そのかわりエクセルには「int()関数」というものが用意されてますが、この関数がマイナスの数字をどのように切り捨てるのかをよく考えないといけません。
Excel
エクセルの一般関数 1月2月を前年の13月14月とし、年月日をそれぞれ[a1][a2][a3]セルに入れてあるものとする =mod( ([a1] + int([a1]/4) - int([a1]/100) + int([a1]/400) + int((13*[a2]+8)/5) + [a3]) ,7)

subZeller.cgi // Topに戻る // indexに戻る
Copyright(C) 2009-2021 Grandmaster Last up : 2020/09/18