Zeller公式用于计算给定日期是星期几。
该方法可以用数论知识进行证明。
假设给定日期Date为Year-Month-Day,求解该日期是星期几的问题实际上就是以之前某个确定星期几的日期作为参考点,计算它们之间的总天数,然后再除以7取余数,即可算出指定日期是星期几。
这里,我们将选择公元0年12月31日作为参考点。原则原因是:1、公元1年1月1日是周1;2、选择一年的末尾,便于以整年计算日期,防止过多分段计算带来的麻烦。
由上可得,参考日期到给定日期的将分成两部分:PastYearsDays(过去的整数年天数)和PresentYearDays(指定年已流逝的天数);
则可以得到,WholeDays = PastYearsDays + PresentYearDays。
参考日期到指定日期经过的闰年数为:LeapYearsNum = [(Year - 1) / 4] - [(Year - 1) / 100] + [(Year - 1) / 400]。
由上可得到,PastYearsDays = (Year - 1) * 365 + LeapYearsNum。
又因为(Year - 1) * 365 = (Year - 1) * ( 7 * 52 + 1) ≡ Year - 1 (mod 7),故可得到,PastYearsDays ≡Year - 1 + LeapYearsNum。
因此,可得到WholeDays = PastYearsDays + PresentYearDays ≡ Year - 1 + LeapYearsNum + PresentYearDays (mod 7)。
接来下,我们开始计算PresentYearDays:
下表分别展示了每个月的天数:
Month | Jan. | Feb. | Mar. | Fri. | May. | Jun. | Jul. | Aug. | Sep | Oct. | Nov. | Dec. |
Days | 31 | 28(29) | 31 | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31 |
又因为离28、29、30、31最近的7的倍数是28 = 4 * 7。故,当每个月的天数都减去28后可得如下表格:
Month | Jan. | Feb. | Mar. | Fri. | May. | Jun. | Jul. | Aug. | Sep | Oct. | Nov. | Dec. |
LeftDays | 3 | 0(1) | 3 | 2 | 3 | 2 | 3 | 3 | 2 | 3 | 2 | 3 |
CommonYearAcc | 3 | 3 | 6 | 8 | 11 | 13 | 16 | 19 | 21 | 24 | 26 | 29 |
LeapYearAcc | 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。根据循环数列的规律(该理论将在另一博文中描述),可得到如下表述:
当Month = 1时,PresentYearDays = Day;
当Month = 2时,PresentYearDays = 31 + Day;
当Month ≥ 3时,[13 * (Month + 1) / 5] - 7 + (Month - 1) * 28 + Day + i,其中,当闰年时i = 1,当平年时i = 0.
将1月和2月当做上一年的“13月”和“14月”,最终上边三个公式将合并为以下公式:
PresentYearDays = [13 * (Month + 1) / 5] - 7 + (Month - 1) * 28 + Day (3 ≤ Month ≤ 14)
最终,WholeDays将变成:
WholeDays ≡ (Year - 1) + [(Year - 1) / 4] - [(Year - 1) / 100] + [(Year - 1) / 400] + [13 * (Month +1) / 5 ] - 7 + (Month-1) * 28 + Day (mod 7)
≡ (Year - 1) + [(Year - 1) / 4] - [(Year - 1) / 100] + [(Year - 1) / 400] + [13 * (Month +1) / 5 ] + Day (mod 7)
注意:当将1月和2月当做13月和14月计算的时候,年份也得减一。
又因为每隔四个世纪,星期就重复一次。
故我们可容易得到每个世纪的第一个月(3月1日)的计算公式:
PastYearsDays‘ ≡ (4 - (Century - 1) mod 4) * 2 - 4 (mod 7)
其中Century为给定时期所在的世纪。
最终得到计算每隔世纪第一年的星期几的公式:
WholeDays’ ≡ (4 - (Century - 1) mod 4) * 2 - 1 + [13 * (Month + 1) / 5] + Day
接下来,我们就要求在一个世纪内的任意天的星期。显然在一个世纪内,只需考虑能被4整除的年份即是闰年LeapYear,根据此规则,最终我们得到计算任意一天星期几的公式:
WholeDays ≡ (4 - (Century - 1) mod 4) * 2 - 1 + (Year - 1) + [Year / 4] + [13 * (Month + 1) / 5] + Day
假设C = Century - 1,根据数论知识,我们又可以得到如下公式:
4q + r = C,其中q为商,r为余数。
⇒ r = C - 4 * [C / 4]
⇒ (4 - C mod 4) * 2 = (4 - C + 4 * [C / 4]) * 2
= 8 - 2C + 8 * [C / 4]
≡ [C / 4] - 2C + 1 (mod 7)
最终,由上式,我们可以得到
WholeDay ≡ [C/4] - 2C + Year + [Year / 4] + [13 * (Month + 1) / 5] + Day - 1 (mod 7)
代码示例:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace TestOne 7 { 8 /// <summary> 9 /// Used to record Date. 10 /// </summary> 11 class LocalDate 12 { 13 public int m_nYear; 14 public int m_nMonth; 15 public int m_nDay; 16 17 public LocalDate(int year, int month, int day) 18 { 19 m_nYear = year; 20 m_nMonth = month; 21 m_nDay = day; 22 } 23 24 public LocalDate() 25 { 26 m_nYear = 1980; 27 m_nMonth = 1; 28 m_nDay = 1; 29 } 30 31 /// <summary> 32 /// Used to calculate what day it is. 33 /// </summary> 34 /// <param name="localDate"></param> 35 /// <returns></returns> 36 public static int CalWhatDay(ref LocalDate localDate) 37 { 38 int year = localDate.m_nYear; 39 int month = localDate.m_nMonth; 40 int day = localDate.m_nDay; 41 if (month < 3) 42 { 43 year -= 1; 44 month += 12; 45 } 46 47 int c = year / 100, y = year - 100 * c; 48 int w = (c / 4) - 2 * c + y + (y / 4) + (26 * (month + 1) / 10) + day - 1; 49 w = (w % 7 + 7) % 7; 50 return w; 51 } 52 } 53 class Program 54 { 55 static void Main(string[] args) 56 { 57 LocalDate localDate = new LocalDate(2014, 3, 12); 58 int whatDay = LocalDate.CalWhatDay(ref localDate); 59 Console.WriteLine("Date: {0}-{1}-{2} is {3}", localDate.m_nYear, localDate.m_nMonth, localDate.m_nDay, PrintWahtDay(whatDay)); 60 Console.ReadKey(); 61 } 62 63 private static string PrintWahtDay(int whatDay) 64 { 65 switch(whatDay) 66 { 67 case 0: return "Sunday"; 68 case 1: return "Monday"; 69 case 2: return "Tuesday"; 70 case 3: return "Wednesday"; 71 case 4: return "Thursday"; 72 case 5: return "Friday"; 73 case 6: return "Saturday"; 74 default: return "Invalid number"; 75 } 76 } 77 } 78 }