【C言語】
ツェラーの公式で
年月日から曜日を求める

年月日から ツェラーの公式で 曜日を求める

■ツェラー(Zeller)の公式で曜日を求める

/* 曜日の算出関数(ツェラーの公式版)  0:日〜6:土 */
char *zeller(int year, int mon, int day)
{
    static char *w[] = {"日","月","火","水","木","金","土"};
    if ((mon == 1) || (mon == 2)){
        year--;
        mon += 12;
    }
    int idx = (year + year/4 - year/100 + year/400 +
            (13 * mon + 8)/5 + day) % 7;
    return  w[idx];
}

年月日から曜日を求めるのに有名な
ツェラーの公式をC言語化してます。

1月2月が前年の13月14月になったり、
4,100,400の閏年関連の定数が

筆者には難解なので、
オリジナル版を考えてみました。


■mktime()関数で曜日を求める

/* 曜日の算出関数(オリジナル版)  0:木〜6:水 */
char    *ymd2week(int year,int mon,int day)
{
       struct tm tm = {
        .tm_sec     =0,         /* 秒 [0-61] 最大2秒までのうるう秒を考慮 */
        .tm_min     =0,         /* 分 [0-59] */
        .tm_hour    =0,         /* 時 [0-23] */
        .tm_mday    =day,       /* 日 [1-31] */
        .tm_mon     =mon-1,     /* 月 [0-11] 0から始まることに注意 */
        .tm_year    =year-1900, /* 年 [1900からの経過年数] */
        .tm_wday    =0,         /* 曜日 [0:日 1:月 ... 6:土] */
        .tm_yday    =0,         /* 年内の通し日数 [0-365] 0から始まることに注意*/
        .tm_isdst   =-1         /* 夏時間が無効であれば 0 */
    };
    time_t  t = mktime(&tm);
    if(t == (time_t)-1){
        puts("mktimeで失敗しました");
        fflush(NULL);
        exit(1);    
    }
    //1970/0/01 は木曜日
    //UTC:Universal time coordinated(協定世界時)
    static char *w[] = {"木","金","土","日","月","火","水"};
    time_t      sec     = t + 9*60*60;//UTCとの 時差 9時間
    time_t      min     = sec/60;
    time_t      hour    = min/60;
    time_t      days    = hour/24;    
    days %= 7 ;
    if(days <0){     //1970/01/01より前  
        days += 7;
    }
    return  w[days];
}

1970年1月1日を起点として
経過日を7で割ったあまりから
曜日を求めました。

64BIT版Linux-gccで動作確認しています。
32Bit版Windowsでは動きません。

■まとめ(コンパイル可能)

#include    <stdio.h>
#include    <stdlib.h>
#include    <time.h>
#include    <limits.h>
#include    <stdint.h>
#include    <string.h>
#include    <assert.h>
#if   (__x86_64 && __GNUC__)  
    /*OK*/
#else
  #error  "試験してません"
#endif

/* 曜日の算出関数(ツェラーの公式版)  0:日〜6:土 */
char *zeller(int year, int mon, int day)
{
    static char *w[] = {"日","月","火","水","木","金","土"};
    if ((mon == 1) || (mon == 2)){
        year--;
        mon += 12;
    }
    int idx = (year + year/4 - year/100 + year/400 +
            (13 * mon + 8)/5 + day) % 7;
    return  w[idx];
}
/* 曜日の算出関数(オリジナル版)  0:木〜6:水 */
char    *ymd2week(int year,int mon,int day)
{
       struct tm tm = {
        .tm_sec     =0,         /* 秒 [0-61] 最大2秒までのうるう秒を考慮 */
        .tm_min     =0,         /* 分 [0-59] */
        .tm_hour    =0,         /* 時 [0-23] */
        .tm_mday    =day,       /* 日 [1-31] */
        .tm_mon     =mon-1,     /* 月 [0-11] 0から始まることに注意 */
        .tm_year    =year-1900, /* 年 [1900からの経過年数] */
        .tm_wday    =0,         /* 曜日 [0:日 1:月 ... 6:土] */
        .tm_yday    =0,         /* 年内の通し日数 [0-365] 0から始まることに注意*/
        .tm_isdst   =-1         /* 夏時間が無効であれば 0 */
    };
    time_t  t = mktime(&tm);
    if(t == (time_t)-1){
        puts("mktimeで失敗しました");
        fflush(NULL);
        exit(1);    
    }
    //1970/0/01 は木曜日
    //UTC:Universal time coordinated(協定世界時)
    static char *w[] = {"木","金","土","日","月","火","水"};
    time_t      sec     = t + 9*60*60;//UTCとの 時差 9時間
    time_t      min     = sec/60;
    time_t      hour    = min/60;
    time_t      days    = hour/24;    
    days %= 7 ;
    if(days <0){     //1970/01/01より前  
        days += 7;
    }
    return  w[days];
}
static inline int isleap(int y) {
    return  y%4==0 && (y%100!=0 || y%400==0);  
}
int mdays[2][13] ={ 
    {0,31,28,31,30,31,30,31,31,30,31,30,31},
    {0,31,29,31,30,31,30,31,31,30,31,30,31}
};
int main(void){
  //1582年10月15日からグレゴリオ暦
  //2038年1月19日 32bit版linux time_t 桁あふれ
  for(int  ymd = 15821015 ; ymd <= 20380119 ;ymd++){
    int  year = (ymd/10000) ;
    int  mon  = (ymd/100)%100;
    int  day  = (ymd)%100;
    
    if(mon < 1 || 12 < mon)
      continue ;
    if(day < 1 || mdays[isleap(year)][mon] < day)
      continue ;
    
    //年月日を曜日変換
    char *z = zeller  (year,mon,day);
    char *y = ymd2week(year,mon,day);
    
    //検算:違ってたらバグ
    if((strcmp(z,y)) != 0) {
        assert(0);
    } 
    
    //量が多いので2月29日だけ表示
    if(isleap(year) && mon == 2 && day == 29){   
        printf("%d(%s)\n",ymd,y);
    }
  }
}