必须设置时区并严格校验输入:date() 默认用服务器时区导致时间偏差,需在首次调用前执行date_default_timezone_set('Asia/Shanghai');strtotime()会自动归正非法日期且不报错,须用date('Y-m-d', $ts)还原比对或checkdate()校验;所有外部输入时间需trim()并严格判断strtotime()返回值是否为false。

date() 和 strtotime() 是 PHP 里最常用、也最容易出错的两个日期时间函数。它们不是“用对就行”,而是必须理解其隐含依赖和边界行为,否则上线后可能在跨时区、闰年、月末或夏令时切换时突然出错。
为什么 date() 输出的时间总是错8小时?
这不是函数错了,是它默认读取服务器本地时区(比如 Europe/London),而你的代码期望的是北京时间(Asia/Shanghai)。PHP 不会自动猜你想要哪个时区。
解决办法只有一条:在所有涉及时间输出的脚本开头加一句:
date_default_timezone_set('Asia/Shanghai');注意:date_default_timezone_set() 必须在首次调用 date() 前执行;放在 date() 后面无效。如果项目已上线,别只改一处——要检查所有入口文件(如 index.php、api.php、定时任务脚本)是否都设置了。
立即学习“PHP免费学习笔记(深入)”;
常见错误现象:
- Web 页面显示时间正常,但
cron脚本跑出来的时间比预期早/晚整点 - 日志里同一请求的
date("Y-m-d H:i:s")和数据库写入的NOW()相差 8 小时 - 使用
date("w")判断星期几,结果周日返回0,但前端需要中文“星期日”——别硬写映射,先确认时区对了再处理展示逻辑
strtotime() 解析 "2025-02-29" 不报错?
strtotime() 的设计目标是“尽力解析”,而不是“严格校验”。它遇到非法日期(如平年的 2 月 29 日)会自动“归正”:把 "2025-02-29" 当作 "2025-03-01" 处理,且不抛异常、不警告、不返回 false —— 它照样返回一个有效时间戳。
这意味着你不能靠 strtotime($input) !== false 来判断用户输入的日期是否合法。
正确做法是组合使用:
- 先用
strtotime($input)得到时间戳 - 再用
date('Y-m-d', $ts)把它转回来 - 对比原始字符串和还原后的字符串是否完全一致(注意空格、分隔符)
示例:
$input = '2025-02-29';$ts = strtotime($input);if ($ts === false || date('Y-m-d', $ts) !== $input) { // 输入非法}更稳妥的替代方案是用 checkdate() 拆解年月日手动校验,尤其适合表单提交场景。
用 getdate() 取“今天星期几”为什么有时返回 0 有时返回 1?
getdate() 返回的 wday 键值始终是数字(0=星期日,1=星期一……6=星期六),但它依赖当前时区设置。如果没设时区,服务器本地时区可能是 UTC,而你所在地是 Asia/Shanghai(UTC+8),那么当 UTC 时间还是周三 23:00 时,北京时间已是周四 07:00 —— 这时候 getdate()['wday'] 就会因时区漂移产生偏差。
所以不要直接用 getdate()['wday'] 做业务判断(比如“只在周一执行”)。应该:
- 先确保
date_default_timezone_set()已调用 - 用
date('w')替代getdate()['wday']—— 它们语义相同,但date()更轻量、更可控 - 如果真要用
getdate(),记得传入明确时间戳:getdate(strtotime('today')),避免隐式依赖当前时间
为什么线上环境 date("Y-m-d") 突然变成 1970-01-01?
这几乎一定是 strtotime() 返回了 false,而你把它直接喂给了 date():
$ts = strtotime($user_input);echo date('Y-m-d', $ts); // $ts === false → date() 强制转为 0 → 1970-01-01date() 对非整数时间戳参数不做防御性检查,false、null、空字符串都会被当作 0 处理。
关键检查点:
- 所有来自用户输入、API 请求、数据库字段的字符串,在进
strtotime()前先 trim(),防止首尾空格导致解析失败 -
strtotime()返回值必须显式判断:if ($ts === false) { /* 处理错误 */ } - 别省略
===的严格比较——== false会把 0、""、null 都判为 true
真正容易被忽略的地方是:这种错误往往只在特定输入下触发(比如用户填了“2026/13/01”),测试时很难覆盖全,上线后才暴露。所以任何涉及外部输入的时间解析,必须带兜底逻辑。



















