convert_tz返回null主因是时区表未加载;需执行mysql_tzinfo_to_sql加载系统时区数据,并验证mysql.time_zone_name表非空,使用iana命名时区(如asia/shanghai)而非固定偏移以支持夏令时。

CONVERT_TZ 返回 NULL 不是写错了,是时区表没加载
执行 CONVERT_TZ(NOW(), '+00:00', 'Asia/Shanghai') 却返回 NULL,99% 的情况不是语法问题,而是 MySQL 根本不认识 Asia/Shanghai —— 它的时区名来自系统表 mysql.time_zone_name,而这个表默认为空。
验证方式很简单:SELECT COUNT(*) FROM mysql.time_zone_name; 如果结果是 0,就得手动加载:
- Linux:运行
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql - macOS:路径通常是
/var/db/timezones/zoneinfo,命令类似 - Windows:去 MySQL 官网下载 timezone SQL 文件,再用
mysql -u root mysql 导入
加载后重启 MySQL 或至少刷新权限(FLUSH PRIVILEGES;),再试一次函数。
CONVERT_TZ 第二个参数不是“源时区”,而是“当前值所处的时区”
很多文档说第二个参数是“源时区”,比如写成 'UTC',但这是有前提的:你传进去的时间值本身必须是 UTC 时间。如果原始字段是 DATETIME 类型且存的是东八区本地时间,却硬写 'UTC',结果必然错位 8 小时。
更安全的做法是显式绑定会话时区:
- 查当前会话时区:
SELECT @@session.time_zone; - 转换时用它作源时区:
CONVERT_TZ(created_at, @@session.time_zone, 'Asia/Shanghai') - 前提是你的应用连接时已设好时区,例如 JDBC 连接串加
?serverTimezone=Asia/Shanghai,或 Python pymysql 执行SET time_zone = 'Asia/Shanghai';
如果你的 created_at 字段明确存的是 UTC(推荐做法),那第二个参数才该固定写 '+00:00' 或 'UTC'。
别在视图里硬编码目标时区,改用 @@time_zone 动态读取
创建视图时写死 CONVERT_TZ(created_at, '+00:00', 'Asia/Shanghai') 是危险的:不同用户需要不同时区,但视图无法传参。MySQL 提供了会话变量 @@time_zone,它可被视图直接引用。
正确姿势:
- 确保客户端连接时设置了目标时区,比如 PHP 中
$pdo->exec("SET time_zone = 'Asia/Tokyo'"); - 视图定义用:
CREATE VIEW event_local AS SELECT id, CONVERT_TZ(created_at, '+00:00', @@time_zone) AS local_time FROM events; - 这样每个用户查视图,看到的就是自己会话指定的本地时间
注意:如果某个连接没设 time_zone,@@time_zone 默认是 'SYSTEM',可能触发系统调用开销;建议统一在连接池初始化时强制设置。
夏令时切换时偏移突变?别用 +08:00,改用 Asia/Shanghai
用固定偏移如 '+08:00' 看似简单,但它不会随夏令时自动调整。比如美国东部时间在 EST(UTC−5)和 EDT(UTC−4)之间切换,硬写 '+05:00' 会导致半年时间错 1 小时。
命名时区(IANA 格式)才能真正处理 DST:
- ✅ 正确:
CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'America/New_York') - ❌ 危险:
CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', '-05:00')
但前提是:你已加载完整时区表,且确认 mysql.time_zone_name.Name 中存在该名称(大小写敏感)。缩写如 CST、PDT 不可靠,一律避免。
最易被忽略的一点:CONVERT_TZ 对 DATETIME 和 TIMESTAMP 的行为差异极大。前者是纯字符串转换,后者在存储层已按 UTC 归一化——混用会导致双重转换,结果完全不可控。务必先确认字段类型和存储语义,再决定是否套用 CONVERT_TZ。











