內(nèi)容同步于我的博客:https://blog.bigrats.net/archives/sakamoto-algorithm.html
算法代碼
int dayofweek(int y, int m, int d)
{
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
y -= m < 3;
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}
原理及推導(dǎo)
首先,1年1月1日為星期w0,由于365 = 527 + 1,366 = 527+2,因此每過一個(gè)平年,對(duì)應(yīng)日期的星期數(shù)便加1,每過一個(gè)閏年,3月前對(duì)應(yīng)日期的星期號(hào)遞增1,3月開始對(duì)應(yīng)日期的星期號(hào)遞增2。
一、假設(shè)我們計(jì)算y年1月1日的星期數(shù)
(1)我們首先計(jì)算一共有多少個(gè)閏年:因?yàn)槊克哪暧幸粋€(gè)閏年,同時(shí)考慮到能被100整除但不能被400整除的年份不是閏年,因此從1年到y(tǒng)年一共有y/4 - y/100 + y/400個(gè)閏年
(2)因此我們可以得到y(tǒng)年1月1日的星期數(shù)計(jì)算方法:(其中H為一個(gè)修正常數(shù))
if(isLeap(y)){
w = (y + y/4 - y/100 + y/400 - 1 + H) % 7;
// 因?yàn)?月1日在3月前,因此當(dāng)y為閏年時(shí),計(jì)算的天數(shù)增值多了1,我們把它減去
else {
w = (y + y/4 - y/100 + y/400 + H) % 7;
}
二、接下來我們來計(jì)算y年m月d日的星期數(shù)
(1)由于我們已經(jīng)得到了y年1月1日的星期數(shù),我們只需算出m月d日相對(duì)于1月1日的天數(shù)增值即可計(jì)算的y年m月d日的星期數(shù)。
對(duì)于平年:deltaDays_noleap[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}
對(duì)于閏年:deltaDays_leap[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
模7取余數(shù)后為:
deltaWofMonth_noleap[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};
deltaWofMonth_leap[12] = {0, 3, 4, 0, 2, 5, 7, 3, 6, 1, 4, 6};
(2)從而我們得到了y年m月d日的星期數(shù)計(jì)算方法:
if(isLeap(y)){
w = (y + y/4 - y/100 + y/400 - 1 + deltaWofMonth_leap[m - 1] + d + H) % 7;
else {
w = (y + y/4 - y/100 + y/400 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
}
(3)我們發(fā)現(xiàn),在3月以后,實(shí)際有:deltaWofMonth_noleap[i] % 7 == (deltaWofMonth_leap[i] - 1) % 7
因此我們可以僅采用平年的deltaWofMonth_noleap,得到計(jì)算方法如下:
if(isLeap(y)){
if(m < 3){
w = (y + y/4 - y/100 + y/400 - 1 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
} else {
w = (y + y/4 - y/100 + y/400 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
}
else {
w = (y + y/4 - y/100 + y/400 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
}
可以合并簡(jiǎn)化為:
if(m < 3 && isLeap(y)) {
w = (y + y/4 - y/100 + y/400 - 1 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
}else {
w = (y + y/4 - y/100 + y/400 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
}
(4)現(xiàn)在來進(jìn)一步簡(jiǎn)化算法,當(dāng)m < 3時(shí),令y = y – 1,則有:當(dāng)y為閏年時(shí),y + y/4 - y/100 + y/400減少了2,當(dāng)y為平年時(shí),y + y/4 - y/100 + y/400減少了1。因此,算法可以簡(jiǎn)化為:
if(m < 3) {
y--;
w = (y + y/4 - y/100 + y/400 + 1 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
} else {
w = (y + y/4 - y/100 + y/400 + deltaWofMonth_noleap[m - 1] + d + H) % 7;
}
修改deltaWofMonth,其中,1月和2月不變,將3月之后的所有月的數(shù)值減1后模7,得到:
deltaW = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
從而代碼簡(jiǎn)化為:
y -= (m < 3);
w = (y + y/4 - y/100 + y/400 + deltaW[m - 1] + d + H + 1) % 7;
(5)經(jīng)過調(diào)試,得到修正常數(shù)H = 6,因此得到算法如下:
int dayofweek(int y, int m, int d)
{
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
y -= m < 3;
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}
三、一些說明
由于目前使用的歷法并不是從1年1月1日開始使用的,而是從1582年年底才開始使用的格利戈里歷,并且在1582年不存在10月5日到10月14日這10天,因此此算法只能準(zhǔn)確計(jì)算1582年10月15日開始之后的星期數(shù)。