公历历法::星期算法
【引】
《创世纪》
创世在宇宙天地尚未形成之前,黑暗笼罩着无边无际的空虚混饨,上帝那孕育着生命的灵运行其中,投入其中,施造化之工,展成就之初,使世界确立,使万物齐备。
上帝用七天创造了天地万物。这创造的奇妙与神秘非形之笔墨所能写尽,非诉诸言语所能话透。
第一日,上帝说:“要有光!”便有了光。上帝将光与暗分开,称光为昼,称暗为夜。于是有了晚上,有了早晨。
第二日,上帝说:“诸水之向要有空气隔开。”上帝便造了空气,称它为天。
……
第七日,天地万物都造齐了,上帝完成了创世之功。在这一天里,他歇息了,并赐福给第七天,圣化那一天为特别的日子,因为他在那一天完成了创造,歇工休息。就这样星期日也成为人类休息的日子。
“造化钟神秀,阴阳割分晓。”上帝就是这样开辟鸿蒙,创造宇宙万物的。
以上来自《圣经》,现在还有一些基督教徒每个礼拜天去教堂朝拜,可见一个星期七天就源于此,这正是我讨论的中心,如何计算哪年哪月哪日是星期几。
【公历历法】
公历历法很简单,年有润年(LeapYear)和平年之分,平年每月天数恒为:
月份 一 二 三 四 五 六 七 八 九 十 十一 十二
天数 31 28 31 30 31 30 31 31 30 31 30 31
共365天。润年366天,二月多一天,29天。
【润年判断】
如果年份数能被4整除但不能被100整除或者能被400整除者,为润年。
【400年刚好一个轮回】
很容易想到每400年内的润年数相等,即刚好一个轮回,400年有多少个润年?被4整除的有100个,被100整除的有4个,被400整除的只有1个,所以一共有100-4+1=97个润年,所以400年共有(365*400+97)天,即146097天,除7余0!
也就是说2001年1月1日的星期数与1年1月1日的星期数相同,翻出日历一看,星期一,我不知道上帝什么时候造的人,或许是1年1月1日。这样一来,任何一个日期我们都可以把它折算到0~399年之内的日期来算,0年的说法不准确,或许没有,但不影响这个数学结论,我们只是借它来推一推而已。
【起初的400年】
如果每一年都是平年,即365天,除7余1,也就是说如果不遇上润年,每往下一年就会使星期数增一。同样的道理,如果遇上润年,则多增一。我们要算的也就是把润年数算出来就行了,很容易,如果是y年(0<=y<400),则遇到的润年数为[y/4]-[y/100]+1,为什么要+1,因为0年也是润年(被400整除),然后把y自己身加起来就得到年指数:(y+[y/4]-[y/100])%7,它影响着星期的轮转。(其中[]表示取整,%表示取余,%7表示除以7得到的余数)
【月份的变迁】
月份的天数也不一定,所以不好直接用数学公式来表示,所以列一个表,表中对应数为星期的月指数。前面有数据:(假设为平年)
月份 一 二 三 四 五 六 七 八 九 十 十一 十二
天数 31 28 31 30 31 30 31 31 30 31 30 31
每月对应前面过去的天数:
月份 一 二 三 ……
天数 0 31 31+28 ……
对天数对7除取余得:
月份 一 二 三 四 五 六 七 八 九 十 十一 十二
天数 0 3 3 6 1 4 6 2 5 0 3 5
用一个数组保存下来:
R[13]={0,0,3,3,6,1,4,6,2,5,0,3,5};
设月为m,则月份的影响指数为R[m],它影响着星期的轮转。
【全部加起来取7的余数】
最后剩下天指数,这很容易,直接加起来就行了,所以最后得到一个总的表达式:(先把年对400除取其余数:Year=Year%400)
Week=(Year+[Year/4]-[Year/100]+R[Month]+Day+C)%7
最后有一个常数C,如何确定这个常数,拿一个平年的正常日期数代进来,和星期数比较就可以确定C。
如2001年1月1日是星期一,所以(1+0-0+0+1+C)%7=1,即(2+C)%7=1 ==> C=6。
【遇到润年C会变】
如果没有拿足够的数据来验证,我不敢说C是常数。举几个例子:
2000年1月1日星期六,如果C=6,按公式算出来:(0+0-0+0+1+6)%7=0,星期天,错了一天,哪里错了?
前面算月指数的时候是按平年来算的,如果遇到润年,多的一天是在2月29日,如果在这之前的日期,润年不润,所以年指数会多算一天,如果在这之后,润年已润,年指数已经计算在内,所以月指数同样按平年来算不误,如2000年5月1日星期一,按公式:(0+0-0+1+1+6)%7=1,完全正确。
所以改进的做法是先判断是否是在润年而且月份数小于3,如果是,则C=5,否则C=6。
C++源码:
#include<iostream.h>
#include<string.h>
int week(int y,int m,int d)
{
static int r[13]={0,0,3,3,6,1,4,6,2,5,0,3,5};
int c,w;
y%=400;
if((y==0||y%4==0&&y%100!=0)&&m<3)c=5;
else c=6;
w=(y+y/4-y/100+r[m]+d+c)%7;
return w;
}
void main()
{
char wk[15]="天一二三四五六";
int y,m,d,w;
cin>>y>>m>>d;
w=week(y,m,d);
cout<<y<<"年"<<m<<"月"<<d<<"日 星期"<<wk[w*2]<<wk[w*2+1]<<endl;
}
来个简单的
用了C++的cin和cout,直接改用scanf,和printf就可以了
#include<iostream>
using namespace std;
const int mon[2][12]={31,28,31,30,31,30,31,31,30,31,30,31,31,29,31,30,31,30,31,31,30,31,30,31};
int Is_Leap(int year){
if(year%400==0||(year%4==0&&year%100!=0)) return 1;
return 0;
}
void output(int fir,int n){
int i,j;
cout<<endl;
for(i=1;i<=12;i++){
cout<<i<<"月↓"<<endl<<"----------------------------"<<endl<<" 一 二 三 四 五 六 日"<<endl;
for(j=1;j<fir;j++) printf(" ");
for(j=1;j<=mon[n][i-1];j++){
printf("%4d",j);
fir++;
if(fir==8){
cout<<endl;
fir=1;
}
}
if(fir!=1) cout<<endl;
cout<<"----------------------------"<<endl;
}
cout<<endl;
}
int main()
{
int year,t;
while(cout<<"请输入要查询的年号(input 0 to end): "&&cin>>year&&year>0){
t=year-1;
t=(1+t+t/4-t/100+t/400)%7;
if(t==0) t=7;
output(t,Is_Leap(year));
}
return 0;
}
结果如下:
请输入要查询的年号(input 0 to end): 1985
1月↓
----------------------------
一 二 三 四 五 六 日
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
----------------------------
2月↓
----------------------------
一 二 三 四 五 六 日
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
----------------------------
3月↓
----------------------------
一 二 三 四 五 六 日
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
----------------------------
4月↓
----------------------------
一 二 三 四 五 六 日
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
----------------------------
5月↓
----------------------------
一 二 三 四 五 六 日
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
----------------------------
6月↓
----------------------------
一 二 三 四 五 六 日
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
----------------------------
7月↓
----------------------------
一 二 三 四 五 六 日
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
----------------------------
8月↓
----------------------------
一 二 三 四 五 六 日
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
----------------------------
9月↓
----------------------------
一 二 三 四 五 六 日
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
----------------------------
10月↓
----------------------------
一 二 三 四 五 六 日
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
----------------------------
11月↓
----------------------------
一 二 三 四 五 六 日
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30
----------------------------
12月↓
----------------------------
一 二 三 四 五 六 日
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
----------------------------