正文

公历历法::星期算法2005-06-12 18:24:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/rickone/1777.html

分享到:

[原创]

【引】

《创世纪》

创世在宇宙天地尚未形成之前,黑暗笼罩着无边无际的空虚混饨,上帝那孕育着生命的灵运行其中,投入其中,施造化之工,展成就之初,使世界确立,使万物齐备。

上帝用七天创造了天地万物。这创造的奇妙与神秘非形之笔墨所能写尽,非诉诸言语所能话透。

第一日,上帝说:“要有光!”便有了光。上帝将光与暗分开,称光为昼,称暗为夜。于是有了晚上,有了早晨。

第二日,上帝说:“诸水之向要有空气隔开。”上帝便造了空气,称它为天。

……

第七日,天地万物都造齐了,上帝完成了创世之功。在这一天里,他歇息了,并赐福给第七天,圣化那一天为特别的日子,因为他在那一天完成了创造,歇工休息。就这样星期日也成为人类休息的日子。

“造化钟神秀,阴阳割分晓。”上帝就是这样开辟鸿蒙,创造宇宙万物的。


以上来自《圣经》,现在还有一些基督教徒每个礼拜天去教堂朝拜,可见一个星期七天就源于此,这正是我讨论的中心,如何计算哪年哪月哪日是星期几。

【公历历法】
公历历法很简单,年有润年(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;
}

[作者]rickone
[日期]2005年6月12日

阅读(11570) | 评论(13)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册