一次看似“语义化”的代码优化,却险些引入生产事故。从
3 * 86400到strtotime('+3 days'),这中间究竟藏着哪些不为人知的坑?
一、一个来自代码审核的意外发现
某天,你在优化一段判断用户操作是否超过三天时限的代码:
1 | // 原代码 |
编辑器提示:可以使用更具语义化的时间计算方式。你深以为然,改成了:
1 | // 优化后 |
提交后,代码审核被驳回,并附上一段话:
❌ 请不要使用
strtotime('+n days', 0)来表示 n 天的秒数。时区、夏令时、性能三大问题。推荐使用3 * 86400。
你愣住了:strtotime 不是更易读吗?为什么会引起这么大争议?
二、三个致命的陷阱
1. 依赖时区与夏令时:自然日 ≠ 固定秒数
strtotime('+3 days', 0) 的意思是:从 Unix 纪元(1970-01-01 00:00:00 UTC)开始,加上 3 个自然日。这里的“自然日”依赖于你服务器的时区设置和夏令时(DST)规则。例如:
- 假设服务器时区为
America/New_York,2025 年 3 月 9 日星期日,美国进入夏令时,当天只有 23 小时。 - 如果系统在 UTC 0 点执行
strtotime('+3 days', 0),得到的时间戳对应的实际 UTC 时间可能会偏移 ±1 小时,导致计算出的秒数不等于3 * 86400。
更极端的例子:某一天有 25 小时(夏令时结束)时,strtotime('+1 day') 相当于加了 25 小时,而不是 86400 秒。而在判断过期等场景中,我们通常需要的是 固定的秒数间隔(例如 3 天 = 259200 秒),而非“自然日”的起止点。
结果:经过 strtotime 转换后的时间戳与 time() 直接相减,可能因为时区和夏令时而相差一个小时,导致超时判断出现 ±3600 秒的误差。
2. 性能开销:毫秒级调用 vs 常数级运算
3 * 86400 在做 Php 编译时就已经被折叠为一个常量(259200),运行时无任何函数调用,速度极快。
而 strtotime('+3 days', 0) 则复杂得多:
- 解析字符串
'+3 days'; - 根据当前时区构造内部时间对象;
- 进行日历计算(考虑闰年、月长、夏令时切换);
- 最终返回整数时间戳。
一次 strtotime 的耗时大约是乘法的数十倍甚至上百倍。在高频调用的循环或热点代码中,这种差异可能被放大。
3. 可读性陷阱:语义误解
很多开发者看见 strtotime('+3 days') 会下意识认为“加 3 天的秒数”。然而如前所述,它加了 3 个自然日,而不是固定秒数。当你用 time() - $create_time > strtotime(...) 比较时,实际比较的是 时间戳差值是否大于某个会变的数。这是一个隐蔽的逻辑错误。
相反,3 * 86400 直白地表达了“三天的秒数”,任何程序员都能立即理解,且结果恒定。
三、什么时候才应该用 strtotime('+3 days')?
strtotime 和 DateTime 类的设计初衷是处理带日历语义的时间,比如:
- “2025-05-08 12:00:00 的三天后是几月几日?” → 需要
strtotime('+3 days', $timestamp)或(new DateTime('@'.$timestamp))->modify('+3 days')。 - “下个月的同一天” →
strtotime('+1 month')。 - “下一周的周三” →
strtotime('next Wednesday')。
这些场景中,我们关心的是日期的自然流动,不关心具体的秒数。而判断一个操作是否超过 3 天(固定秒数间隔),大部分商业逻辑(如会话超时、API 限流窗口、红包有效期)并不涉及时区和夏令时。此时应当使用固定秒数。
四、最佳实践指南
| 场景 | 推荐写法 | 理由 |
|---|---|---|
| 固定秒数间隔(如缓存过期、登录超时) | 3 * 86400 或 259200 |
性能好、可预测、无时区干扰 |
| 自然日偏移(如“3天后的同一时刻”) | strtotime('+3 days', $timestamp) 或 DateTime::modify('+3 days') |
正确处理时区和夏令时 |
| 可读性优先且确定是固定秒数 | $seconds = 86400 * 3 |
也可,但建议加注释说明 |
| 需要避免魔法数字 | define('DAY_SECONDS', 86400); 配合乘法 |
便于全局维护 |
更安全的写法(PHP 8+):
1 | $expired = time() - $create_time > 86400 * 3; |
如果你确实需要自然日比较,不要用 strtotime('+3 days', 0),而是这样写:
1 | $createDate = (new DateTime())->setTimestamp($create_time); |
五、总结
- 固定秒数用乘法:
3 * 86400高效、精确、无副作用。 - 自然日操作用
DateTime:需要处理时区、闰年、月份长度时,让专门的日期时间库去做。 - 避免将
strtotime('+n days', 0)当作秒数常量使用:它既不快,也不一定等于n * 86400。
回到最初的问题:编辑器的提示并非有错,只是它的建议适用于字符串与日期的格式化显示,而非用于固定时间间隔的计算。代码优化时,理解语义比追求“看起来高级”重要得多。
请记住:当你只需要“秒”时,请用“秒”来计算。