0. 本文的初衷及蔡勒公式的用處
前一段時間,我在準備北郵計算機考研複試的時候,作了幾道與日期計算相關的題目,在這個過程當中我接觸到了蔡勒公式。先簡單的介紹一下蔡勒公式是幹什麼用的。算法
咱們有時候會遇到這樣的問題:看到一個日期想知道這一天是星期幾,甚至看到一個歷史日期或記念日,咱們想快速的知道這一天是星期幾。對於這個問題,若是用編程的方式,應該怎麼實現呢?你可能已經有思路了,好比你知道某個日期是星期幾,把這個日期做爲原點,而後計算目標日期和這個原點之間相差多少天,再除以 7 求餘數,最後經過餘數判斷目標日期的星期數。經過這樣的過程,你確實能夠獲得正確的結果,但這不夠快速也不夠優雅。對於這個問題,若是你懂得蔡勒公式,那就變得異常簡單了,你只須要將年月日等數據代入公式,而後計算出結果,這一結果就是目標日期對應的星期數。編程
當我知道蔡勒公式以後,我以爲它很是有趣也很酷,因此我不只但願掌握公式的使用,也但願能夠掌握公式背後的推導過程。然而,當我在網上搜索相關的文章時,我發現幾乎全部向我展現的博客(從零幾年到最近的 19 年)大可能是轉載、複製於這篇文章(連接):數組
星期制度是一種有古老傳統的制度。聽說由於《聖經·創世紀》中規定上帝用了六天時間創世紀,第七天休息,因此人們也就以七天爲一個週期來安排本身的工做和生活,而星期日是休息日……markdown
這篇文章質量很不錯,講解過程天然流暢,可是在一些細節上存在錯誤,有些推導步驟讓人感到困惑。所以,當我掌握蔡勒公式後,很但願能夠將個人理解輸出出來,讓想要學習蔡勒公式推導過程的人看到一些新的材料。好了,廢話少說,咱們開始吧。函數
1. 蔡勒公式的形式
若是你對公式的推導過程不感興趣,只是但願使用蔡勒公式,那麼只看此小節便可。蔡勒公式的形式以下:
post
DW=[c4]−2c+y+[y4]+[13(m+1)5]+d−1=Dmod7D=[c4]−2c+y+[y4]+[13(m+1)5]+d−1W=Dmod7
其中:學習
- W 是星期數。
- c 是世紀數減一,也就是年份的前兩位。
- y 是年份的後兩位。
- m 是月份。m 的取值範圍是 3 至 14,由於某年的 一、2 月要看做上一年的 1三、14月,好比 2019 年的 1 月 1 日要看做 2018 年的 13 月 1 日來計算。
- d 是日數。
- [] 是取整運算。
- mod 是求餘運算。
注意:這些符號在後面的推導中還會使用。舉一個實際的計算例子:計算 1994 年 12 月 13 日是星期幾。顯然 c = 19,y = 94,m = 12,d = 13,帶入公式:
測試
DW=[194]−2×19+94+[944]+[13×(12+1)5]+13−1=4−38+94+23+33+13−1=128=128mod7=2D=[194]−2×19+94+[944]+[13×(12+1)5]+13−1=4−38+94+23+33+13−1=128W=128mod7=2
由此可得 1994 年 12 月 13 日是星期二,經過查詢日曆能夠驗證正確性。優化
最後關於蔡勒公式,還須要作兩點補充說明:
- 在計算機編程中,W 的計算結果有多是負數。咱們須要注意,數學中的求餘運算和編程中的求餘運算不是徹底相同的,數學上餘數不能是負數,而編程中餘數能夠是負數。所以,在計算機中 W 是負數時,咱們須要進行修正。修正方法十分簡單:讓 W 加上一個足夠大的 7 的整數倍,使其成爲正數,獲得的結果再對 7 取餘便可。好比 -15,我可讓其加上 70,獲得 55,再除以 7 餘 6,經過餘數可知這一天是星期六。
- 此公式只適用於格里高利曆(也就是如今的公曆)。關於曆法的問題,你們有興趣能夠自行查閱。
下面是公式的推導。
2. 推導過程
推導蔡勒公式以前,咱們先思考一下,若是咱們不知道這一公式,咱們如何將一個日期轉化爲星期數呢?
咱們可能會很天然地想到:先找到一個知道是星期幾的日子,把這個日期做爲「原點」,而後計算目標日期和這個原點相差幾天,將相差的天數對 7 取餘,再根據餘數判斷星期數。舉一個實際例子,好比咱們知道 2019 年 5 月 1 日是星期三,把這一天看成原點,如今咱們想知道 2019 年 5 月 17 日是星期幾,顯然這兩個日期之間相差 16 天,用 16 除 7 餘 2,由於原點是星期三,加上做爲偏移量的餘數 2,可知 2019 年 5 月 17 日是星期五。
從這個思路出發,通過優化擴展,咱們就能夠獲得神奇的蔡勒公式了。首先,若是咱們仔細觀察一下能夠發現,這個思路中比較麻煩的是計算相差天數(設爲 DD ),咱們能夠把 DD 的計算過程分解成三部分:
- D1D1 :從原點到原點所在年份末尾的天數。
- D2D2 :原點所在年份和目標日期所在年份之間全部年份的天數和。
- D3D3 :目標日期所在年份的第一天到目標日期的天數。

