使用方法
- 在「出生日期」填生日。页面默认填
1996-08-15,这是一个合法的公历日期。 - 在「参考日期」填要算到哪天。默认固定为
2026-10-03,这样正文里的示例数字可以直接对照;想算到今天就点「用今天作参考」,或者把这一栏清空。 - 如果出生日期是 2 月 29 日,在下拉里选平年按
2 月 28 日还是3 月 1 日算。默认选 2 月 28 日。其它日期这一栏不参与。 - 点「计算」或按 Enter,五张卡片给出已过生日次数、下一个生日日期、距今天数、已过整日整月和下个生日的星期。
- 想一次看多年份的星期分布,点「加载更多年份」展开 8 月 15 日在 12 个年份的星期对照表。
这个生日计算器能解决什么问题
生日计算器只回答「生日」相关的问题,不做通用的日期相减。四类具体场景:一是倒计时,1996 年 8 月 15 日出生的人,到 2026 年 10 月 3 日已经过了 11006 个整日、361 个完整月(整 30 年 1 个月零 18 天),下一个生日是 2027 年 8 月 15 日、还有 316 天;二是聚会排期,2026 年的生日落在星期六,2027 年落在星期日,两年差一天而不是差两天;三是闰年生日,2000 年 2 月 29 日出生的人,到 2026 年 10 月 3 日过了 9713 天、按替代日算过 26 个生日,而 2028 年又是闰年、2 月 29 日真实存在;四是确认口算对不对,从 1996-08-15 到 2096-08-15 整整 100 年是 36525 天,比 100 × 365 = 36500 多 25 天,多出来的正是期间经过的 25 个闰日。
本页不算两个任意日期之间隔了多少天,也不单独给「几岁几个月零几天」——那两件事请用文末的日期间隔与年龄页面,它们和本页的搜索意图不同,混在一页会互相抢词。
原理和变量:为什么全程用 Date.UTC
本页的核心是三个函数:Date.UTC(y, m-1, d) 构造时间戳、Math.round((t2 - t1) / 86400000) 取整日差、getUTCDay() 取星期。
为什么不用 new Date(y, m, d)。带参数的 Date 构造函数按本地时区解释年月日,而 Date.UTC 按 UTC 解释。实测在本机东八区,new Date(2026,9,3).getTime() 与 Date.UTC(2026,9,3) 相差 -28800000 毫秒,正好 8 小时。这个偏差在跨月、跨年时会变成整整一天的 off-by-one:一个人 23:30 在本地日期控件里选了「明天」,用本地 Date 算就可能少一天。
为什么夏令时也必须绕开。中国 1986 到 1991 年实行过夏令时,1991 年 4 月 14 日凌晨 2:00 直接跳到 3:00,那一天只有 23 小时;美国 2007 年起同样在 3 月第 2 个周日把 2:00 跳到 3:00。如果用本地时间戳相减,跨越切换点的那一天会算出 23 或 25 小时,Math.round 之后就多一天或少一天。用 Date.UTC 全程按 UTC 计时,任何时区、任何夏令时规则都影响不到它——这是本页选它的唯一理由。
整日差为什么要 Math.round 而不是 Math.floor。因为两个时间戳都精确落在 UTC 午夜上,差值一定是 86400000 的整数倍,理论上 floor 就够。实测 new Date(2026,9,4) - new Date(2026,9,3) 正好 86400000 毫秒。这里仍用 Math.round 是为了在极端情况下(某些引擎对 Date.UTC 年份 0~99 特判)不因小数误差掉一天——Date.UTC(99, 0, 1) 会被解释成 1999 年,本页因此把年份下限设在 1900。
星期用 getUTCDay() 而不是 getDay()。后者按本地时区算,跨时区看同一份生日会得到不同答案。索引 ['日','一','二','三','四','五','六'] 对应 getUTCDay() 的 0~6。
闰年判定用哪条规则。公历闰年规则是:能被 4 整除且不能被 100 整除,或能被 400 整除。实测 2000 年(能被 400 整除)是闰年、2024 年是闰年,而 1900 年和 2100 年都不是——它们能被 100 整除但不能被 400 整除。这条规则来自公历(格里高利历)本身,公元 1582 年起施行;本页不涉及 1582 年之前的日期,所以不处理儒略历与公历的错位(那 10 天被抹掉)。
完整示例(数值仅作演示)
打开页面时出生日期是 1996-08-15、参考日期是 2026-10-03,生日计算器给出的五张卡片是:
- 已过生日次数 30 个(2026 年生日在 8 月 15 日,已过 49 天,所以已满 30 整岁);
- 下一个生日 2027-08-15,距今天 316 天;
- 已过 11006 整日、361 个完整月;
- 下个生日落在星期日,而 2026 年这个生日是星期六,出生当天 1996-08-15 是星期四;
- 100 岁生日 2096-08-15 是星期三。
生日星期逐年挪动的规律能直接看出来:2026-08-15 星期六 → 2027-08-15 星期日(365 天,区间不含闰日,星期 +1)→ 2028-08-15 星期二(366 天,区间含 2028 年 2 月 29 日,星期 +2)→ 2029-08-15 星期三(365 天,+1)。所以平年之间固定 +1,只有跨过闰日那年才 +2。
把出生日期换成 2000-02-29(闰年 2 月 29 日),参考日期不变,答案变成:已过 9713 天,已过 26 个生日,下一个生日按「2 月 28 日」策略算是 2027-02-28、距今 148 天;改选「3 月 1 日」策略则变成 2027-03-01、距今 149 天,差 1 天。
输入范围与对照表
年份范围 1900 ~ 2100。下限 1900 避开了 Date.UTC(0..99, ...) 被引擎特判成 1900~1999 的坑;上限 2100 让 getUTCFullYear() 落在四位数内,格式化不会出现 5 位年。
8 月 15 日这个生日在 12 个年份的星期与倒计时(以参考日期 2026-10-03 为基准):
| 年份 | 8 月 15 日 | 星期 | 距 2026-10-03 | 当年是闰年 |
|---|---|---|---|---|
| 1996 | 1996-08-15 | 星期四 | 11006 天前 | 是 |
| 2000 | 2000-08-15 | 星期二 | 9545 天前 | 是 |
| 2004 | 2004-08-15 | 星期日 | 8084 天前 | 是 |
| 2008 | 2008-08-15 | 星期五 | 6623 天前 | 是 |
| 2012 | 2012-08-15 | 星期三 | 5162 天前 | 是 |
| 2016 | 2016-08-15 | 星期一 | 3701 天前 | 是 |
| 2020 | 2020-08-15 | 星期六 | 2240 天前 | 是 |
| 2024 | 2024-08-15 | 星期四 | 779 天前 | 是 |
| 2026 | 2026-08-15 | 星期六 | 49 天前 | 否 |
| 2027 | 2027-08-15 | 星期日 | 还有 316 天 | 否 |
| 2028 | 2028-08-15 | 星期二 | 还有 682 天 | 是 |
| 2032 | 2032-08-15 | 星期日 | 还有 2143 天 | 是 |
2 月 29 日出生者(2000-02-29)在三个年份的替代日对照,注意 2028 年是闰年、2 月 29 日真实存在:
| 年份 | 2 月 28 日距参考日 | 3 月 1 日距参考日 | 当年有 2 月 29 日 | 策略差异 |
|---|---|---|---|---|
| 2026 | 已过 217 天 | 已过 216 天 | 否 | 1 天 |
| 2027 | 还有 148 天 | 还有 149 天 | 否 | 1 天 |
| 2028 | 还有 513 天 | 还有 515 天 | 是 | 2 天 |
各月天数(用于校验出生日期是否合法):1 月 31、2 月平年 28 / 闰年 29、3 月 31、4 月 30、5 月 31、6 月 30、7 月 31、8 月 31、9 月 30、10 月 31、11 月 30、12 月 31。
边界与精度
非法日期会被静默顺延,本页必须自己拦。实测 Date.UTC(2025, 1, 29) 返回的是 2025-03-01——2 月只有 28 天,29 日被顺延到 3 月;Date.UTC(2026, 0, 32) 落到 2026-02-01;Date.UTC(2026, 13, 1) 落到 2027-02-01。引擎不报错,如果你只把字符串丢给 Date,2025 年 2 月 29 日这个不存在的生日会被当成 3 月 1 日悄悄算完。所以本页在 readDate() 里先查该年该月的实际天数,超了直接报「第 4 行月份 2 在 2025 年只有 28 天,没有 29 日」,绝不进下一步。
未来日期按报错处理。出生日期填 2030-01-01、参考日期 2026-10-03 时,差值是 -1186 天。负数意味着「还没出生」,这不是本页要算的东西,报错并指出两个字段谁在前面。
整日差没有精度问题。最大跨度是 1900-01-01 到 2100-12-31,约 73414 天,时间戳量级 6.3 × 1015 毫秒,远低于双精度整数能精确表示的 253-1 = 9007199254740991。所以本页所有天数都是精确整数值,不存在浮点尾差。
回归年不是 365 天。公历平均年长是 365.2425 天(400 年 97 个闰日)。实测 1996-08-15 到 2096-08-15 这 100 年是 36525 天,而 100 × 365 = 36500,差的 25 天就是期间经过的 25 个闰日(1996、2000、2004 …… 2096,均能被 4 整除且不被 100 整除、其中 2000 和 2100 需单独判断)。用 365 去心算「几岁」会得出偏小的结果。
「已过生日次数」的判定规则。比较的是月日而不是日期:参考日期的月日 ≥ 生日的月日 才算今年这个生日已过。默认示例里 2026-10-03 与 08-15 相比月日更大,所以已过 30 个;如果参考日期填 2026-01-01,同一个生日就只算 29 个。这是有意的:生日当天算「满 30 岁」而非「马上 31 岁」。
常见问题
参考日期固定成 2026-10-03,会不会算错?
不会,它是刻意固定的,这样正文里的 11006 天、316 天、星期日这些数字可以逐位复现,表格和正文能对上。要按真实今天算,点「用今天作参考」或直接把那一栏清空,页面会用当天日期重算全部结果。
2 月 29 日出生的人,平年到底算 2 月 28 日还是 3 月 1 日?
没有法定答案,两种都是常见做法,本页把它做成可选项。选 2 月 28 日表示「在二月内过」,选 3 月 1 日表示「过了二月再过」。实测选 2 月 28 日时 2027 年生日距参考日 148 天,选 3 月 1 日是 149 天,差 1 天。闰年(2028)两种差 2 天,因为那天 2 月 29 日真实存在。
为什么不用 new Date(1996, 7, 15)?
因为它按本地时区解释年月日。实测东八区下 new Date(2026,9,3) 与 Date.UTC(2026,9,3) 差 -28800000 毫秒(正好 8 小时)。这个偏差在月末、年末会变成整整一天的误差,而生日这种「差一天就全错」的场景不能接受。
夏令时会让我少一天或多一天吗?
用本地时间戳算会。本页用 Date.UTC 就不会。中国 1986 到 1991 年实行过夏令时,1991-04-14 那天只有 23 小时;美国 2007 年起在 3 月第 2 个周日跳一小时。这些切换点会让本地时间戳相减出现 23 或 25 小时,Math.round 之后就变成少一天或多一天。UTC 计时完全绕开这个变量。
我填 2025-02-29 会怎样?
会报错并告诉你 2025 年 2 月只有 28 天。这条校验是必须的:Date.UTC(2025, 1, 29) 本身不报错,它静默返回 2025-03-01,等于把你填的 2 月 29 日当成 3 月 1 日继续算完,结果全错但你看不到任何提示。
「已过生日次数」和「年龄」有什么区别?
生日次数只数整生日,是整数。年龄若要精确到「30 岁 1 个月 22 天」需要额外算整月与剩余天数。本页两个都给:卡片上是次数 30 和整月 362,但要「几岁几个月零几天」这种完整三元组,请用文末的年龄计算器,那才是它的专长。
100 岁生日是星期几?
按默认示例 1996-08-15 出生,100 岁生日是 2096-08-15,落在星期三。从 1996-08-15 到 2096-08-15 整好 36525 天,其中 25 天是期间经过的闰日补的,所以不能用「每年 365 天」推算星期。
填的出生日期会被保存吗?
不会。日期值只在本页的 JavaScript 里交给 Date.UTC() 解析一次,全页没有 fetch 或 XMLHttpRequest,可在开发者工具 Network 面板确认没有任何请求。刷新页面即恢复默认的 1996-08-15 与 2026-10-03。