config/app.php 的 'timezone' 仅控制 PHP 原生时间函数和无上下文 Carbon 实例化,不影响数据库时区、Eloquent 行为、Carbon 解析逻辑及 API 输出;多时区需数据库存 UTC、查时按用户时区转换。

config/app.php 里的 'timezone' 只改 PHP 默认时区,不控制数据库存取、Eloquent 行为或 Carbon 解析逻辑——单靠它无法让 created_at 显示北京时间,也不能解决多时区用户问题。
config/app.php 的 'timezone' 到底管什么
它只影响 PHP 原生时间函数(date()、strtotime())和无上下文的 Carbon 实例化(如 now()、Carbon::now()、new Carbon())。但以下场景它完全不生效:
- Eloquent 写入
created_at字段时,默认用当前 PHP 时区值,但若数据库是 MySQL +TIMESTAMP类型,实际入库值还会被 MySQL 自己的time_zone覆盖 -
Carbon::parse('2026-05-12')会按这个配置解析字符串,但如果你本意是“用户在东京选的时间”,那它就错当成上海时间了 - API 返回 JSON 时调用
toArray(),输出的 ISO 字符串时区偏移仍取决于 Carbon 实例当前时区,不是config/app.php的值
MySQL 连接层必须显式设 time_zone
即使 config/app.php 设了 'Asia/Shanghai',Laravel 不会自动给 PDO 连接发 SET time_zone。结果就是迁移里 $table->timestamps() 生成的 CURRENT_TIMESTAMP 用的是 MySQL 全局时区(常为 SYSTEM),不是你想要的 +08:00。
正确做法是在 config/database.php 的 MySQL 配置里加:
'options' => [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '+08:00'"
]
- 仅对 MySQL 有效;PostgreSQL 或 SQLite 不支持
- 必须确保连接字符集兼容(比如
utf8mb4),否则该命令可能静默失败 - 执行
SELECT @@session.time_zone验证是否生效
Carbon::tz() 和 setTimezone() 别混用
这是最常踩的坑:一个返回新实例,一个修改原实例。
-
$c = Carbon::now(); $c->tz('Europe/London');→$c没变,因为tz()返回新对象,你没接住 -
$c = Carbon::now(); $c->setTimezone('Europe/London');→$c被原地改掉,后续所有操作都基于伦敦时区 - 链式调用推荐用
tz():Carbon::parse('2026-05-12')->tz('Asia/Shanghai')->format('Y-m-d H:i') - 入库前转 UTC 必须用
->utc()(等价于->tz('UTC')),不是->setTimezone('UTC')—— 后者破坏原实例状态,影响后续复用
多时区用户不能依赖全局 timezone
把 'timezone' => 'Asia/Shanghai' 当成“全站北京时间”用,前端纽约用户看到的还是 08:00,不是他本地的 20:00。真要支持多时区,唯一靠谱路径是:
- 数据库统一存 UTC(
now()->utc()入库,迁移字段不加useCurrent()或确认 MySQL 已设 +00:00) - 查出后按用户时区转:
$post->created_at->tz($user->timezone ?? 'UTC')->format('Y-m-d H:i') - 用户提交带时区的时间,必须传时区上下文:
Carbon::parse($request->input('when'), $request->input('timezone'))->utc(),不能裸写Carbon::parse($request->input('when')) - Blade 里别信
@displayDate(),它对空或非法$user->timezone静默 fallback,建议显式判断并报错
时区不是配一次就完事的事——它横跨 PHP、数据库连接、SQL 执行、Carbon 实例生命周期、用户输入上下文五层,漏一层,时间就偏 8 小时。


















