TO_TIMESTAMP函数不支持毫秒级时间戳,需先将毫秒值转为秒(除以1000并用numeric类型避免截断),再通过AT TIME ZONE指定时区转换为timestamptz,并需校验数值范围与NULL值。

TO_TIMESTAMP函数不直接支持毫秒级时间戳
PostgreSQL 的 TO_TIMESTAMP 默认把输入当作「秒级」处理,传入毫秒值(比如 1717023600000)会当成 1717023600000 秒,结果是公元 56373 年——明显错乱。根本原因:它没内置毫秒单位识别逻辑,必须手动缩放。
正确做法是先除以1000再转
毫秒转秒只需除以 1000,再喂给 TO_TIMESTAMP。注意要用 numeric 类型避免整数截断,尤其当原始字段是 BIGINT 时:
SELECT TO_TIMESTAMP(1717023600000::numeric / 1000);
常见错误写法包括:
-
TO_TIMESTAMP(1717023600000 / 1000)—— 整数除法在 PostgreSQL 中会截断小数,1717023600000 / 1000 = 1717023600,但若数值末尾非整千(如1717023600123),/默认是整除,丢失毫秒精度 - 漏掉类型转换,直接用
TO_TIMESTAMP(bigint_col / 1000),同样触发整除 - 用
CAST(... AS double precision)也可以,但::numeric更稳妥,避免浮点舍入误差
带时区的毫秒时间戳要额外指定 timezone
TO_TIMESTAMP 返回的是 timestamp without time zone,如果原始毫秒值是 UTC 时间,但你想转成带时区的 timestamptz,得显式 AT TIME ZONE:
SELECT TO_TIMESTAMP(1717023600000::numeric / 1000) AT TIME ZONE 'UTC';
这行返回的是 timestamptz 类型;若省略 AT TIME ZONE,即使输入是 UTC 毫秒,结果也只是一个无时区的时间点,后续参与时区计算可能出错。
实际使用中容易忽略两点:一是毫秒值本身是否真为 UTC(比如前端 JS 的 Date.now() 是本地时区?不,它始终是毫秒级 UTC 时间戳);二是数据库 timezone 配置会影响 AT TIME ZONE 解析行为,建议统一用显式时区名(如 'UTC' 或 'Asia/Shanghai'),别依赖 session 设置。
批量转换时注意 NULL 和非法数值
生产环境字段常含 NULL 或异常大/小值(比如 0、负数、超 13 位数字)。直接除法 + TO_TIMESTAMP 会报错或返回意外结果:
-
TO_TIMESTAMP(NULL / 1000)→NULL(安全) -
TO_TIMESTAMP(-1::numeric / 1000)→ 公元前时间(可能合法,但业务上通常不合理) -
TO_TIMESTAMP(99999999999999::numeric / 1000)→ 超出 PostgreSQL 支持范围(约公元 294276 年上限),抛date/time field value out of range
稳妥做法是加 CASE 过滤:
SELECT CASE
WHEN ts_ms BETWEEN 0 AND 253402300799999 THEN
TO_TIMESTAMP(ts_ms::numeric / 1000) AT TIME ZONE 'UTC'
ELSE NULL
END AS converted_time
FROM your_table;
其中 253402300799999 是 PostgreSQL 最大支持毫秒值(对应 '9999-12-31 23:59:59.999')。
时区语义和数值边界检查不是锦上添花,而是上线前必须确认的点——尤其跨系统对接时,毫秒来源五花八门,靠文档不如靠防御性 SQL。










