Menu

SQLite 日期和时间:strftime、date() 与修饰符

SQLite 如何存储和处理日期:五个日期函数、格式字符串、修饰符,以及能让查询保持高效的存储选择。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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) 会截掉不足一天的小数部分:

存储日期:选定一种格式并坚持下去

有三种合理的选择,按你应该选用它们的频率排列:

  1. ISO 8601 文本(TEXT)。在导出的数据中一眼就能看懂,排序正确,与所有日期函数都配合良好。这是默认选择。
  2. Unix 秒数(INTEGER)。紧凑,比较速度快,没有时区歧义。当你有数百万行数据时是个好选择。读取时需要用 datetime(col, 'unixepoch') 转换回来。
  3. 儒略日(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 函数,这两种写法几乎能覆盖所有情况。

Coddy 编程语言插图

和 Coddy 一起学编程

开始