金子邦彦研究室プログラミングC 言語によるアルゴリズムとデータ構造曜日計算

曜日計算

1 年月日から経過年数の計算

日数の計算の最大の問題点はやはり閏年の存在であろう。 閏年の定義(グレゴリオ暦)は「年数が4の倍数の年をうるう年とする。ただし, 100の倍数の年で400の倍数でない年 は,平年とする」となっている。もちろん閏年とは2月が29日ある年のことであ る。したがって、1600年や2000年はうるう年だが, 1700年,1800年,1900年, 2100年などは平年。うるう年は400年に97回で,1年の平均日数は365.2422日であ る。判定の仕方はこのように書く。
  y % 4 == 0 && y % 100 != 0 || y % 400 == 0                            (1)

1.1 考え方

閏年の2月は29日だがその他は28になる。これを考えやすくするために1年の起点 を3月とし、月は3月から14月まであると考える。そうすると以下のような式で 経過日数を計算することができる。
  365×(y-1)+[y/4]-[y/100]+[y/400]+31+28+1
さらに第m月1日までのの日数は次のように表すことができる。
  [306×(m+1)/10]-122                                                   (2)
   where 3 ≦ m ≦ 14
式(1),(2) を用いるとy年m月d日までの日数は次のように表すこ とができる。
  365×(y-1)+[y/4]-[y/100]+[y/400]+31+28+1+[306×(m+1)/10]-122+(d-1)    (3)
                 =365×y+[y/4]-[y/100]+[y/400]+[306×(m+1)/10]-428+d
                     where  3 ≦ m ≦ 14
この式をFairfield公式と呼ぶ。これを用いれば曜日の計算もできる。起源元年1 月1日は月曜日らしい!!

1.2 ツェラーの公式

曜日を求めるもう一つの方法にツェラー(Zeller)の公式と呼ばれるものが ある。
  [(y+[y/4]-[y/100]+[y/400]+[2.6m+1.6]+d) mod 7]                            (4)
この値が0なら日曜、1なら月曜、、、といった具合いになる。ただしこの場合も 1、2月は前年の13、14月となる。
  if(month == 1 || month == 2 ){
    year--;
    month += 12;
  }
  (y+y/4-y/100+y/400+(13*m+8)/5+d)%7
ここでは丸め誤差を防ぐため小数ではなく分数形式で計算している。

1.3 暦

これらの方式は1583年から有効である。なぜなら1582年にユリウス暦からグレゴ リオ暦へと改定したからなのだ。「ユリウス暦」とは紀元前46年にユリウスシー ザーが制定した暦で、1年は365日、4年に1度閏年を置くとしたもの だった。しかし、この暦では4年におよそ36分の誤差が現れてしまう。 1600年ほどたった1582年頃にはその誤差 はおよそ10日ほどに膨れ上がっていた。そして、時のローマ教皇,グレゴリウ ス13世がグレゴリオ暦に改暦した。改暦に際し、日付を10日とばして、ユリウス 暦の1582年10月4日の翌日をグレゴリオ暦の10月15日としたり、閏年の置き方を 上記のように改めた。
ちなみに、新教や東方正教会の国では改暦が遅れ,デンマーク,オランダ,ド イツでは1700年,イギリスでは1752 年,ソ連では1918年,ギリシャでは1924年に改暦が行われた。日本は1873年に太 陰太陽暦の天保暦からグレゴ リオ暦に改暦した。つまり、日本で考える場合は1873年以降でないと有効ではな いということである。

2 年月日の計算

経過日数の周期から考えて、閏年を考慮にいれると400年で146097日あることに なる。さらに100年で36524日、4年で1461日、1年で365日であることを用いて、 3月1日までの経過日数を表すと
  d=400K  146097×K-365+30+28+1
  d=100L  36524×L-365+30+28+1
  d=4M    1461×M-365+30+28+1
  d=N     365×N-365+30+28+1
