json_encode() 将 datetime 转为空对象 {} 是因默认不支持该类型,需用 format() 或实现 jsonserializable 接口转为 iso 8601 字符串,并确保 date_default_timezone_set() 正确设置时区。

PHP json_encode() 为什么把 DateTime 变成空对象?
因为 json_encode() 默认不认识 DateTime 对象,既不调用其 __toString(),也不自动转成字符串,直接序列化成空 {}。这不是 bug,是设计如此——它只处理数组、标量和能递归展开的公共属性。
常见错误现象:json_encode(['time' => new DateTime()]) 输出 {"time":{}},前端拿到的是个空对象,解析失败或显示 undefined。
- 解决办法不是改
DateTime类,而是提前把它转成字符串 - 最稳妥的是用
format()指定格式,比如$dt->format('Y-m-d\TH:i:sP')(ISO 8601),兼容 JavaScriptDate构造函数 - 别依赖
__toString()—— 它默认输出的是Y-m-d H:i:s,没带时区信息,且空格不是 ISO 标准分隔符,JS 解析可能出错
用 JsonSerializable 接口统一处理时间字段
当项目里大量出现 DateTime 或自定义时间类(如 Carbon)时,硬写 format() 易漏、难维护。实现 JsonSerializable 是更干净的方案。
使用场景:API 返回数组含多个时间字段,或封装了带时间属性的 DTO 类。
立即学习“PHP免费学习笔记(深入)”;
- 在类中实现
jsonSerialize()方法,返回字符串而非对象 - 例如:
class Event implements JsonSerializable { public function jsonSerialize() { return [ 'id' => $this->id, 'starts_at' => $this->startsAt->format('c'), // 'c' 等价于 ISO 8601 'ends_at' => $this->endsAt?->format('c'), ]; } } - 注意:如果字段可能为
null,?->format()语法可避免报错;但format('c')输出带毫秒(如2024-05-20T14:30:00+08:00),某些老旧系统不支持,必要时改用Y-m-d\TH:i:sP
全局替换:用 JSON_UNESCAPED_UNICODE 和时区设置防乱码与时差
中文时间字符串本身不会乱码,但若服务器时区设错,format() 输出的时间就偏了;而 JSON_UNESCAPED_UNICODE 不是为日期服务的,但它常被一起误用。
-
date_default_timezone_set('Asia/Shanghai')必须在json_encode()前调用,否则new DateTime()默认用 UTC,format()输出却是东八区时间,造成“明明设了 14:00 却变成 06:00” -
JSON_UNESCAPED_UNICODE只影响中文字符是否被转成\uXXXX,和日期无关;但它常和日期一起出现在 JSON 配置里,容易让人以为它能“修复时间” - 真正影响时间解析的是格式一致性:后端用
Y-m-d\TH:i:sP,前端new Date(str)才能正确识别时区;用Y-m-d H:i:s则会被当成本地时间,跨时区访问必然错
Carbon 实例要注意 toIso8601String() 和 toISOString() 的区别
很多人用 Carbon 就直接链式调用 toISOString(),结果发现输出少了时区(如 2024-05-20T14:30:00Z),本地时间被强制转成 UTC,和预期不符。
-
toISOString()总是返回 UTC 时间 +Z后缀,不管原始时区是什么 -
toIso8601String()保留原始时区,输出如2024-05-20T14:30:00+08:00,这才是前后端对齐的关键 - 如果你用
Carbon::now(),它默认是服务器时区,直接toISOString()就等于“把本地时间强行当 UTC 发出去”,前端再转一次,时间就差 8 小时











