date() 用默认时区,strtotime() 默认按服务器本地时区解析未带时区的时间字符串,导致跨服务器结果不一致;DateTime 构造未显式传时区会隐式绑定默认时区;MySQL NOW() 与 PHP date() 时区不一致易引发数据偏差;应统一用 UTC 存储并显式指定时区。

PHP 中 date() 默认使用 date_default_timezone_get() 返回的时区,而 strtotime() 在解析字符串时,若未显式指定时区(如 "2025-01-01 12:00:00 UTC"),会默认按服务器本地时区解释输入——哪怕你已用 date_default_timezone_set('Asia/Shanghai') 设置过。这意味着同一字符串在不同服务器上可能被解析成完全不同的时间戳。
实操建议:
"2025-01-01 12:00:00 +0800" 或 "2025-01-01T12:00:00+08:00"
strtotime() 自动推断,改用 DateTime::createFromFormat() 并传入明确的时区对象date_default_timezone_get() 返回值,不要假设它和系统 /etc/timezone 或 PHP 配置中 date.timezone 一致写
$dt = new DateTime('2025-01-01'); 看似简单,但实际等价于 $dt = new DateTime('2025-01-01', new DateTimeZone(date_default_timezone_get()));。如果当前脚本运行在东京服务器但业务面向欧洲用户,这个 DateTime 对象内部时间戳就已绑定东京时区,后续调用 $dt->format('c') 会输出带 +09:00 的 ISO 字符串,而非你预期的 +01:00。
实操建议:
DateTime 实例时,显式传入目标时区对象:$tz = new DateTimeZone('Europe/Berlin');
$dt = new DateTime('2025-01-01', $tz);DateTimeImmutable + setTimezone(new DateTimeZone('UTC')) 统一归零,再格式化为本地显示DateTime::__construct() 第二个参数为 null 的情况——它不会 fallback 到 UTC,而是 fallback 到默认时区PHP 脚本里执行 date('Y-m-d H:i:s') 写入数据库,和 SQL 中直接用 NOW() 插入,在跨时区部署时极易出现小时级偏差。因为 NOW() 返回的是 MySQL 服务端配置的时区(SELECT @@time_zone;),而 PHP 的 date() 取决于 PHP 进程的时区设置——两者默认互不感知。
实操建议:
+00:00(即 UTC),所有写入用 UTC_TIMESTAMP(),PHP 端也统一用 gmdate() 或 (new DateTime('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s')
time_zone 和 PHP 的 date.timezone 配置值完全相同(注意:SYSTEM 不可靠,应写死如 'Asia/Shanghai')SELECT NOW(), UTC_TIMESTAMP(), @@time_zone;和 PHP 的
date('c')、gmdate('c') 对比验证strftime() 的输出受 setlocale(LC_TIME, ...) 影响,但该函数在多线程 SAPI(如 PHP-FPM)中是进程级全局状态,一次请求修改会影响后续请求;且 en_US.UTF-8 在 Alpine Linux 容器里常不存在,导致返回空字符串或错误格式。
实操建议:
setlocale() + strftime(),改用 IntlDateFormatter(需启用 intl 扩展):$fmt = new IntlDateFormatter('zh_CN', IntlDateFormatter::FULL, IntlDateFormatter::FULL);
echo $fmt->format(strtotime('2025-01-01'));strftime(),先用 locale -a | grep 'zh_CN\|en_US' 确认系统可用 locale,再在 PHP 启动时(非每次请求)设置,并加异常兜底strftime() 不支持毫秒、时区缩写(如 CST)等现代需求,DateTime::format() 更可控