SQLite strftime 读取毫秒时间戳返回 NULL
SQLite 日期函数不会从 INTEGER 声明推断毫秒单位。确认原始单位后,在查询中换算成秒并紧跟 unixepoch,核对 UTC 年份、空值与历史数据。
字段中存着 1548263300000 这样的整数,strftime('%Y', created_ms) 却返回 NULL;换成 '2020-05-01' 文本以后又能得到年份。这里要分别核对时间戳单位和日期函数的解释方式。
SQLite 可以用整数保存日期,但整数本身不携带“这是 Unix 毫秒”的标记。下面针对已确认按 Unix epoch 毫秒写入的字段,处理查询表达式,不修改历史数据。
毫秒先换算成秒,再指定 unixepoch
普通数字时间值默认按儒略日解释。让日期函数按 Unix 时间读取,需要紧跟时间值写 'unixepoch';它接受的是秒,不能直接传毫秒。
用常量区分这两个条件:
SELECT
strftime('%Y', 1548263300000) AS original_year,
strftime('%Y', 1548263300000 / 1000.0, 'unixepoch') AS corrected_year;
这个例子的预期结果分别是 NULL 和文本 '2019'。它不需要建表或更新数据。确认实际列为毫秒后,查询可写成:
SELECT created_ms,
strftime('%Y', created_ms / 1000.0, 'unixepoch') AS year_utc
FROM events
LIMIT 10;
events 和 created_ms 是示例表、列名。这里用 1000.0 做浮点除法,避免整数除法向零截断小数秒。无需先 ROUND,也无需先转成日期文本再提取年份;四舍五入还可能让接近整秒边界的值改变。
列声明为 INTEGER,并不能证明单位
先看少量原始值和实际存储类型,并与写入代码的单位约定核对:
SELECT created_ms, typeof(created_ms) AS stored_type
FROM events
LIMIT 10;
如果实际存的是秒,就保留原值并使用 'unixepoch',不要再除以 1000。位数只能作为线索,不能替代写入约定;同一字段可能混有不同单位、日期文本或缺失值。不要为了让查询有结果,直接把整列覆盖成换算后的数字。
若样本是格式化日期文本,应按文本支持的格式处理,不能对它盲目做除法。若样本为 NULL,换算也不会补出缺失的时间。先确认数据含义,再选择表达式。
年份还取决于统计使用的时区
上面的 year_utc 按 UTC 取年。在跨年附近,UTC 年份可能与业务所在地不同。只有业务确实采用运行环境的本地时区时,才考虑在 'unixepoch' 后追加 'localtime';不要靠这个修饰符猜测用户所在时区。
修复后选几条已知日期核对,包括跨年附近的记录,确认年份与业务口径一致。对仍为 NULL 的行保留原始值继续调查,检查单位、值域与格式字符串;不要把空结果替换成当前年份以掩盖问题。
这项改动只影响读取表达式。上线前保留原查询,回退时恢复该表达式即可;如果此前已把毫秒误覆盖成秒,需要依据备份和写入记录恢复,不能用再次乘除来猜测每一行的单位。


