■ツェラー(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);
}
}
}