となる。例えば2143年は400×4+100×1+4×10+3となる。 これをK,L,M,Nについて変形させると年が求められる。 次に経過月を求める。求め方は全体の経過日数からその年の初めの日(3月1日)ま での日数を引いた日数から月を算出できる。例えば40日だったら4月とか。まず3 月1日からの経過日数を算出する。d'=3月1にからの経過日数、days=全体の経過 日数として
  d'=days-(365×(y-1)+[y/4]-[y/100]+[y/400]+31+28+1)                    (5)
とする。このとき、閏年の2月29日は-1日となるのでしっかり補正しなくてはな らない。
  if(d == -1 ){
    y--;
    d=365;
  }    
mを次の条件から求める。
  3 ≦ m ≦ 14, [306×(m+1)/10]-122 ≦ d' ≦ [306×(m+2)/10]-122        (6)
mが求まると日付dがもとまる。
  d=d'-([306×(m+1)/10]-122-1)                                          (7)
mは3≦m≦14なのでこれを普通の月に変える必要がある。
  if(m > 12){
    y++;
    m-=12;
  }

3 プログラム例

3.1 年月日から曜日を求める

#include<stdio.h>

const int BUFFER_SIZE = 100;

int main()
{
    int y, m, d, days, n;
    char buf[BUFFER_SIZE],c;

    printf("Please input year month date\n");
    while(true){
        fgets(buf, BUFFER_SIZE, stdin);
        n = sscanf(buf,"%d %d %d %c", &y, &m, &d, &c);
        if(n == 3) {
            break;
        }
        else if(n == -1) {
            continue;
        }
        else{
            printf("Usage:year month date\n");
            return 0;
        }
    }

    printf("%d年%d月%d日\n",y,m,d);
    if(m < 3){
        y--;
        m += 12;
    }
    days = 365 * y + y / 4 - y / 100 + y / 400 + 306 * (m + 1) / 10 + d - 428;

    if(days < 0){
        printf("error:days<0\n");
        return 0;
    }

    switch (days % 7){
    case 0 :
        printf("日曜日\n");
        break;
    case 1 :
        printf("月曜日\n");
        break;
    case 2 :
        printf("火曜日\n");
        break;
    case 3 :
        printf("水曜日\n");
        break;
    case 4 :
        printf("木曜日\n");
        break;
    case 5 :
        printf("金曜日\n");
        break;
    case 6 :
        printf("土曜日\n");
        break;
    }

    return 0;
}

3.2 経過日数から年月日と曜日を求める

#include<stdio.h>

const int BUFFER_SIZE = 100;

int main()
{
    int y, m, d, days, n;
    char buf[BUFFER_SIZE], c;

    printf("Please input days\n");
    while(1){
        fgets(buf, BUFFER_SIZE, stdin);
        n = sscanf(buf,"%d %c",&days, &c);
        if(n == 1) {
            break;
        }
        else if(n == -1) {
            continue;
        }
        else{
            printf("Usage:days\n");
            return 0;
        }
    }

    if(days < 0){
        printf("error:days\n");
        return 0;
    }
    y = (days + 305) / 146097 * 400
        + (days + 305) % 146097 / 36524 * 100
        + (days + 305) % 146097 % 36524 / 1461 * 4
        + (days + 305) % 146097 % 36524 % 1461 / 365;
    d = days - (365 * (y - 1) + y / 4 - y / 100 + y / 400 + 31 + 28 + 1);
    if(d == -1){
        y--;
        d = 365;
    }
    for(m = 3; m < 15; m++){
        if(306 * (m+1) / 10 - 122 <= d && 306 * (m + 2) / 10 - 122 > d)
            break;
    }
    d -= 306 * (m + 1) / 10 - 122 - 1;
    if(m > 12){
        y++;
        m -= 12;
    }
    printf("%d年%d月%d日\n",y,m,d);
    switch (days % 7){
    case 0 :
        printf("日曜日\n");
        break;
    case 1 :
        printf("月曜日\n");
        break;
    case 2 :
        printf("火曜日\n");
        break;
    case 3 :
        printf("水曜日\n");
        break;
    case 4 :
        printf("木曜日\n");
        break;
    case 5 :
        printf("金曜日\n");
        break;
    case 6 :
        printf("土曜日\n");
        break;
    }
    return 0;
}