日期计算是那种「看着简单、一算就吵架」的题目:租房押金算到哪天、工期是 30 天还是 31 天、农历生日为什么今年提前了 20 天、后端给的时间戳解析出来差了 8 小时。
这些问题的根源不是算术,而是口径和历法规则。下面逐个拆开。
一、天数差:含头含尾决定要不要加 1
两个日期之间有几天,有两种都成立的答案:
- 差值口径(不含头含尾):两个日期相减,得到的是间隔天数。9 月 1 日到 9 月 10 日是
9天。 - 含头含尾口径:把首尾两天都算作完整的一天,结果是差值加 1,也就是
10天。
哪个对取决于业务约定:住院天数、酒店房晚、租期、工程日历天各有各的惯例。合同里写「30 天内完成」通常指含起始日的自然日,而计息天数常按差值算。
本站的日期计算提供三种模式:算几天后的日期、算两个日期之差、算某天是星期几。其中日期差给出的是差值口径——直接取两个日期的时间差绝对值折算成天数。需要含头含尾时自己加 1,这比工具替你决定要安全,因为工具无法知道你的合同怎么写的。
「算几天后的日期」这个模式同样有口径问题:从 9 月 1 日起算 +30 天得到 10 月 1 日,如果你要的是「9 月 1 日算第 1 天的第 30 天」,那应该是 +29。
二、工作日与调休:先算日历天,再逐项扣
工作日没有纯数学解法,因为它依赖每年国务院单独发布的放假安排。可行的做法是分三步:
- 第一步,算出区间内的日历总天数,用日期差工具得到。
- 第二步,扣掉周末。 完整的周数乘 2 即为周末天数,再单独处理首尾不足一周的零头——所以起始日是星期几会影响结果,这一步不能只用总天数除以 7。
- 第三步,按当年放假安排调整。 法定节假日要扣,调休上班的周末要加回来。中国的调休制度会把周末变成工作日,这一项漏掉就会算少。
这也是为什么跨春节、国庆的工期估算特别容易错:假期本身连着好几天,前后又各挂一两个调休上班日,正负两个方向的修正都存在。放假安排以每年官方公告为准,任何工具内置的节假日表都有过期风险。
三、农历的闰月与大小月是怎么来的
农历是阴阳合历:月份跟着月相走,年份跟着太阳走,两套周期不整除,于是必须打补丁。
月相周期(朔望月)平均约 29.5306 日,不是整数。所以农历月只能取整:30 日的叫大月,29 日的叫小月。哪个月是大月不是固定的,而是按实际朔日(月亮完全看不见的时刻)落在哪天来定,这就是为什么农历大小月的排布年年不同,没有「单月大双月小」这种规律。
12 个朔望月约 354.37 日,比回归年 365.2422 日短约 10.9 日。如果不管,春节会一路提前,几十年后过到夏天去。补救办法是置闰:
19 个回归年 ≈ 235 个朔望月 = 19 × 12 + 7
也就是 19 年里加 7 个闰月,这个规律古称「章」,误差极小。具体某年闰几月,按「无中气之月置闰」的规则确定:二十四节气中逢单的十二个是节气、逢双的十二个是中气(雨水、春分、谷雨、小满、夏至等),冬至必须落在十一月;两个冬至之间若出现 13 个月,则其中第一个不含中气的月被定为闰月,取前一个月的月名并冠以「闰」字。
结果就是农历年长度在 353 到 355 日(平年)和 383 到 385 日(闰年)之间跳动。农历生日对应的公历日期每年前后浮动一个月左右,正是这个原因。公历农历转换覆盖 1900 年到 2100 年,闰月与大小月按天文历算数据处理,农历转公历时可以直接在下拉框里选闰月,不需要自己判断某年有没有闰某月。
四、二十四节气按太阳黄经算,不是固定日期
流传很广的一个误解是「节气是农历的一部分」。恰恰相反,二十四节气完全由太阳位置决定,属于阳历成分,这也是节气在公历上日期相当稳定的原因。
定义是等分黄经:以春分点为 0°,太阳视黄经每增加 15° 就是一个节气,清明 15°、夏至 90°、秋分 180°、冬至 270°。求某年某节气的准确时刻,就是求解「太阳视黄经等于指定度数」的那个瞬间,属于天体位置计算,不是查表。
两个推论值得记住:
- 节气的公历日期会浮动 1 到 2 天。 回归年是
365.2422日而非整数,加上闰年规则的修正,所以立春在 2 月 3 日、4 日或 5 日都可能。 - 相邻节气的间隔不等长。 地球公转轨道是椭圆,1 月初过近日点时走得快、7 月初过远日点时走得慢,因此节气间隔在
14.7到15.7天之间变化。夏半年的节气间隔明显比冬半年长。
所以任何把节气写成固定日期的做法都会在某些年份出错。上面提到的转换工具不使用固定数据表,而是按太阳视黄经实时推算并换算为东八区时间,还会给出当日节气与下一个节气;想系统了解每个节气的气候特点、习俗与养生说法,可以看二十四节气查看工具的平铺卡片。
五、时间戳:10 位与 13 位、UTC 与东八区
Unix 时间戳的定义是从 1970-01-01 00:00:00 UTC 起经过的时间,注意三件事。
- 秒还是毫秒。 秒级时间戳是 10 位数字,毫秒级是 13 位。这是当前这个年代的位数特征,不是定义的一部分,但足以用来判断——时间戳转换就是按输入长度识别的,10 位按秒、13 位按毫秒,其他长度会直接提示无效。粘贴时多带或少带一位,得到的日期会差出几十年。
- 时间戳本身没有时区。 它是一个绝对时刻,时区只影响显示。同一个时间戳在 UTC 下是
2026-09-12 00:00:00,在东八区就是2026-09-12 08:00:00,两者相差28800秒:
东八区显示时刻 = UTC 时刻 + 8 × 3600 秒
前后端时间差 8 小时这类经典 bug,几乎都是某一端把带时区的字符串当成本地时间解析,或者反过来。转换工具会同时列出标准格式、ISO 格式、北京时间与 UTC 时间,正是为了在这一步做交叉核对。
- 两个历史遗留问题。 一是闰秒:Unix 时间戳按每天恰好
86400秒推进,不计入闰秒,所以它不是严格的物理时间轴。二是 2038 年问题:用 32 位有符号整数存储的秒级时间戳会在2038-01-19溢出,老系统迁移时要检查。
六、常见误算原因
- 含头含尾没约定清楚。 同一段日期,差值口径和含头含尾口径永远差 1 天。
- 工作日直接按总天数除以 7 估。 忽略了起始星期与调休,跨长假必错。
- 以为农历大小月有固定规律。 大小月按实际朔日排定,年年不同。
- 把节气当固定日期。 立春不总是 2 月 4 日,节气间隔也不等长。
- 闰年规则只记了「四年一闰」。 完整规则是四年一闰、百年不闰、四百年再闰,所以 1900 年和 2100 年都不是闰年,2000 年是。
- 秒级与毫秒级时间戳混用。 相差 1000 倍,解析出来是 1970 年附近或者遥远的未来。
- 忽略时区偏移。 UTC 与东八区差
8小时,跨零点的日期会整体错一天。
常见问题
工具算出的日期差和我手算的差 1 天,谁错了? 大概率都没错,只是口径不同。工具给的是差值口径,你如果按含头含尾数,就会多 1 天。先确认业务上要哪一种,再统一。
为什么我的农历生日今年比公历生日早了将近一个月? 因为上一个农历年是闰年(含 13 个月)或平年(约 354 天),农历年长与公历年长相差约 11 天,闰年时又多出一个月。多年来看,农历生日对应的公历日期在一个月左右的范围内往复摆动。
闰月出生的人怎么过生日? 没有统一规则,民间做法不一致:有的按对应的正常月同日过,有的固定按公历过。工具只负责给出准确的历法对应关系,具体习俗按各地惯例。
时间戳转出来的时间和数据库里对不上,怎么排查? 先看差值。差 8 小时是时区问题;差 1000 倍是秒毫秒问题;差几十年通常是位数被截断或补零。把同一个时间戳分别按 UTC 和东八区展示一遍,对照数据库的时区设置,问题一般就定位了。