SQLite 在日期和时间处理上的最大特点是没有独立的日期时间类型,所有时间值都需要借助 TEXT、INTEGER 或 REAL 三种基础类型来存储。这种设计让 SQLite 非常轻量,但也意味着必须自己约定存储格式。如果一张表里既有 2024-05-15 这样的文本,又有 1715731200 这样的整数,后续做范围查询和排序时就会非常混乱。理解这些底层表示之后,再配合 SQLite 内置的日期时间函数,才能把时间比较、时区转换、时间差计算处理得稳定可靠。

一、三种时间表示方式与选择
SQLite 支持用 TEXT、INTEGER、REAL 三种类型保存时间。TEXT 通常采用 ISO8601 格式,例如 2024-05-15 09:30:00,这种格式可读性最好,也便于直接查看和调试。只要所有文本都严格使用 YYYY-MM-DD HH:MM:SS 并且月、日、时、分、秒都补零,字符串比较就能与真实时间先后一致。但问题在于,如果有些记录写成 2024-1-5,有些写成 2024-01-05,字符串比较就会严重失真。
INTEGER 类型保存 Unix 时间戳,也就是从 1970-01-01 00:00:00 UTC 开始经过的秒数。时间戳是纯数字,排序和范围查询效率很高,适合日志、事件流水这类数据量较大的表。REAL 类型使用 Julian day 表示时间,虽然 SQLite 内部支持,但浮点数存在精度问题,实际业务中不建议用它保存普通时间。下面示例展示了三种类型的写入方式:
-- 创建使用三种时间类型的表
CREATE TABLE events (
id INTEGER PRIMARY KEY,
event_text TEXT,
event_int INTEGER,
event_real REAL
);
-- 插入同一时间
INSERT INTO events (event_text, event_int, event_real)
VALUES (
'2024-05-15 09:30:00',
strftime('%s', '2024-05-15 09:30:00'),
julianday('2024-05-15 09:30:00')
);
选择存储格式时,优先考虑查询模式。如果经常需要按时间段过滤,INTEGER 时间戳配合索引最省心;如果需要人工查看数据、导出报表,TEXT 格式更直观。但不建议在同一张表里混用多种格式,否则同一列的处理逻辑会变得复杂,容易产生隐藏错误。
二、五个核心日期时间函数与修饰符
SQLite 提供了 date、time、datetime、julianday、strftime 五个核心函数。date 返回日期部分,time 返回时间部分,datetime 返回日期加时间,julianday 返回对应的 Julian day 数值,strftime 则用于自定义格式化输出。它们都接收一个时间字符串作为参数,并可以继续追加修饰符。最常用的时间字符串是 now,表示当前 UTC 时间。
SELECT date('now') AS current_date;
SELECT time('now') AS current_time;
SELECT datetime('now') AS current_datetime;
SELECT julianday('now') AS julian_day;
SELECT strftime('%Y-%m-%d %H:%M:%S', 'now') AS formatted_time;
修饰符让时间计算变得灵活,例如 start of month 可以取当月第一天,+1 day 可以加一天,-1 month 可以减一个月,localtime 和 utc 用于时区转换。修饰符会从左到右依次作用在时间值上,因此顺序很重要。下面示例先取指定日期,再加一天,最后回到当月第一天。
SELECT datetime('now', 'start of month') AS month_start;
SELECT datetime('2024-05-15 00:00:00', '+1 day') AS next_day;
SELECT datetime('now', 'localtime') AS local_now;
SELECT datetime('2024-05-15 00:00:00', '+1 day', 'start of month') AS adjusted;
需要特别注意,SQLite 在解析时间字符串后会先转换为 UTC,再应用修饰符。也就是说,如果传入的是本地时间字符串,直接使用 localtime 修饰符不一定能得到预期结果。跨时区应用最好统一使用 UTC 存储,展示层再做本地化转换,避免数据库内部逻辑混乱。
三、常见疑问与容易出错的场景
很多人第一次踩坑是在字符串比较上。比如用 TEXT 存储日期,但格式不统一,业务代码里就会出现 2024-1-15 和 2024-10-1 这样的混合值。由于字符串比较按字典序进行,2024-10-1 反而会排在 2024-1-15 前面,因为第二个字符 1 比 0 大。这个错误非常隐蔽,只有当数据跨月、跨年时才暴露出来。
-- 错误:格式不统一,字符串比较结果不符合时间先后 SELECT '2024-1-15' > '2024-10-1' AS result; -- 正确做法:统一使用补零格式 SELECT '2024-01-15' < '2024-10-01' AS correct_result;
时间差计算也容易出错。直接对两个日期字符串做减法没有任何意义,必须把日期转换为数值。Julian day 相减得到天数,Unix 时间戳相减得到秒数。下面的写法可以准确得到两个时间点之间的差值:
SELECT julianday('2024-05-16 10:00:00') - julianday('2024-05-15 09:30:00') AS diff_days;
SELECT (strftime('%s','2024-05-16 10:00:00') - strftime('%s','2024-05-15 09:30:00')) AS diff_seconds;
另一个常见疑问是 now 返回的时间到底是本地时间还是 UTC。默认情况下,SQLite 的 now 以及相关函数返回的是 UTC 时间,只有显式使用 localtime 修饰符才会转换为系统本地时区。如果服务器部署在多个时区,或者应用跨时区提供服务,建议所有写入都使用 UTC,读取时再根据用户时区转换,这样能减少大量排查成本。
四、存储与索引优化建议
如果表需要按时间做频繁的范围查询,建议使用 INTEGER 类型保存 Unix 时间戳,并建立索引。时间戳是整数,索引体积小、比较速度快,查询最近七天、最近一小时等需求都能高效完成。例如:
CREATE TABLE logs (
id INTEGER PRIMARY KEY,
message TEXT,
created_at INTEGER
);
CREATE INDEX idx_logs_created_at ON logs(created_at);
-- 查询最近7天的日志
SELECT message
FROM logs
WHERE created_at >= strftime('%s', 'now', '-7 days');
如果可读性要求高于性能,用 TEXT 保存 ISO8601 字符串也可以,但必须保证所有值都采用统一的 UTC 时间和补零格式。否则索引即使建了,范围查询也可能因为字符串顺序错误而失效。不要使用 REAL 类型存储业务时间,浮点精度问题会导致秒级甚至毫秒级的时间偏差,排查起来非常困难。
综合来看,SQLite 日期时间处理并不复杂,关键是先确定存储格式,再统一时区和格式约定。日常开发中优先考虑整数时间戳加索引,必要时配合 strftime 做展示格式化。把基础约定做好,后续的时间比较、范围查询和统计分析都会稳定很多。
SQLite日期和时间strftime时间戳修改时间:2026-09-30 23:01:49