#102 時を計算する(後編)

2000/02/01

<前目次次>


 前回お話ししたように、暦の単位である年月日は繰り上がり方がばらばらである。そのためそのままでは計算機で扱うのにはいささか不自由する。

 例えば日時の新旧を比較するだけであれば、年月日の順に桁をそろえて繋いだ数値どうしを比較すれば、その結果は時刻順の通りになる。例えば、1999年9月1日と1999年12月31日と2000年1月1日では、19990901<19991231<20000101となって、大小関係は時刻順に従っている。しかし、二つの日時の差を計算しようと思うと、その数字を引き算すればいいというわけにはいかない。

 そのためコンピュータ内部で日数の計算をする時には、あらかじめ、過去のある時点からの通算日数に変換し、その差を取る場合が多い。例えば、天文分野などで用いられているユリウス日というものは、紀元-4712年1月1日からの通日で表されている。またプログラミング言語のCやPerlなどでは、1970年1月1日からの通算秒を元に時刻を扱い、現在時刻をその数値で返したり、その値から年月日時分秒の各要素を返す関数などが用意されている。

 ここではわかりやすいように、グレゴリウス暦で計算した紀元0年1月1日からの日数を計算することを考えてみる。実際にグレゴリウス暦が制定されたのは1582年以降であるが、ここではそれを過去にまで延長したものとして考える。

 まず年の日数を計算する。1年は通常365日だが、うるう年が1回あるごとに1日増えていく。グレゴリウス暦の規則によれば、うるう年は4年ごとに1回現れ、100年毎に1回間引かれ、更に400年毎に1回現れる。従って、現在の西暦年をYとすれば、Y年1月1日までの通算日数は 365*Y + int(Y/4) - int(Y/100) + int(Y/400) で表される。ここでint( )は、( )内の整数部分を取り出す関数である。

 ややこしいのは月の日数である。所謂「小の月」は2,4,6,9,11月であり、これは等間隔にはなっていない。しかも2月は平年では28日、うるう年でも29日と少し短い。何より年の中途半端な位置に1日が挿まるので計算が面倒である。

 ここでツェラーの公式という、非常にうまい方法を紹介する。まず年の初めを3月とみなし、1月と2月は前年の13月、14月というように仮定する。そうするとうるう年の2月29日は「年の終わり」ということになり、別に条件分けする必要がなくなる。そして3月から14月までの大の月(O)と小の月(o)の並びは、OoOoO OoOoO Ooという具合になり、5ヵ月毎にOoOoOという具合に規則的に並ぶ。つまり5ヵ月で合計3日が挿まるのだ。intによる整数化の結果うまく1日が差し挟まるように調整すると、1月1日からの通日は、月をM、日をDで表して、30*M + int((M+1)*3/5) + D - 33という具合になる。(ただし、M=1,2については、前年の13月,14月ということにする。)

 従って、最初にMの値による条件判断によりYとMの値を修正すれば、あとは整数化関数を使った1本の式で通日が求められるのである。適当な値を加減すれば基準となる日時は変更可能であるし、この数字を7で割った余りを計算すれば曜日が分かる。ちなみに400年=365*400+97日=146097日は7で割り切れるので、七曜は400年ごとに全く同じになる。従ってグレゴリウス暦紀元0年1月1日は、2000年の場合と同じく土曜日になる。

 ちなみに曜日だけを計算するのであれば、整数の除算の余り部分にのみ着目するともっと簡略化できる。先の式と同様に前、処理として1月,2月は前年の13月,14月とし、西暦の4桁の上2桁を4で割った余りをa, 同じく下2桁を4で割った商と余りをそれぞれb,cとし、月と日をそれぞれM, Dとすれば、Mの条件判断を行ったあと、W = -2*(a+b) + c + int(13*(M+1)/5) + dを求め(値が負であれば、適宜7の倍数を加える)、それを7で割った余りを0=土、1=日…、6=金とすれば曜日は求まる。特に今年の場合、3月以降であればa=b=c=0なので式の前半は0である。例えば3月1日であれば、W = int(13*4/5) + 1 = 11なので、水曜日である。2月1日の場合は、1999年14月1日とみなして、a=3, b=24, c=3, M=14, D=1 となり、W = -11となり、14を加えればW = 3で火曜日である。a,b,c,dは計算の途中で独立に7の倍数を加減しても結果に変わりはないので、訓練すれば暗算で曜日が割出せるようになるであろう。これであなたも今年からカレンダー要らずである。


<前目次次>