学习一下如何根据指定的年月日快速计算星期几,由于公历的规律性很强,将所有规律整理并不断简化就可以得到这种简便算法,有很多不同的变形算法,例如蔡勒公式,康威裁决日算法等,本文主要学习蔡勒公式。

公历

简要回顾一下公历:每年12个月,每个月可能有28、29、30、31天。 区分普通的年和闰年,非闰年有365天,闰年有366天,年份满足以下情况时为闰年:

  • 年份除以100的余数不为0,除以4的余数为0
  • 年份除以100的余数为0,并且除以400的余数为0

非闰年的12个月的天数依次为:

  • 28天:2月
  • 30天:4月、6月、9月、11月
  • 31天:1月、3月、5月、7月、8月、10月、12月

闰年的12个月的天数依次为:

  • 29天:2月
  • 30天:4月、6月、9月、11月
  • 31天:1月、3月、5月、7月、8月、10月、12月

由于公历于1582年10月正式建立,在这段时间以及更早的年份可能存在日期修正,细节比较复杂,不作讨论。

蔡勒公式

公式如下 \[ w = \left( y + \left[ \frac{y}4 \right] + \left[ \frac{c}4 \right] - 2 c + \left[ \frac{13(m+1)}{5} \right] + d - 1 \right) \mod 7 \]

其中:

  • \(w\): 计算得到的星期几,取值范围是 \(0 - 6\),对应星期日到星期六
  • \(c\): 年份除以100的商,也就是年份的前两位数
  • \(y\): 年份除以100的余数,也就是年份的后两位数
  • \(m\):月份,注意取值范围为 \(3 - 14\),也就是说某年的 1 月和 2 月被视作前一年的 13 月和 14 月。
  • \(d\):日

例如:2006年4月4日,代入公式计算 \[ \begin{aligned} w ={}& \left( y + \left[ \frac{y}4 \right] + \left[ \frac{c}4 \right] - 2 c + \left[ \frac{13(m+1)}{5} \right] + d - 1 \right) \mod 7 \\ ={}& \left( 6 + \left[ \frac{6}4 \right] + \left[ \frac{20}4 \right] - 2 * 20 + \left[ \frac{13 * (4+1)}{5} \right] + 4 - 1 \right) \mod 7 \\ ={} & (-12) \mod 7 \\ ={} & 2 \end{aligned} \] 因此是星期二。

例如:2025年4月6日,代入公式计算 \[ \begin{aligned} w ={}& \left( y + \left[ \frac{y}4 \right] + \left[ \frac{c}4 \right] - 2 c + \left[ \frac{13(m+1)}{5} \right] + d - 1 \right) \mod 7 \\ ={}& \left( 25 + \left[ \frac{25}4 \right] + \left[ \frac{20}4 \right] - 2 * 20 + \left[ \frac{13 * (4+1)}{5} \right] + 6 - 1 \right) \mod 7 \\ ={} & 14 \mod 7 \\ ={} & 0 \end{aligned} \] 因此是星期日。

代码实现

在编程实现时,需要注意使用的整除对负数的处理,因为计算结果可能是负数,模7运算需要保证结果为0-6。

具体代码非常简单,几行代码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def weekday_str(year, month, day):
if month < 3:
month += 12
year -= 1

c = year // 100
y = year % 100
m = month
d = day

w = (y + y // 4 + c // 4 - 2 * c + (13 * (m + 1)) // 5 + d - 1) % 7

days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
]
return days[w]

例如

1
2
weekday_str(2025, 4, 6)
# 'Sunday'

简单的理解

由于公历的置闰规律很明确,不考虑计算效率的前提下,算法的思路其实是非常清晰的:

  • 找一个理想的基准日期,已知当天的星期,最好是星期天
  • 计算当前日期与基准相差的天数
  • 将相差的天数对 7 取模,就可以知道当前日期是星期几

由于2月的天数频繁变动,非常不好处理,最好从3月开始计算,将2月放在最后。

然后可以从这个直观的算法开始,不断寻找规律,简化运算,就可以得到优化后的算法了。