顯然,D=D1+D2+D3D=D1+D2+D3 。若是咱們把原點選擇在某一年的 12 月 31 日,那麼就能夠省去 D1D1 的計算了,由於原點剛好就是所在年份的最後一天。如今,D=D2+D3D=D2+D3 。
咱們再去觀察上述思路中的實際例子,能夠發現,由於原點是星期三,因此獲得餘數後,咱們須要加上 3 纔是正確的星期數。這啓示咱們能夠把原點選定在星期日,這樣算出來的餘數是幾就是星期幾(餘數 0 表明星期日)。
通過這樣的分析。咱們但願能夠優化原點的日期,使其知足下面兩個條件:
- 是某一年的 12 月 31 日。
- 是星期日。
咱們按照如今使用的公曆的規則逆推,能夠發現公元元年的前一年的 12 月 31 日剛好是星期日,知足咱們想要的兩個條件,是一個完美的原點。
如今假設目標日期是 y 年 m 月 d 日,咱們已經能夠很容易的計算 D2D2 了:
D2=(y−1)×365+[y−14]−[y−1100]+[y−1400]D2=(y−1)×365+[y−14]−[y−1100]+[y−1400]
簡單的解釋一下。由於一年最少有 365 天,因此 D2D2 至少是 (y−1)×365(y−1)×365 。此外,由於閏年比平年多一天,咱們還須要加上這些年份中閏年的數量。按照閏年的規則:每 4 年一閏,但每 100 年不閏,每 400 年又閏。可知閏年的數量爲 [y−14]−[y−1100]+[y−1400][y−14]−[y−1100]+[y−1400] 。
如今,咱們須要獲得 D3D3 的計算公式,這塊要複雜一些。首先,不考慮閏年的話,每一年中 2 月份天數最少,爲 28 天。所以,咱們不妨把每月的天數看做 「28 + Excess」的模式,m 月以前全部月份的 Excess 之和爲 Accum(m),則 D3=28×(m−1)+Accum(m)+dD3=28×(m−1)+Accum(m)+d ,而且咱們能夠獲得這樣一張表格:
天數 |
31 |
28 |
31 |
30 |
31 |
30 |
31 |
31 |
30 |
31 |
30 |
31 |
Excess |
3 |
0 |
3 |
2 |
3 |
2 |
3 |
3 |
2 |
3 |
2 |
3 |
Accum |
0 |
3 |
3 |
6 |
8 |
11 |
13 |
16 |
19 |
21 |
24 |
26 |
仔細觀察,能夠發現 Excess 從 3 月份開始是 三、二、三、二、3 的循環,所以,當 m≥3m≥3 時,Accum(m)Accum(m) 的值的增幅也是 三、二、三、二、3 的循環。由於每 5 個月增長 13,因此把 135135 做爲係數;由於 Accum(m)Accum(m) 的值是離散的(都是整數),因此咱們用取整運算符,獲得:
f(x)=[135x]f(x)=[135x]
咱們將 xx 的值取 1,2,3……,而後觀察 f(x)f(x) 的值,可得下面這張表格:
f(x) |
10 |
13 |
15 |
18 |
20 |
23 |
26 |
28 |
31 |
33 |
36 |
咱們能夠發現,當 x≥4x≥4 時,f(x)f(x) 的值的增幅也是 3,2,3,2,3 的循環。也就是說 f(x)f(x) 的值的增幅(x≥4x≥4 )與 Accum(m)Accum(m) 的值的增幅(m≥3m≥3 )相同,這意味着 f(x)f(x) 與 Accum(m)Accum(m) 之間相差一個常數,咱們隨便帶入一個具體的值計算:
f(4)−Accum(3)=10−3=7f(4)−Accum(3)=10−3=7
可知相差的常數爲 7。由此可得,當 m≥3m≥3 時,Accum(m)Accum(m) 的值的序列,等於當 x≥4x≥4 時,f(x)−7f(x)−7 的值的序列。這樣咱們就獲得了 Accum(m),m≥3Accum(m),m≥3 的函數形式:
Accum(m)=f(m+1)−7=[13(m+1)5]−7Accum(m)=f(m+1)−7=[13(m+1)5]−7
這裏多說兩句,實際上,Accum(m)Accum(m) 的函數形式是不惟一的,使用其餘的構造方法,能夠獲得形式不一樣的 Auccm(m)Auccm(m) ,只要符合要求便可。
進一步,咱們能夠獲得 D3D3 的函數形式:
D3=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪d,31+d,(m−1)×28+[13(m+1)5]−7+d+i,m=1m=2m≥3D3={d,m=131+d,m=2(m−1)×28+[13(m+1)5]−7+d+i,m≥3
其中,平年時,i=0i=0 ;閏年時,i=1i=1 。這還不是 D3D3 最完美的形式。咱們繼續分析,從 3 月份到 12 月份的 Excess 正好是兩個 三、二、三、二、3 的循環,那麼假若有第 13 月,想要繼續保持這種循環規律,13 月的 Excess 值應該是 3。而 1 月份的 Excess 的值剛好是 3,因此咱們不妨變通一下,把每一年的 1 月、2月看成上一年的 13月、14 月。這樣不只仍然符合公式,並且 2 月份變成了上一年的最後一個月,也就是公式中 dd 的部分,因而平閏年的影響也去掉了,D3D3 的公式簡化成了:
D3=(m−1)×28+[13(m+1)5]−7+d,3≤m≤14D3=(m−1)×28+[13(m+1)5]−7+d,3≤m≤14
如今,咱們已經獲得了 D2D2 和 D3D3 的計算函數,由 D=D2+D3D=D2+D3 ,可知:
D=(y−1)×365+[y−14]−[y−1100]+[y−1400]+(m−1)×28+[13(m+1)5]−7+d,3≤m≤14D=(y−1)×365+[y−14]−[y−1100]+[y−1400]+(m−1)×28+[13(m+1)5]−7+d,3≤m≤14
注意!這個公式離正確形式還差一小步。由於在當前的公式中,每一年的 1 月和 2 月被看成上一年的 13 月和 14 月計算,所以當前公式中計算閏日的部分([y−14]−[y−1100]+[y−1400][y−14]−[y−1100]+[y−1400] )存在錯誤。舉一個具體的例子,好比計算公元 4 年(閏年)3 月 1 日的星期數。在當前公式中,公元 4 年的 2 月被算做了公元 3 年的 14 月(換句話說公元 3 年變成了閏年),而公式中計算閏日的部分沒有考慮這點,依然將公元 3 年當成平年計算,所以少算了一天。所以,計算閏日的部分應當改進,公式以下:
D=(y−1)×365+[y4]−[y100]+[y400]+(m−1)×28+[13(m+1)5]−7+d,3≤m≤14(1)(1)D=(y−1)×365+[y4]−[y100]+[y400]+(m−1)×28+[13(m+1)5]−7+d,3≤m≤14
計算出 D 的值後,對 7 取模便可獲得星期數。
根據同餘定理,D 除以 7 所得的餘數等於 D 式的各項分別除以 7 所得餘數之和(餘數之和大於等於 7 時,再對 7 取餘),所以能夠消去 D 式中能被 7 整除的項,進行化簡:
D=(y−1)×365+[y4]−[y100]+[y400]+(m−1)×28+[13(m+1)5]−7+d=(y−1)×(364+1)+[y4]−[y100]+[y400]+[13(m+1)5]+d=(y−1)+[y4]−[y100]+[y400]+[13(m+1)5]+d(2)D=(y−1)×365+[y4]−[y100]+[y400]+(m−1)×28+[13(m+1)5]−7+d=(y−1)×(364+1)+[y4]−[y100]+[y400]+[13(m+1)5]+d(2)=(y−1)+[y4]−[y100]+[y400]+[13(m+1)5]+d
簡單說明一下:
(y−1)×365=(y−1)×(364+1)=(y−1)×364+(y−1)=(y−1)×52×7+(y−1)(y−1)×365=(y−1)×(364+1)=(y−1)×364+(y−1)=(y−1)×52×7+(y−1)
顯然,結果中的第一項是 7 的倍數,除以 7 餘數爲 0,所以 (y−1)×365(y−1)×365 除以 7 的餘數其實就等於 (y−1)(y−1) 除以 7 的餘數,咱們只保留 (y−1)(y−1) 就夠了。化簡過程當中,其餘被銷去的項同理。
公式(2)還不是最簡練的形式,咱們還能夠對年份進行處理。咱們如今用公式(2)計算出每一個世紀第一年 3 月 1 日的星期數,獲得以下結果:
能夠發現,每隔 4 個世紀,星期數就會重複一次。由於在數學上,-2 和 5 除以 7 的餘數相同,因此咱們不妨把這個重複序列中的 5 改成 -2。這樣,四、二、0、-2 剛好構成了一個等差數列。利用等差公式,咱們能夠獲得計算每一個世紀第一年的 3 月 1 日星期數的公式:
W=4−2(cmod4)(3)(3)W=4−2(cmod4)
其中,cc 是世紀數減一。咱們把公式(2)和公式(3)聯立,代入特定的日期——3 月 1 日,能夠獲得:
((y−1)+[y−14]−[y−1100]+[y−1400]+11)mod7=4−2(cmod4)((y−1)+[y−14]−[y−1100]+[y−1400]+11)mod7=4−2(cmod4)
利用同餘定理,通過變換獲得:
(y−1)+[y−14]−[y−1100]+[y−1400]≡−2(cmod4)(mod7)(4)(4)(y−1)+[y−14]−[y−1100]+[y−1400]≡−2(cmod4)(mod7)
其中,≡≡ 是表示同餘的符號,括號中 mod7mod7 的意思是指 ≡≡ 兩邊的數除以 7 獲得的餘數相同。根據公式(4),咱們能夠知道在每一個世紀的第一年,(y−1)+[y−14]−[y−1100]+[y−1400](y−1)+[y−14]−[y−1100]+[y−1400] 能夠被 −2(cmod4)−2(cmod4) 同餘替換。進而計算 DD 的公式獲得以下形式:
D=−2(cmod4)+[13(m+1)5]+d(5)(5)D=−2(cmod4)+[13(m+1)5]+d
注意!如今的計算公式只能適用於每一個世紀的第一年。可是,有個這個公式,再加上計算一個世紀中閏日的部分,咱們就能夠很容易地獲得計算這個世紀其餘年份的日期的星期數的公式了。令 c 等於世紀數減一,y 等於世紀中的年份數(如 1994 年,則 c = 19,y = 94)。由於一個世紀中只有一百年,因此不用考慮「四百年又閏」的狀況;由於每百年,即每一個世紀最後一年的 y = 00,而 [y4]y=0=0[y4]y=0=0 ,因此 [y4][y4] 既能夠計算四年一閏的狀況,又知足百年不閏的要求 。綜合這些狀況,與獲得公式(2)的過程相似,咱們能夠獲得:
D=−2(cmod4)+(y−1)+[y4]+[13(m+1)5]+d(6)(6)D=−2(cmod4)+(y−1)+[y4]+[13(m+1)5]+d
在公式(6)中,yy 是年份的後兩位。
最後,咱們來把公式中的取模運算改爲四則運算。設商爲 qq ,餘數爲 rr ,則:
4q+r=c4q+r=c
又由於,
qr=[c4]=cmod4q=[c4]r=cmod4
可得:
cmod4=c−4×[c4]cmod4=c−4×[c4]
代入公式(6)可得:
D=[c4]−2c+y−1+[y−14]+[13(m+1)5]+d(7)(7)D=[c4]−2c+y−1+[y−14]+[13(m+1)5]+d
至此,咱們就獲得了蔡勒公式的最終形式。
==============================================================================================
下面咱們徹底按本身的思路由簡單到複雜一步步進行推導……
推導以前,先做兩項規定:
①用 y, m, d, w 分別表示 年 月 日 星期(w=0-6 表明星期日-星期六
②咱們從 公元0年1月1日星期日 開始
1、只考慮最開始的 7 天,即 d = 1---7 變換到 w = 0---6
很直觀的獲得:
w = d-1
2、擴展到整個1月份
模7的概念你們都知道了,也沒什麼好多說的。不過也能夠從咱們日常用的日曆中看出來,在周曆裏邊每列都是一個按7增加的等差數列,如一、八、1五、22的星期都是相同的。因此獲得整個1月的公式以下:
w = (d-1) % 7 --------- 公式⑴
3、按年擴展
因爲按月擴展比較麻煩,因此將年擴展放在前面說
① 咱們不考慮閏年,假設每年都是 365 天。因爲365是7的52倍多1天,因此每年的第一天和最後一天星期是相同的。
也就是說下一年的第一天與上一年的第一天星期滯後一天。這是個重要的結論,每過一年,公式⑴會有一天的偏差,因爲咱們是從0年開始的,因此只需要簡單的加上年就能夠修正擴展年引發的偏差,獲得公式以下:
w = (d-1 + y) % 7
② 將閏年考慮進去
每一個閏年會多出一天,會使後面的年份產生一天的偏差。如咱們要計算2005年1月1日星期幾,就要考慮前面的已通過的2004年中有多少個閏年,將這個偏差加上就能夠正確的計算了。
根據閏年的定義(能被4整但不能被100整除或能被400整),獲得計算閏年的個數的算式:y/4 - y/100 + y/400。
因爲咱們要計算的是當前要計算的年以前的閏年數,因此要將年減1,獲得了以下的公式:
w = [d-1+y + (y-1)/4-(y-1)/100+(y-1)/400] % 7 -----公式⑵
如今,咱們獲得了按年擴展的公式⑵,用這個公式能夠計算任一年的1月份的星期
4、擴展到其它月
考慮這個問題頗費了一翻腦筋,後來仍是按前面的方法大膽假才找到突破口。
①如今咱們假設每月都是28天,且不考慮閏年
有了這個假設,計算星期就太簡單了,由於28正好是7的整數倍,每月的星期都是同樣的,公式⑵對任一個月都適用 :)
②但假設終究是假設,首先1月就不是28天,這將會形成2月份的計算偏差。1月份比28天要多出3天,就是說公式⑵的基礎上,2月份的星期應該推後3天。
而對3月份來講,推後也是3天(2月正好28天,對3月的計算沒有影響)。
依此類推,每月的計算要將前面幾個月的累計偏差加上。
要注意的是偏差隻影響後面月的計算,由於12月已經是最後一個月,因此不用考慮12月的偏差天數,同理,1月份的偏差天數是0,由於前面沒有月份影響它。
由此,想到創建一個偏差表來修正每月的計算。
==================================================
月 偏差 累計 模7
1 3 0 0
2 0 3 3
3 3 3 3
4 2 6 6
5 3 8 1
6 2 11 4
7 3 13 6
8 3 16 2
9 2 19 5
10 3 21 0
11 2 24 3
12 - 26 5
(閏年時2月會有一天的偏差,但咱們如今不考慮)
==================================================
咱們將最後的偏差表用一個數組存放
在公式⑵的基礎上能夠獲得擴展到其它月的公式
e[] = {0,3,3,6,1,4,6,2,5,0,3,5}
w = [d-1+y + e[m-1] + (y-1)/4-(y-1)/100+(y-1)/400] % 7 --公式⑶
③上面的偏差表咱們沒有考慮閏年,若是是閏年,2月會一天的偏差,會對後面的3-12月的計算產生影響,對此,咱們暫時在編程時來修正這種狀況,增長的限定條件是若是當年是閏年,且計算的月在2月之後,須要加上一天的偏差。大概代碼是這樣的:
w = (d-1 + y + e[m-1] + (y-1)/4 - (y-1)/100 + (y-1)/400);
if(m>2 && (y%4==0 && y%100!=0 || y%400==0) && y!=0)
++w;
w %= 7;
如今,已經能夠正確的計算任一天的星期了。
注意:0年不是閏年,雖然如今大都不用這個條件,但咱們因從公元0年開始計算,因此這個條件是不能少的。
④ 改進
公式⑶中,計算閏年數的子項 (y-1)/4-(y-1)/100+(y-1)/400 沒有包含當年,若是將當年包含進去,則實現了若是當年是閏年,w 自動加1。
由此帶來的影響是若是當年是閏年,1,2月份的計算會多一天偏差,咱們一樣在編程時修正。則代碼以下
w = (d-1 + y + e[m-1] + y/4 - y/100 + y/400); ---- 公式⑷
if(m<3 && (y%4==0 && y%100!=0 || y%400==0) && y!=0)
--w;
w %= 7;
與前一段代碼相比,咱們簡化了 w 的計算部分。
實際上還能夠進一步將常數 -1 合併到偏差表中,但咱們暫時先不這樣作。
至此,咱們獲得了一個階段性的算法,能夠計算任一天的星期了。
public class Week {
public static void main(String[] args){
int y = 2005;
int m = 4;
int d = 25;
int e[] = new int[]{0,3,3,6,1,4,6,2,5,0,3,5};
int w = (d-1+e[m-1]+y+(y>>2)-y/100+y/400);
if(m<3 && ((y&3)==0 && y%100!=0 || y%400==0) && y!=0){
--w;
}
w %= 7;
System.out.println(w);
}
}
5、簡化
如今咱們推導出了本身的計算星期的算法了,但還不能稱之爲公式。
所謂公式,應該給定年月往後能夠手工算出星期幾的,但咱們如今的算法須要記住一個偏差表才能進行計算,因此只能稱爲一種算法,還不是公式。
下面,咱們試圖消掉這個偏差表……
=============================
消除閏年判斷的條件表達式
=============================
因爲閏年在2月份產生的偏差,影響的是後面的月份計算。若是2月是排在一年的最後的話,它就不能對其它月份的計算產生影響了。可能已經有人聯想到了文章開頭的公式中爲何1,2月轉換爲上年的13,14月計算了吧 :)
就是這個思想了,咱們也將1,2月看成上一年的13,14月來看待。
由此會產生兩個問題須要解決:
1>一年的第一天是3月1日了,咱們要對 w 的計算公式從新推導
2>偏差表也發生了變化,須要得新計算
①推導 w 計算式
1> 用前面的算法算出 0年3月1日是星期3
前7天, d = 1---7 ===> w = 3----2
獲得 w = (d+2) % 7
此式一樣適用於整個三月份
2> 擴展到每年的三月份
[d + 2 + y + (y-1)/4 - (y-1)/100 + (y-1)/400] % 7
②偏差表
==================================================
月 偏差 累計 模7
3 3 0 0
4 2 3 3
5 3 5 5
6 2 8 1
7 3 10 3
8 3 13 6
9 2 16 2
10 3 18 4
11 2 21 0
12 3 23 2
13 3 26 5
14 - 29 1
==================================================
③獲得擴展到其它月的公式
e[] = {0,3,5,1,3,6,2,4,0,2,5,1}
w = [d+2 + e[m-3] +y+(y-1)/4-(y-1)/100+(y-1)/400] % 7
(3 <= m <= 14)
咱們仍是將 y-1 的式子進行簡化
w = [d+2 + e[m-3] +y+y/4-y/100+y/400] % 7
(3 <= m <= 14)
這個式子若是當年是閏年,會告成多1的偏差
但咱們將1,2月變換到上一年的13,14月,年份要減1,因此這個偏差會自動消除,因此獲得下面的算法:
int e[] = new int[]{0,3,5,1,3,6,2,4,0,2,5,1};
if(m < 3) {
m += 12;
--y;
}
int w = (d+2 + e[m-3] +y+(y/4)-y/100+y/400) % 7; -----公式⑸
咱們能夠看到公式⑸與公式⑷幾乎是同樣的,僅僅是偏差天和一個常數的差異
常數的區別是由起始日期的星期不一樣引發的,0年1月1日星期日,0年3日1日星期三,有三天的差異,因此常數也從 -1 變成了 2。
如今,咱們成功的消除了繁瑣的閏年條件判斷。
=============================
消除偏差表
=============================
假如存在一種m到e的函數映射關係,使得
e[m-3] = f(m)
則咱們就能夠用 f(m) 取代公式⑸中的子項 e[m-3],也就消除了偏差表。
因爲偏差表只有12個項,且每一項均可以加減 7n 進行調整,這個函數關係是能夠拼湊出來的。可是這個過程多是極其枯燥無味的,我如今不想本身去推導它,我要利用前人的成果。所謂前人栽樹,後人乘涼嘛 :)
文章開頭開出的公式中的 2*m+3*(m+1)/5 這個子項引發了個人興趣
通過屢次試試驗,我運行下面的代碼
for(m=1; m<=14; ++m)
System.out.print((-1+2*m+3*(m+1)/5)%7 + " ");
System.out.println();
天哪,輸出結果與個人偏差表不謀而合,成功了,哈哈
2 4 0 3 5 1 3 6 2 4 0 2 5 1
Press any key to continue...
上面就是輸出結果,看它後面的12項,與個人偏差表徹底吻合!!!
如今就簡單的,將 f(m) = -1 + 2*m + 3*(m+1)/5 代入公式⑸,獲得
w = (d+1+2*m+3*(m+1)/5+y+(y/4)-y/100+y/400) % 7 ----公式6
約束條件: m=1,m=2 時 m=m+12,y=y-1;
如今,咱們獲得了通用的計算星期的公式,而且「徹底」是按本身的思想推導出來的(那個函數映射關係不算),只要理解了這個推導的步驟,即便有一天忘記了這個公式,也能夠從新推導出來!
可能有人會注意到公式⑹與文章開頭的公式相差一個常數 1,這是由於原公式使用數字0--6表示星期一到星期日,而我用0--6表示星期日到星期六。其實是同樣,你能夠改爲任意你喜歡的表示方法,只需改變這個常數就能夠了。
6、驗證公式的正確性。
一個月中的日期是連續的,只要有一天對的,模7的關係就不會錯,因此一個月中只須驗證一天就能夠了,一天須要驗12天。因爲擴展到年和月只跟是否閏年有關係,就是說至少要驗證一個平年和一個閏年,也就是最少得驗證24次。
我選擇了 2005 年和 2008 年,驗證每月的1號。
測試代碼以下:
class test {
public int GetWeek(int y, int m, int d) {
if(m<3) {
m += 12;
--y;
}
int w = (d+1+2*m+3*(m+1)/5+y+(y>>2)-y/100+y/400) % 7;
return w;
}
}
public class Week {
public static void main(String[] args){
int y = 2005;
int m = 1;
int d = 1;
test t = new test();
String week[] = new String[]{
"星期日","星期一","星期二","星期三","星期四","星期五","星期六"
};
for(y=2005; y<=2008; y+=3) {
for(m=1; m<=12; ++m) {
String str = y + "-" + m + "-" + d + "\t" + week[t.GetWeek(y,m,d)];
System.out.println(str);
}
}
}
}
查萬年曆,檢查程序的輸出,徹底正確。
7、後話
咱們這個公式的推導是以0年3月1日爲基礎的,對該日之後的日期都是能夠計算的。可是否能夠擴展到公元前(1,2已屬於公元前1年的13,14月了)呢?
雖然我對0年1月和2月、以及公元前1年(令y=-1)的12月做了驗證是正確的,但我在推導這個公式時並未想到將其擴展到公元前,因此上面的推導過程沒有足夠理論依據能夠證實其適用於公元前。(負數的取模在不一樣的編譯器如C++中好象處理並不徹底正確)。
另一有點是對於0年是否存在的爭議,一種折中的說法是0年存在,但什麼也沒有發生,其持續時間爲0。還有在羅馬的格利戈裏曆法中有10天是不存的(1582年10月5日至14持續時間爲0),英國的歷法中有11天(1752年9月3日至13日)是不存在的。感興趣的朋友能夠看看這裏:
http://www.whtv.com.cn/zhuanti/celebration/when/wz16.htm
也能夠參考個人blog裏的文章:
http://www.cnblogs.com/mq0036/p/3534186.html
可是咱們作的是數字計算,無論那一天是否存在,持續的時間是24小時仍是23小時甚至是0小時,只要那個號碼存在,就有一個星期與之對應。因此這個公式仍然是適用的。
若是要計算的是時間段,就必須考慮這個問題了。