杂记 › 生活随笔 › 每月同一天和每 28 天一次,一年差出 29 天

每月同一天和每 28 天一次,一年差出 29 天

朋友那张记录表有两列日期:一列把上次那个日子往后挪一个自然月,一列把它加 28 天。以 2026-01-31 为起点,2 月两列都写 2026-02-28;到年底一列是 2027-01-31、一列是 2027-01-02,差 29 天。两个人都认定自己没错,就拿着表去对照页面实现——每月同一天和每 28 天一次,本来就是两套算术。

这一页只做按天的事

period-calculator 的模板 244 行,脚本在 L87 到 L243。我把脚本原样抽出来跑在 DOM 桩上(Node v24,冻结今天 2026-10-22):全文 0 处 setMonth,setDate 只有 1 处,就是 L125-129 的 addDays,函数体 d.setDate(d.getDate() + n)。这一页里没有日历月,预测全是起点加上周期的整数倍天。

「未来 3 次经期预测」因此是 1、2、3 倍周期,不是 3 个自然月:起 2026-01-31、周期 28,给的是 2026-02-28、2026-03-28、2026-04-25,第 3 次既不是 31 号也不是月末。文案那句「可预测未来3个月的经期日期」按日历月该走到 2026-04-30,差 5 天。反方向也得防:JS 裸 setMonth 把 2026-01-31 推到 2026-03-03,2 月没有 31 号就直接溢出;自己写月末夹取才给 2026-02-28,而那段夹取在本页并不存在,只是我另起的外部对照。

两条轨道从第几次分叉

日历月长度在 28 到 31 天之间摆,28 天一档是定长,并排一列就露馅:

次数每月同日每 28 天差
第 1 次2026-02-282026-02-280
第 3 次2026-04-302026-04-25-6
第 6 次2026-07-312026-07-18-13
第 12 次2027-01-312027-01-02-29
第 24 次2028-01-312027-12-04-58
起点 2026-01-31:每 28 天 减 每月同日 的差(天)0-6-13-29-58第 1 次第 3 次第 6 次第 12 次第 24 次

图 1:本页 28 天口径比日历月同日提前多少天,绿柱那格就是标题里的 29 天;同日列是页面之外的夹取对照。

第 2 次已经差 -3 天,2026-03-31 对 2026-03-28。想凑「一年就算 12 个 30 天」两头都不对:同一起点加 360 天是 2027-01-26,加 372 天是 2027-02-07,12 个 28 天只走到 2027-01-02。跨闰年再多一层:2026-01-31 加 365 天正好是 2027-01-31,2024-01-31 要加 366 天才回得到 2025-01-31,只加 365 天停在 2025-01-30。2 月几天这页全交给 JS Date,模板 0 处自写闰年判定:2024 年 29 天、2000 年 29 天、1900 年 28 天、2100 年 28 天。2 月 29 号出生怎么算周年,闰日生日那篇里掰扯过。

输出是定型的,缺的那块真没有

面板固定 4 个 badge——下次月经、排卵日、易孕期、安全期,外加 3 行预测和 5 行日期说明。它只有 1 个日期输入,没有结束日期,所以给不出两个日子相差几天,也没有年/月/周/时/分级分解:唯一的除法是 L122 的 Math.round((target - today) / 86400000),粒度只有天,带符号的天数仅说明里那行「距今天 -236 天」。排卵日恒为下次月经前 14 天,易孕期恒为前 5 天到后 4 天共 10 天,安全期却有两套口径:badge 在 2026-02-19 到 2026-02-27 之间只给 9 天,时间轴同一组输入判出 13 格绿色。免责写得很直白,不能作为避孕依据。要盯某个日子还剩多久,得去节日倒计时那类以目标日为输入的页。

时间轴只有 35 格,画不下不等于没到

var totalDays = 35 把窗口写死了,7 列网格。起 2026-01-31、经期 5 的那组扫描里,周期 21 时下次经期占 5 格,周期 27 仍是 5 格,周期 33 只剩 2 格,周期 39 和 45 归零——周期 45 的下次月经落在第 46 天,在窗口外,尾部整片被绿色补满。起 2026-03-08、经期 10、周期 45 那组是经期 10 格、易孕期 9 格、安全期 16 格,下次经期与未到都是 0,末格 title 只写到第 35 天:没有红格是截断,不是没到。判定还讲先到先得,经期 10、周期 21 时那 10 天的易孕期被吃掉 8 天,只剩 2 格。

35 格窗口的实际填色,每格 1 天起 2024-02-29 经期 3 周期 21:经期 3、易孕期 9、安全期 9、下次经期 3、未到 11起 2026-03-08 经期 10 周期 45:经期 10、易孕期 9、安全期 16、下次经期 0下次月经在第 46 天,35 格画不出来

图 2:同一块 35 格在两个周期下的填色,深为经期、浅灰为易孕期、绿为安全期、半透明为下次经期、空为未到。

输入侧几乎不报错

整页只有 3 条提示:请选择上次月经开始日期、经期天数应在 2-10 之间、月经周期应在 21-45 之间。日期框没有 min 和 max,parseDate 只 split('-') 再 parseInt,不判合法性:2026-02-31 被吞成 2026-03-03,下次月经照样给 2026-03-31;2026-13-01 吞成 2027-01-01;少一段的 2026-10 和顶到 JS Date 上限的 275761-01-01 连提示都没有,badge 直接写 NaN-NaN-NaN。小数在函数层被截断,经期 5.9、周期 28.9 照样出结果。上限是 21-45,文案那句「正常月经周期21-35天」拦不住任何东西,周期填 40 就给 2026-03-12。所以核对时先读回显的日期串,别等红字。

被追问的三个问题

Q:记录时到底按哪一个?

按周期天数,也就是本页这套:起点加周期的整数倍。别在一张表里一半按日历同日、一半按定长天数——第 12 次差 29 天,第 24 次差 58 天,两条轨道各自成立,越走越不相遇。

Q:它不会自动把 31 号挪到 2 月末吗?

不会。全文 0 处 setMonth,也没有夹取分支。2026-01-31 加 28 天是 2026-02-28,加 30 天是 2026-03-02,落在月末纯属周期长度恰好对上。

Q:安全期那一栏能当依据吗?

不能,免责原文就是不能作为避孕依据;同一组输入下 badge 的 9 天和时间轴的 13 格,连长度口径都对不齐。

回到那张两列表:2026-02-28 那一格两种写法给的是同一个日子,年底一个写 2027-01-31、一个写 2027-01-02,谁都没算错,是两句话本来不是一件事。要长期记,先把口径写进表头,再挑工具——工具只是替你沿选定那条推到底。把这 3 个输入丢进经期计算器里,它推的是周期乘倍数的天,不是自然月。

📎 相关文章

照片转了 90°,换台设备方向又回来了:EXIF 与真实旋转的分工 2026-10-22 用 sort(() => Math.random() - 0.5) 打乱名单,抽签就已经不公平了 2026-10-21 加仑有两种、马力有两种、字节有两套进位:换算前先问是哪一种 2026-10-21 2 月 29 日出生的人,平年哪天满 18 周岁?两种周年口径差一天 2026-10-21 全马 3:30 要跑多快?九段递进配速表、前后半差和那段触底的配速 2026-10-21