
2026年09月22日星期二——单看这个标题没有任何附带说明连个冒号都省了。但作为一个常年跟日期打交道的人我第一反应不是哦这是一个日期而是这个组合到底隐藏着什么需求谁会专门把一个日期和星期拿出来当标题大概率是这几类情况之一写日历组件要做测试用例做排期需要确定某一天是不是某个星期或者就是拿到了一组日期星期的数据需要校验。不管哪一种落脚点都是同一个核心问题——怎么准确算出一周中的某一天。这个日期本身还挺有意思2026年9月22日确实是个星期二。我用三种方法交叉验证过蔡勒公式、康威末日算法、Python 的 datetime 模块结果完全一致。这三种方法正好代表了三种不同的能力纸笔计算、纯心算、程序化处理。这篇文章就按这条主线展开先解决为什么是星期二的原理问题再落到代码和办公工具里的具体操作最后讲清楚实际排期和系统开发中最容易踩的坑。适合谁读呢写过业务代码、被日期函数折磨过的开发者做项目排期、活动日历、周期性任务的运营和项目经理还有单纯想搞清楚日历背后的数学的普通读者。这篇文章不会停留在查一下日历就知道了而是让你从原理到实现都能自己搞定。1. 日期推算的应用场景与核心思路1.1 哪些场景真正依赖星期信息先说个扎心的现实我们平时口头说的下周二每月第一个星期五在计算机眼里都不是自然语言而是需要被精确翻译的日期表达式。系统里但凡出现这类需求就绕不开星期推算。最典型的场景有三个。第一个是排期和日程。比如运营要做每周二晚上八点直播如果负责写配置的同事直接填了日期列表那每周都要改一次正确做法是让程序算出未来所有星期二这就要用到星期推算。第二个是财务和结算。很多规则按工作日、周末、月底走比如合同到期日是某个星期四之前的最后一个工作日没有星期信息就完全没法落库。第三个是日历组件本身。我在项目里实现过自定义日历核心逻辑就是输入年月输出这个月每天对应星期几然后才能决定怎么排列格子。这几个场景的共同点都是把人脑里的星期概念翻译成机器能计算的整数关系。1.2 为什么要有多种算法路线选择的逻辑有人会问既然编程语言里都有现成的函数为什么还要学手算我的回答是第一调试依赖库时你必须能验证库算得对不对第二很多轻量级环境根本没有完整的时间库第三理解了原理之后你才能准确说出为什么1900年不是闰年为什么 JavaScript 的 getMonth() 从0开始这类面试和实战都常考的问题。手算路线大致有三条。蔡勒公式是一套成熟的代数公式给出一串输入就能得到结果适合有纸笔的场合康威末日算法是纯心算流派靠着十几个固定日期做锚点熟练后两分钟内就能算出任意日期的星期高斯算法和蔡勒本质是同一套数学结构的变体只是把计算摊在不同系数上。这三条没有谁绝对更好选哪条取决于你当时的场景公式最稳末日算法最快代码实现最不易错。我在后面会各给一个例子全部用2026年9月22日来跑方便对照。2. 手推星期三套经典算法逐行拆解2.1 蔡勒公式纸笔可算的通用解先上公式。蔡勒公式Zellers Congruence适用于格里高利历也就是我们现在用的公历形式是h (q ⌊13(m1)/5⌋ K ⌊K/4⌋ ⌊J/4⌋ 5J) mod 7看这个公式眼睛发酸是正常的我第一次接触也这样。我们逐个拆变量q 是日期里的日9月22日就取22m 是月这里有一个容易忽视的关键规则——1月和2月要当作上一年的13月和14月来算因为公式里的 m 是从3月起算的这是为了让3月作为每年第一个月在数学上更对称K 是年份的后两位2026年就是26J 是年份的前两位也就是世纪数减一2026年就是20。公式最后对7取模因为一周七天这个 mod 7 就是把所有日期对应关系压缩进一个循环周期。把2026年9月22日代进去算一遍q 22m 9K 26J 20逐步计算⌊13×(m1)/5⌋ ⌊13×10/5⌋ 26⌊K/4⌋ ⌊26/4⌋ 6⌊J/4⌋ ⌊20/4⌋ 55J 100累加22 26 26 6 5 100 185185 mod 77×26 182余3。蔡勒公式里 h 的映射是0代表星期六1代表星期日2代表星期一3代表星期二。所以余数3对应的就是星期二和标题对上了。这里有一个细节值得展开为什么1月2月要当成上一年的13月14月因为公式的核心思想是让每个月的天数差尽量规整而2月因为闰年问题多出一天如果把它放在循环末尾即14月闰年带来的额外一天只会影响3月之后的结果处理起来更干净。平台上很多现成的蔡勒实现坑都出在它处理1月2月时忘了把年份减一导致元旦附近的日期全部算错。2.2 康威末日算法适合心算的简洁路线如果你觉得蔡勒公式的系数太绕可以试试约翰·康威设计的末日算法Doomsday Algorithm。它的核心思想很巧妙每一年都有一批末日日期这些日期在一个月内对应的星期是完全相同的记住了这批日期再数偏移量就行。先给结论2026年的末日是星期六。算出它再数到9月22日就完成了推算。末日算法分三步走。第一步记住一个世纪的锚点日2000年代锚点是星期二1900年代是星期三1800年代是星期五2100年代是星期日。第二步用年份的后两位推当年的末日。规则是用年份后两位数除以12取商和余数再把商、余数、以及余数除以4的商三数相加然后对7取模最后用这个差值去偏移锚点日。2026年的后两位是2626÷122余数22÷40所以三数和是2204。锚点星期二往后数4天就是星期六。第三步找一个离目标日期最近的末日日期来数偏移量。我常用的末日日期是4月4日、6月6日、8月8日、10月10日、12月12日以及5月9日、9月5日、7月11日、11月7日它们落在2026年都是星期六。9月5日是星期六9月22日比它晚17天。17 mod 7 3星期六往后数3天就是星期二。整套过程熟练之后二十秒内可以完成完全不需要纸笔。这个方法在业务验证上特别好用你手上没有电脑又想让别人相信2026年9月22日确实是星期二心算这一套比掏公式更有说服力。2.3 高斯算法与辅助口诀补充视角除了上面两套高斯也提出过类似的星期公式核心逻辑和蔡勒基本相通只是把计算分摊到了不同的常系数上。实务中我很少单独用高斯算法因为它既不比蔡勒简单也不比末日算法好记但了解一下有好处很多老代码里会看到高斯风格的写法比如对月份查一张权重表而不是直接算13(m1)/5。如果你在维护旧代码遇到这种写法至少要知道它到底在干什么。我还想提两个辅助技巧。一是用指节判断每个月的天数凸起的关节代表31天凹陷处代表30天2月单独记这能在心算日期差时少出错。二是基准日法找几个你永远不会忘的日期当参照比如元旦、生日、某个纪念日把陌生日期换算成和参照日差几天再 mod 7。这类技巧不替代算法但对提高日常手算速度和准确率很有帮助。3. 代码与工具验证把星期二跑出来3.1 Python官方库三行搞定顺便实现一个蔡勒到了代码层面Python 是最省心的语言标准库的 datetime 模块直接给答案from datetime import date d date(2026, 9, 22) print(d.weekday()) # 1周一0所以1代表星期二 print(d.strftime(%A)) # Tuesday区域设置为英文时输出英文三行代码就完成了验证。不过在生产环境里我建议你把取星期这件事封装成一个函数统一处理项目里对星期的各种口味有人要0-6的整数有人要中文星期二有人要英文缩写Tue。真实项目中日期星期的表示方式混乱往往是低级 bug 的主要来源后面4.2节会细讲。如果你想亲自验证蔡勒公式在代码里怎么写这里有一个最小实现def zeller(year, month, day): if month 3: month 12 year - 1 K year % 100 J year // 100 h (day 13 * (month 1) // 5 K K // 4 J // 4 5 * J) % 7 # 0周六, 1周日, 2周一, 3周二, 4周三, 5周四, 6周五 return h print(zeller(2026, 9, 22)) # 3对应星期二这个实现里有两个容易写错的点一是 month3 的提前处理必须同时把 year 减1二是整除符号Python 里是 //如果你写成单斜杠浮点数混进来之后 mod 7 的结果会变得非常诡异。我之前帮同事排查过一个只在特定月份算错的 bug最后发现就是蔡勒实现里的整除类型问题。3.2 JavaScript前端最常见的两个坑JavaScript 的 Date 对象用起来比 Python 门槛高一些主要因为两个历史包袱。第一个是月份从0开始。new Date(2026, 9, 22) 代表的不是9月22日而是10月22日。这就是我开头说的为什么 getMonth() 从0开始——它是从 C 语言的 struct tm 继承下来的设计月范围是0-11。所以正确写法是 new Date(2026, 8, 22) 才代表9月22日。这个坑在每一轮9月变10月的测试用例里必然出现一次。const d new Date(2026, 8, 22); // 月份0-118代表9月 console.log(d.toLocaleDateString(en-US, { weekday: long })); // Tuesday第二个坑是时区。new Date(2026, 8, 22) 创建的是本地时区的零点如果你在服务器端用 UTC 时间做字符串解析比如 new Date(2026-09-22)那么这个字符串会被当成 UTC 零点在非零时区环境下可能显示成前一天。跨时区系统里我建议明确使用 Date.UTC() 或者直接操作年月日整数别在图省事时把 ISO 字符串丢进 Date 构造函数。3.3 Excel 与命令行不写代码的查法对不写代码的同事Excel 其实是最快的工具。在任意单元格里输入TEXT(DATE(2026,9,22),aaaa)会返回星期二因为 aaaa 是中文星期格式如果想输出英文 Tuesday就用 dddd。如果只想拿数字做后续判断WEEKDAY(DATE(2026,9,22),2)会返回2因为第二个参数指定了周一1周日7的规则星期二就是2。Linux 和 macOS 自带 date 命令也可以直接验证date -d 2026-09-22 %A输出 Tuesday 或者你系统本地化后的星期名。注意 macOS 的 BSD date 语法略有不同要写成 date -j -f %Y-%m-%d 2026-09-22 %A。这类系统命令在写脚本、做自动化巡检时很实用比如定时任务想避开周末可以先跑一句 date 判断今天是星期几。4. 真实业务中的应用排期、规律与批量计算4.1 反推场景每月第几个星期二是哪天前面讲的都是给定日期求星期实际业务里经常反过来已知星期几要算出具体日期。比如每个月第4个星期二开会2026年9月的第4个星期二是哪一天这类问题的标准解法是先找到当月1号的星期再算偏移。2026年9月1日是星期二那么第一周的第一个星期二就是1号往后每隔7天加一个9月1日、8日、15日、22日、29日。所以第4个星期二正是9月22日——看绕了一圈又回到了标题本身。所以说这个标题可能本身就是一个排期题。在代码里写通用逻辑时可以用这条规则当月第一个星期X的日期 1 (X - 当月1日的星期 7) mod 7然后再加 7×(n-1) 就是第 n 个星期X。我在做周期性活动排期时经常用这个公式写成 SQL 或 Python 都很快比硬编码日期列表健壮得多新增一个月也不用改配置。4.2 不同语言/环境里星期二的表示差异星期二在程序世界里至少有以下几种表示搞混了必出 bug表示方式示例特点整数英文习惯1Python date.weekday()周一0整数ISO 86012周一1周二2周日7整数JS 习惯2Date.getDay()周日0英文全称Tuesdaystrftime 的 %A英文缩写Tuestrftime 的 %a中文全称星期二Excel 的 aaaa我见过最典型的事故是后端用 Python 的 weekday() 返回1表示周二前端用 JS 的 getDay() 拿到2表示周二两个数字在接口里直接对不上排查了一下午才发现是星期基准不一致。这个问题没有捷径只能在团队里约定统一格式最好在接口字段注释里写清楚0周几。4.3 周编号与日历自动化的扩展玩法如果业务要按周统计就要用到 ISO 周编号。2026年9月22日落在 ISO 2026-W39 这一周这周的周一是9月21日周日是9月27日。ISO 周编号的规则是包含当年第一个星期四的那一周是第一周这意味着元旦所在年份的周号可能是上一年的 W52 或 W53。很多报表系统在此翻车因为直接用1月1日是第几周来推。日历自动化的另一个扩展是生成某个月份的完整日历。核心逻辑就三步先算当月1日的星期确定第一行前面要空几格再根据当月天数补格子最后逐个日期填进去并标记周末。我曾经用这个逻辑十分钟给团队做了一个内部轮班表生成器输入月份自动输出每个星期的排班情况。原理就是这篇文章第二、三节的内容没有更高深的东西。5. 常见问题与排查技巧实录5.1 闰年百年例外最容易翻车的规则闰年的判断规则是能被4整除但不能被100整除或者能被400整除。很多人记住前半句忘了后半句于是把1900年当成闰年把2000年当成平年——两个都错了。1900年不是闰年因为它能被100整除但不能被400整除2000年是闰年因为它能被400整除尽管它也能被100整除。这条规则直接影响2月29日之后所有日期的星期计算一旦把1900年误判为闰年当年3月到12月每一天的星期都会整体偏移1天。排查这种问题时我有个习惯先随机抽查几个跨世纪日期比如2000年3月1日是星期三、2100年3月1日是星期一如果你的算法输出不一致多半是闰年规则没写全。顺带一提蔡勒公式和末日算法内部都隐含了这套闰年规则所以它们能自动处理公历的这套复杂设计。如果你自己写 date-diff 一类的逻辑一定要把这条规则清楚写在注释里。5.2 时区、夏令时与日期边界2026年9月22日在技术上其实是个不完整说法必须带上时区才有确切含义。服务器普遍使用 UTC业务人员脑子里的9月22日却是本地时间。一个常见坑是用 new Date(2026-09-22T00:00:00Z) 存了 UTC 零点用户在 UTC8 区域看到的是2026年9月22日早上8点没问题但如果用户在西五区这个零点就变成了前一天19点日期就提前了一天。夏令时更麻烦。有些地区在3月和10月附近某一天只有23小时或25小时。如果在夏令时切换的那天做加一天操作用时间戳累加86400秒的方式会得到错乱的日期。正确做法是用高级语言里的日期库做日历运算比如 Python 的 date timedelta(days1)它处理的是逻辑上的下一天而不是24小时后。这个差异我在做跨时区定时提醒功能时吃过亏后来凡是涉及日期相加一律禁止用毫秒数直接加减。5.3 问题速查表症状可能原因解决办法9月22日被算成星期三手算时把当天到当天的偏移算错用蔡勒公式复核先算差值再 mod 7Python 蔡勒结果在1月2月全错忘了 month3 时 year 减一加判断并单独测试1月1日、2月28日JS 里 new Date(2026,9,22) 显示10月月份从0开始月份参数减1服务器的明天比预期少一天用了时间戳累加而不是日历库改用 timedelta 或类似库做日期运算Excel WEEKDAY 结果和别处对不上返回基准不同用第二参数统一为周一16. 最后分享几条实操经验6.1 三个长期有效的编码习惯写到最后分享几个我在实际使用中沉淀下来的习惯。第一任何重要的日期逻辑上线前至少用两种独立方式交叉验证比如手算一次末日算法再用代码跑一次两边对上了才敢合代码。第二代码层面优先使用标准库不要自己造轮子除非环境真的不提供蔡勒公式知道怎么实现就够了真正写业务时直接调 datetime 或 date-fns 更稳妥。第三凡是接口里要传日期星期注释里一定要写清楚基准整数是0起还是1起、周日是0还是7。这个看似微不足道的约定曾经是我见过最多的线上事故来源之一。6.2 一个提升手算速度的小技巧还有一个随时能用的小技巧在手机日历里把自己熟悉的几个锚点日期标出来比如每年的元旦、自己的生日、某个固定节日。任何陌生日期先在心里换成距离某个锚点多少天再按 mod 7 换算星期速度和准确率都会明显提升。这套方法我用了很多年处理日常生活中的排期问题几乎不再需要打开计算器。2026年9月22日这个例子以后你在日历应用里再看到类似的日期星期组合不妨顺手用今天写的方法验一下多验几次手感自然就出来了。