日历查询的算法如何计算某一天是星期几
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
⽇历查询的算法如何计算某⼀天是星期⼏
—— 蔡勒(Zeller)公式
历史上的某⼀天是星期⼏?未来的某⼀天是星期⼏?关于这个问题,有很多计算公式(两个通⽤计算公式和⼀些分段计算公式),其中最著名的是蔡勒(Zeller)公式。
即w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1
公式中的符号含义如下,w:星期;c:世纪-1;y:年(两位数);m:⽉(m⼤于等于3,⼩于等于14,即在蔡勒公式中,某年的1、2⽉要看作上⼀年的13、14⽉来计算,⽐如2003年1⽉1⽇要看作2002年的13⽉1⽇来计算);d:⽇;[ ]代表取整,即只要整数部分。
(C是世纪数减⼀,y是年份后两位,M是⽉份,d是⽇数。
1⽉和2⽉要按上⼀年的13⽉和 14⽉来算,这时C和y 均按上⼀年取值。
)
算出来的W除以7,余数是⼏就是星期⼏。
如果余数是0,则为星期⽇。
以2049年10⽉1⽇(100周年国庆)为例,⽤蔡勒(Zeller)公式进⾏计算,过程如下:
蔡勒(Zeller)公式:w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1
=49+[49/4]+[20/4]-2×20+[26× (10+1)/10]+1-1
=49+[12.25]+5-40+[28.6]
=49+12+5-40+28
=54 (除以7余5)
即2049年10⽉1⽇(100周年国庆)是星期5。
你的⽣⽇(出⽣时、今年、明年)是星期⼏?不妨试⼀试。
不过,以上公式只适合于1582年10⽉15⽇之后的情形(当时的罗马教皇将恺撒⼤帝制订的儒略历修改成格⾥历,即今天使⽤的公历)。
星期制度是⼀种有古⽼传统的制度。
据说因为《圣经·创世纪》中规定上帝⽤了六天时间创世纪,第七天休息,所以⼈们也就以七天为⼀个周期来安排⾃⼰的⼯作和⽣活,⽽星期⽇是休息⽇。
从实际的⾓度来讲,以七天为⼀个周期,长短也⽐较合适。
所以尽管中国的传统⼯作周期是⼗天(⽐如王勃《滕王阁序》中说的“⼗旬休暇”,即是指官员的⼯作每⼗⽇为⼀个周期,第⼗⽇休假),但后来也采取了西⽅的星期制度。
在⽇常⽣活中,我们常常遇到要知道某⼀天是星期⼏的问题。
有时候,我们还想知道历史上某⼀天是星期⼏。
通常,解决这个⽅法的有效办法是看⽇历,但是我们总不会随时随⾝带着⽇历,更不可能随时随⾝带着⼏千年的万年历。
假如是想在计算机编程中计算某⼀天是星期⼏,预先把⼀本万年历存进去就更不现实了。
这时候是不是有办法通过什么公式,从年⽉⽇推出这⼀天是星期⼏呢?
答案是肯定的。
其实我们也常常在这样做。
我们先举⼀个简单的例⼦。
⽐如,知道了2004年5⽉1⽇是星期六,那么2004年5⽉31⽇“世界⽆烟⽇”是星期⼏就不难推算出来。
我们可以掰着指头从1⽇数到31⽇,同时数星期,最后可以数出5⽉31⽇是星期⼀。
其实运⽤数学计算,可以不⽤掰指头。
我们知道星期是七天⼀轮回的,所以5⽉1⽇是星期六,七天之后的5⽉8⽇也是星期六。
在⽇期上,8-1=7,正是7的倍数。
同样,5⽉15⽇、5⽉22⽇和5⽉29⽇也是星期六,它们的⽇期和5⽉1⽇的差值分别是14、21和28,也都是7的倍数。
那么5⽉31⽇呢?31-1=30,虽然不是7的倍数,但是31除以7,余数为2,
这就是说,5⽉31⽇的星期,是在5⽉1⽇的星期之后两天。
星期六之后两天正是星期⼀。
这个简单的计算告诉我们计算星期的⼀个基本思路:⾸先,先要知道在想算的⽇⼦之前的⼀个确定的⽇⼦是星期⼏,拿这⼀天做为推算的标准,也就是相当于⼀个计算的“原点”。
其次,知道想算的⽇⼦和这个确定的⽇⼦之间相差多少天,⽤7除这个⽇期的差值,余数就表⽰想算的⽇⼦的星期在确定的⽇⼦的星期之后多少天。
如果余数是0,就表⽰这两天的星期相同。
显然,如果把这个作为“原点”的⽇⼦选为星期⽇,那
么余数正好就等于星期⼏,这样计算就更⽅便了。
但是直接计算两天之间的天数,还是不免繁琐。
⽐如1982年7⽉29⽇和2004年5⽉1⽇之间相隔7947天,就不是⼀下⼦能算出来的。
它包括三段时间:⼀,1982年7⽉29⽇以后这⼀年的剩余天数;⼆,1983-2003这⼆⼗⼀个整年的全部天数;三,从2004年元旦到5⽉1⽇经过的天数。
第⼆段⽐较好算,它等于21*365+5=7670天,之所以要加5,是因为这段时间内有5个闰年。
第⼀段和第三段就⽐较⿇烦了,⽐如第三段,需要把5⽉之前的四个⽉的天数累加起来,再加上⽇期值,即
31+29+31+30+1=122天。
同理,第
⼀段需要把7⽉之后的五个⽉的天数累加起来,再加上7⽉剩下的天数,⼀共是155天。
所以总共的相隔天数是122+7670+155=7947天。
仔细想想,如果把“原点”⽇⼦的⽇期选为12⽉31⽇,那么第⼀段时间也就是⼀个整年,这样⼀来,第⼀段时间和第⼆段时间就
可以合并计算,整年的总数正好相当于两个⽇⼦的年份差值减⼀。
如果进⼀步把“原点”⽇⼦选为公元前1年12⽉31⽇(或者天⽂学家所使⽤的公元0年12⽉31⽇),这个整年的总数就正好是想算的⽇⼦的年份减⼀。
这样简化之后,就只须计算两段时间:⼀,这么多整年的总天数;⼆,想算的⽇⼦是这⼀年的第⼏天。
巧的是,按照公历的年⽉设置,这样反推回去,公元前1年12⽉31⽇正好是星期⽇,也就是说,这样算出来的总天数除以7的余数正好是星期⼏。
那么现在的问题就
只有⼀个:这么多整年⾥⾯有多少闰年。
这就需要了解公历的置闰规则了。
我们知道,公历的平年是365天,闰年是366天。
置闰的⽅法是能被4整除的年份在2⽉加⼀天,但能被100整除的不闰,能被400整除的⼜闰。
因此,像1600、2000、2400年都是闰年,⽽1700、1800、1900、2100年都是平年。
公元前1年,按公历也是闰年。
因此,对于从公元前1年(或公元0年)12⽉31⽇到某⼀⽇⼦的年份Y之间的所有整年中的闰年数,就等于
[(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400],
[...]表⽰只取整数部分。
第⼀项表⽰需要加上被4整除的年份数,第⼆项表⽰需要去掉被100整除的年份数,第三项表⽰需要再加上被400整除的年份数。
之所以Y要减⼀,这
样,我们就得到了第⼀个计算某⼀天是星期⼏的公式:
W = (Y-1)*365 + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + D. (1)
其中D是这个⽇⼦在这⼀年中的累积天数。
算出来的W就是公元前1年(或公元0年)12⽉31⽇到这⼀天之间的间隔⽇数。
把W ⽤7除,余数是⼏,这⼀天就是星期⼏。
⽐如我们来算2004年5⽉1⽇:
W = (2004-1)*365 + [(2004-1)/4] - [(2004-1)/100] + [(2004-1)/400] +(31+29+31+30+1)= 731702,
731702 / 7 = 104528……6,余数为六,说明这⼀天是星期六。
这和事实是符合的。
上⾯的公式(1)虽然很准确,但是计算出来的数字太⼤了,使⽤起来很不⽅便。
仔细想想,其实这个间隔天数W的⽤数仅仅是为了得到它除以7之后的余数。
这启发我们是不是可以简化这个W值,只要找⼀个和它余数相同的较⼩的数来代替,⽤数论上的术语来说,就是找⼀个和它同余的较⼩的正整数,照样可以计算出准确的星期数。
显然,W这么⼤的原因是因为公式中的第⼀项(Y-1)*365太⼤了。
其实,
(Y-1)*365 = (Y-1) * (364+1)
= (Y-1) * (7*52+1)
= 52 * (Y-1) * 7 + (Y-1),
这个结果的第⼀项是⼀个7的倍数,除以7余数为0,因此(Y-1)*365除以7的余数其实就等于Y-1除以7的余数。
这个关系可以表⽰为:
(Y-1)*365 ≡ Y-1 (mod 7).
其中,≡是数论中表⽰同余的符号,mod 7的意思是指在⽤7作模数(也就是除数)的情况下≡号两边的数是同余的。
因此,完全可以⽤(Y-1)代替(Y-1)*365,这样我们就得到了那个著名的、也是最常见到的计算星期⼏的公式:
W = (Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + D. (2)
这个公式虽然好⽤多了,但还不是最好⽤的公式,因为累积天数D的计算也⽐较⿇
烦。
是不是可以⽤⽉份数和⽇期直接计算呢?答案也是肯定的。
我们不妨来观察⼀下各
个⽉的⽇数,列表如下:
⽉份:1⽉ 2⽉ 3⽉ 4⽉ 5⽉ 6⽉ 7⽉ 8⽉ 9⽉ 10⽉ 11⽉ 12⽉
--------------------------------------------------------------------------
天数: 31 28(29) 31 30 31 30 31 31 30 31 30 31
如果把这个天数都减去28(=4*7),不影响W除以7的余数值。
这样我们就得到另⼀张
表:
⽉份:1⽉ 2⽉ 3⽉ 4⽉ 5⽉ 6⽉ 7⽉ 8⽉ 9⽉ 10⽉ 11⽉ 12⽉
------------------------------------------------------------------------
剩余天数: 3 0(1) 3 2 3 2 3 3 2 3 2 3
平年累积: 3 3 6 8 11 13 16 19 21 24 26 29
闰年累积: 3 4 7 9 12 14 17 20 22 25 27 30
仔细观察的话,我们会发现除去1⽉和2⽉,3⽉到7⽉这五个⽉的剩余天数值是3,2,3,2,3;8⽉到12⽉这五个⽉的天数值也是3,2,3,2,3,正好是⼀个重复。
相应的累积天数中,后⼀⽉的累积天数和前⼀⽉的累积天数之差减去28就是这个重复。
正是因为这种规律的存在,平年和闰年的累积天数可以⽤数学公式很⽅便地表达:
╭ d;(当M=1)
D = { 31 + d;(当M=2) (3)
╰ [ 13 * (M+1) / 5 ] - 7 + (M-1) * 28 + d + i.(当M≥3)
其中[...]仍表⽰只取整数部分;M和d分别是想算的⽇⼦的⽉份和⽇数;平年i=0,闰年 i=1。
对于M≥3的表达式需要说明⼀下:[13*(M+1)/5]-7算出来的就是上⾯第⼆个表中的平年累积值,再加上(M-1)*28就是想算的⽇⼦的⽉份之前的所有⽉份的总天数。
这是⼀个很巧妙的办法,利⽤取整运算来实现3,2,3,2,3的循环。
⽐如,对2004年5⽉1⽇,有:
D = [ 13 * (5+1) / 5 ] - 7 + (5-1) * 28 + 1 + 1
= 122,
这正是5⽉1⽇在2004年的累积天数。
假如,我们再变通⼀下,把1⽉和2⽉当成是上⼀年的“13⽉”和“14⽉”,不仅仍然符合这个公式,⽽且因为这样⼀来,闰⽇成了上⼀“年”(⼀共有14个⽉)的最后⼀天,成了d的⼀部分,于是平闰年的影响也去掉了,公式就简化成:
D = [ 13 * (M+1) / 5 ] - 7 + (M-1) * 28 + d.(3≤M≤14) (4)
上⾯计算星期⼏的公式,也就可以进⼀步简化成:
W = (Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + [ 13 * (M+1) / 5 ] - 7 + (M-1) * 28 + d.
因为其中的-7和(M-1)*28两项都可以被7整除,所以去掉这两项,W除以7的余数不变,公式变成:
W = (Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + [ 13 * (M+1) / 5 ] + d.(5)
当然,要注意1⽉和2⽉已经被当成了上⼀年的13⽉和14⽉,因此在计算1⽉和2⽉的⽇⼦的星期时,除了M要按13或14算,年份Y也要减⼀。
⽐如,2004年1⽉1⽇是星期四,⽤这个公式来算,有:
W = (2003-1) + [(2003-1)/4] - [(2003-1)/100] + [(2003-1)/400] + [13*(13+1)/5]
+ 1
= 2002 + 500 - 20 + 5 + 36 + 1
= 2524;
2524 / 7 = 360……4.这和实际是⼀致的。
公式(5)已经是从年、⽉、⽇来算星期⼏的公式了,但它还不是最简练的,对于年份的处理还有改进的⽅法。
我们先来⽤这个公式算出每个世纪第⼀年3⽉1⽇的星期,列表如下:
年份: 1(401,801,...,2001) 101(501,901, (2101)
--------------------------------------------------------------------
星期: 4 2
==============================================
年份:201(601,1001,...,2201) 301(701,1101, (2301)
--------------------------------------------------------------------
星期: 0 5
可以看出,每隔四个世纪,这个星期就重复⼀次。
假如我们把301(701,1101,…,2301)年3⽉1⽇的星期数看成是-2(按数论中对余数的定义,-2和5除以7的余数相同,所以可以做这样的变换),那么这个重复序列正好就是⼀个4,2,0,-2的等差数列。
据此,我们可以得到下⾯的计算每个世纪第⼀年3⽉1⽇的星期的公式:
W = (4 - C mod 4) * 2 - 4. (6)
式中,C是该世纪的世纪数减⼀,mod表⽰取模运算,即求余数。
⽐如,对于2001年3⽉1⽇,C=20,则:
W = (4 - 20 mod 4) * 2 - 4
= 8 - 4
= 4.
把公式(6)代⼊公式(5),经过变换,可得:
(Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] ≡ (4 - C mod 4) * 2 - 1
(mod 7). (7)
因此,公式(5)中的(Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400]这四项,在计算每个世纪第⼀年的⽇期的星期时,可以⽤(4 - C mod 4) * 2 - 1来代替。
这个公式写出来就是:
W = (4 - C mod 4) * 2 - 1 + [13 * (M+1) / 5] + d. (8)
有了计算每个世纪第⼀年的⽇期星期的公式,计算这个世纪其他各年的⽇期星期的公式就很容易得到了。
因为在⼀个世纪⾥,末尾为00的年份是最后⼀年,因此就⽤不着再考虑“⼀百年不闰,四百年⼜闰”的规则,只须考虑“四年⼀闰”的规则。
仿照由公式(1)简化为公式(2)的⽅法,我们很容易就可以从式(8)得到⼀个⽐公式(5)更简单的计算任意⼀天是星期⼏的公式:
W = (4 - C mod 4) * 2 - 1 + (y-1) + [y/4] + [13 * (M+1) / 5] + d. (9)
式中,y是年份的后两位数字。
如果再考虑到取模运算不是四则运算,我们还可以把(4 - C mod 4) * 2进⼀步改写成只含四则运算的表达式。
因为世纪数减⼀C 除以4的商数q和余数r之间有如下关系:
4q + r = C,
其中r即是 C mod 4,因此,有:
r = C - 4q
= C - 4 * [C/4]. (10)
则
(4 - C mod 4) * 2 = (4 - C + 4 * [C/4]) * 2
= 8 - 2C + 8 * [C/4]
≡ [C/4] - 2C + 1 (mod 7). (11)
把式(11)代⼊(9),得到:
W = [C/4] - 2C + y + [y/4] + [13 * (M+1) / 5] + d - 1. (12)
这个公式由世纪数减⼀、年份末两位、⽉份和⽇数即可算出W,再除以7,得到的余数是⼏就表⽰这⼀天是星期⼏,唯⼀需要变通的是要把1⽉和2⽉当成上⼀年的13⽉和14⽉, C和y都按上⼀年的年份取值。
因此,⼈们普遍认为这是计算任意⼀天是星期⼏的最好的公式。
这个公式最早是由德国数学家克⾥斯蒂安·蔡勒(Christian Zeller, 1822-1899)在1886年推导出的,因此通称为蔡勒公式(Zeller's Formula)。
为⽅便⼝算,式中的[13 * (M+1) / 5]也往往写成[26 * (M+1) / 10]。
现在仍然让我们来算2004年5⽉1⽇的星期,显然C=20,y=4,M=5,d=1,代⼊蔡勒
公式,有:
W = [20/4] - 40 + 4 + 1 + [13 * (5+1) / 5] + 1 - 1
= -15.
注意负数不能按习惯的余数的概念求余数,只能按数论中的余数的定义求余。
为了⽅便计算,我们可以给它加上⼀个7的整数倍,使它变为⼀个正数,⽐如加上70,得到55。
再除以7,余6,说明这⼀天是星期六。
这和实际是⼀致的,也和公式(2)计算所得的结果⼀致。
最后需要说明的是,上⾯的公式都是基于公历(格⾥⾼利历)的置闰规则来考虑的。
对于儒略历,蔡勒也推出了相应的公式是:
W = 5 - C + y + [y/4] + [13 * (M+1) / 5] + d - 1. (13)
这样,我们终于⼀劳永逸地解决了不查⽇历计算任何⼀天是星期⼏的问题。