Laravel时区需与数据库一致:config/app.php设'Asia/Shanghai',MySQL连接需SET time_zone='+08:00',Carbon实例化须显式传时区,跨服务统一用UTC存储+ISO 8601输出。
查 config/app.php 的 'timezone' 和数据库实际时区是否一致
laravel 默认用 php 的时区(date_default_timezone_get()),但数据库可能按系统时区或连接时区走,两者不一致就必然出错。比如 laravel 设为 'asia/shanghai',而 mysql 实例跑在 utc 服务器上,且没显式设置连接时区,created_at 存进去就是 utc 时间,读出来却按东八区解析,差 8 小时。
- 用
php -i | grep "date.timezone"确认 PHP CLI 时区;Web 请求需看对应 SAPI(如 Apache 的phpinfo()) - 进 MySQL 执行
SELECT @@global.time_zone, @@session.time_zone;—— 如果是SYSTEM,就得看系统/etc/timezone或timedatectl - Laravel 的
config/app.php中'timezone'值必须和你期望的业务时区完全匹配,不能写+08:00,得用 IANA 时区名,如'Asia/Shanghai'
确认数据库连接是否主动设置了时区
哪怕 Laravel 和系统时区一致,MySQL 驱动仍可能在握手阶段把连接时区设成 UTC,尤其用 mysqlnd 或某些云数据库(如阿里云 RDS 默认强制 session 时区为 UTC)。这时 ORM 写入的时间戳会被自动转换,但模型取出来又不还原,时间就“漂移”了。
- 在
config/database.php的 MySQL 配置里加'options' => [PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '+08:00';"](注意:用偏移量比时区名更可靠,避免部分 MySQL 版本不识别Asia/Shanghai) - 如果用的是 MySQL 8.0+ 且启用了
mysql_native_password,还要检查default_authentication_plugin是否影响时区初始化顺序 - 验证是否生效:在
DB::select('SELECT @@session.time_zone');中打印结果,确保不是SYSTEM或UTC
Carbon 实例化时不显式指定时区等于埋雷
很多人写 Carbon::now() 或 new Carbon(),以为会自动继承 Laravel 的 'timezone',其实不会——它只读 PHP 默认时区,而这个值可能被中间件、队列 worker、甚至 PHPUnit 测试环境偷偷改过。
- 所有涉及时间生成的场景,优先用
Carbon::now('Asia/Shanghai')显式传参,别依赖全局隐式行为 - 模型中定义
$dates或使用casts时,Laravel 不会帮你做时区转换,datetime字段存的是无时区含义的字符串,数据库怎么存、就怎么读,Carbon 解析时才套上本地时区 - 队列任务里尤其危险:Supervisor 启动的 worker 可能用系统默认时区,和 Web 请求不一致,建议在任务 handle 开头就
date_default_timezone_set('Asia/Shanghai')
跨服务调用时,数据库时区不再是唯一变量
一旦引入 Redis 缓存时间、Swoole 长连接、或是调用 Python/Go 写的微服务,每个环节的时区处理逻辑都可能不同。比如 Laravel 往 Redis 存 Carbon::now()->toDateTimeString(),Python 服务拿去解析时默认当本地时区,结果又差 8 小时。
- 对外输出时间一律用 ISO 8601 带时区格式:
$carbon->toISOString()(即2024-05-20T14:30:00+08:00),别用toDateTimeString() - 入库前统一转成 UTC 存储(配合
Carbon::now('UTC')),应用层读取后再转本地显示,这是最可控的方案 - 云数据库如 AWS RDS、腾讯云 CDB,控制台里看到的“时区设置”只是管理界面显示用的,不影响实际连接行为,必须靠连接参数或 SQL 初始化命令干预











