SQLite 其实没有日期类型
从 Postgres 或 MySQL 转过来的人都会在这里栽跟头。SQLite 只有五种存储类:NULL、INTEGER、REAL、TEXT、BLOB,仅此而已。没有 DATE,没有 DATETIME,也没有 TIMESTAMP。你可以在 CREATE TABLE 中“写”上 created_at DATETIME,SQLite 也会接受,但它会把值存成普通的文本或数字。
SQLite 真正提供的,是一组能理解三种约定格式的函数:
- ISO 8601 文本:
'2026-04-23'、'2026-04-23 10:15:00'、'2026-04-23T10:15:00.123Z'。 - Unix 时间戳:从 UTC 1970-01-01 起算的秒数,存为整数。
- 儒略日数:从公元前 4714 年起算的天数(带小数),存为实数。
选定一种并坚持使用。ISO 8601 文本可读性最好,而且按字符串就能正确排序,所以它是默认选择。
这是询问“现在”的四种方式:文本日期、文本日期时间、Unix 秒数和儒略日。它们描述的是同一个时刻。
五个日期函数
SQLite 提供了五个内置函数,几乎能满足所有需求:
date(time, ...):返回YYYY-MM-DD。time(time, ...):返回HH:MM:SS。datetime(time, ...):返回YYYY-MM-DD HH:MM:SS。julianday(time, ...):返回一个实数(非常适合计算差值)。strftime(format, time, ...):返回自定义格式的字符串。
每个函数的第一个参数是时间值,后面可以跟任意数量的修饰符字符串。
注意 'unixepoch':用它告诉日期函数,输入的整数是 Unix 时间戳而不是儒略日。不加它的话,SQLite 会假定这个数字是儒略日。
strftime:自定义日期格式
strftime 是最常用的主力函数。它使用的 % 格式码和你在 C 或 Python 中见过的一样:
你最常用到的格式码:
%Y:四位数年份。%m:月份(01 至 12)。%d:一个月中的第几天(01 至 31)。%H、%M、%S:时、分、秒。%w:星期几(0 表示星期日)。%j:一年中的第几天(001 至 366)。%s:Unix 时间戳。%f:带小数的秒(SS.SSS)。
strftime 也是“提取”日期某一部分的方法,SQLite 没有单独的 EXTRACT 或 YEAR() 函数。你只需把日期格式化成想要的那一部分,需要数字时再做类型转换:
strftime 总是返回文本,所以当你要做算术运算或按数值比较时,用 CAST(... AS INTEGER) 把它包起来。
修饰符:不用运算符的日期计算
正是这个特性让 SQLite 的日期处理用起来很舒服。在时间参数之后,你可以传入任意数量的“修饰符”字符串,它们会按顺序依次应用:
你会经常用到的修饰符:
'+N days'、'-N days',以及对应的hours、minutes、seconds、months、years。'start of day'、'start of month'、'start of year':截断到对应的起点。'weekday N':前进到下一个指定的星期几(0 表示星期日)。'localtime'和'utc':在时区之间转换。
“月末最后一天”的技巧(月初,加一个月,再减一天)值得记住。SQLite 没有 LAST_DAY 函数,但串联修饰符能达到同样的效果。
UTC 与本地时间
'now' 总是返回 UTC 时间。如果你想要本地时间,必须明确要求:
'localtime' 修饰符把 UTC 值转换为系统的本地时区。'utc' 修饰符正好相反:它把输入当作本地时间,再转换为 UTC。
稳妥的习惯是:所有时间都以 UTC 存储,只在显示时才转换成本地时间。在存储中混用时区会导致 bug,而且每年夏令时切换时都会冒出来两次。
比较日期与筛选范围
如果你把日期存成 ISO 8601 文本,比较运算和 BETWEEN 都能直接使用,因为 ISO 8601 按字典序排序的结果和按时间先后排序的结果完全一致。这正是它成为默认格式的全部原因。
半开区间(>= start、< end)是个好习惯,它能彻底避免“30 号午夜那一刻到底算不算在内”的问题。
对于“最近 7 天”,让 SQLite 来计算边界:
日期差
SQLite 没有 DATEDIFF。下面两种写法就能覆盖所有情况:
julianday() 的差值以天为单位(精确到小数),所以乘以 24 得到小时数,乘以 1440 得到分钟数。strftime('%s', ...) 的差值以秒为单位,想要整数结果时很方便。
如果你想要整天数,CAST(... AS INTEGER) 会截掉不足一天的小数部分:
存储日期:选定一种格式并坚持下去
有三种合理的选择,按你应该选用它们的频率排列:
- ISO 8601 文本(
TEXT)。在导出的数据中一眼就能看懂,排序正确,与所有日期函数都配合良好。这是默认选择。 - Unix 秒数(
INTEGER)。紧凑,比较速度快,没有时区歧义。当你有数百万行数据时是个好选择。读取时需要用datetime(col, 'unixepoch')转换回来。 - 儒略日(
REAL)。很少值得选用,除非你要做大量日期运算,并且希望在一列中保存亚秒级精度。
你“不应该”做的,是在同一列中混用多种格式。日期函数会悄悄地接受任意一种,但索引、排序和比较的结果都会变得毫无意义。
DEFAULT (datetime('now')) 相当于 SQLite 版的 DEFAULT CURRENT_TIMESTAMP:它会给每一行新数据打上当前的 UTC 时间,完全不需要应用代码参与。
按时间段分组
当你想按月、按周或按小时对行进行分组时,strftime 就派上用场了:
“每天各小时的订单数”“每个星期几的注册数”或“每分钟的事件数”也是同样的思路:选一个只保留你想要的粒度的格式字符串,按它分组,再做聚合。
下一篇:聚合函数
说到分组,那个 COUNT(*) 是 SQLite 聚合函数中最简单的一个。接下来我们会看看完整的一组:SUM、AVG、MIN、MAX,以及它们如何把许多行汇总成一个值。
常见问题
SQLite 有 DATE 或 DATETIME 数据类型吗?
没有,SQLite 没有专门的日期类型。你可以把日期存成 ISO 8601 格式的 TEXT('2026-04-23 10:15:00')、Unix 时间戳 INTEGER,或者儒略日 REAL。内置的日期函数接受这三种格式,默认返回 ISO 8601 文本。
如何在 SQLite 中获取当前日期和时间?
用 date('now') 获取当前日期,time('now') 获取当前时间,datetime('now') 同时获取两者,strftime('%s', 'now') 获取 Unix 时间戳。这些函数默认返回 UTC 时间,要转换成本地时间,就传入 'localtime' 修饰符:datetime('now', 'localtime')。
如何在 SQLite 中给日期加上天数或月数?
给任意日期函数传入一个修饰符字符串:date('2026-04-23', '+7 days')、date('now', '-1 month')、datetime('now', '+2 hours', '+30 minutes')。修饰符按顺序依次应用,单位包括 days、hours、minutes、seconds、months 和 years。
如何计算两个日期之间的差?
要算天数,用 julianday(end) - julianday(start):儒略日是浮点数,所以结果会包含不足一天的小数部分。要算秒数,就把两个 Unix 时间戳相减:strftime('%s', end) - strftime('%s', start)。SQLite 没有 DATEDIFF 函数,这两种写法几乎能覆盖所有情况。