Date & Time
概览
| 名称 | 别名 | 存储大小 | 精度 | 最小值 | 最大值 | 格式 |
|---|---|---|---|---|---|---|
| DATE | 4 字节 | 天 | 0001-01-01 | 9999-12-31 | YYYY-MM-DD | |
| TIMESTAMP | DATETIME | 8 字节 | 微秒 | 0001-01-01 00:00:00.000000 | 9999-12-31 23:59:59.999999 UTC | YYYY-MM-DD hh:mm:ss[.fraction],显示时遵循会话时区 |
| TIMESTAMP_TZ | TIMESTAMP WITH TIME ZONE | 8 字节 | 微秒 | 0001-01-01 00:00:00.000000 | 9999-12-31 23:59:59.999999 UTC | YYYY-MM-DD hh:mm:ss[.fraction]±hh:mm,存储 UTC 值及原始偏移 |
DATE 仅保存日历日期;TIMESTAMP 内部存储 UTC 时间,但会根据当前会话的时区进行渲染;TIMESTAMP_TZ 则会保留原始的时区偏移量,适用于审计或数据复制场景。
示例
DATE
CREATE TABLE events (event_date DATE);
INSERT INTO events VALUES ('2024-01-15'), ('2024-12-31');
SELECT * FROM events;
结果:
┌────────────┐
│ event_date │
├────────────┤
│ 2024-01-15 │
│ 2024-12-31 │
└────────────┘
TIMESTAMP
CREATE TABLE meetings (
meeting_id INT,
meeting_time TIMESTAMP
);
INSERT INTO meetings VALUES (1, '2024-01-15 14:00:00+08:00');
SETTINGS (timezone = 'UTC')
SELECT meeting_id, meeting_time FROM meetings;
SETTINGS (timezone = 'America/New_York')
SELECT meeting_id, meeting_time FROM meetings;
结果(timezone = 'UTC'):
┌────────────┬──────────────────────┐
│ meeting_id │ meeting_time │
├────────────┼──────────────────────┤
│ 1 │ 2024-01-15T06:00:00 │
└────────────┴──────────────────────┘
结果(timezone = 'America/New_York'):
┌────────────┬──────────────────────┐
│ meeting_id │ meeting_time │
├────────────┼──────────────────────┤
│ 1 │ 2024-01-15T01:00:00 │
└────────────┴──────────────────────┘
TIMESTAMP_TZ
CREATE TABLE system_logs (
log_id INT,
log_time TIMESTAMP_TZ
);
INSERT INTO system_logs VALUES
(1, '2024-01-15 14:00:00+08:00'),
(2, '2024-01-15 06:00:00+00:00'),
(3, '2024-01-15 01:00:00-05:00');
SETTINGS (timezone = 'UTC')
SELECT log_id, TO_STRING(log_time) AS log_time FROM system_logs;
SETTINGS (timezone = 'Asia/Shanghai')
SELECT log_id, TO_STRING(log_time) AS log_time FROM system_logs;
结果(timezone = 'UTC'):
┌────────┬────────────────────────────────────────────┐
│ log_id │ log_time │
├────────┼────────────────────────────────────────────┤
│ 1 │ 2024-01-15 14:00:00.000000 +0800 │
│ 2 │ 2024-01-15 06:00:00.000000 +0000 │
│ 3 │ 2024-01-15 01:00:00.000000 -0500 │
└────────┴────────────────────────────────────────────┘
结果(timezone = 'Asia/Shanghai'):
┌────────┬────────────────────────────────────────────┐
│ log_id │ log_time │
├────────┼────────────────────────────────────────────┤
│ 1 │ 2024-01-15 14:00:00.000000 +0800 │
│ 2 │ 2024-01-15 06:00:00.000000 +0000 │
│ 3 │ 2024-01-15 01:00:00.000000 -0500 │
└────────┴────────────────────────────────────────────┘
由于偏移量是存储值的一部分,因此显示结果不会随会话时区改变。
如何选择合适的类型
- 如果只需要日历日期而不需要具体时间,请使用
DATE。 - 如果希望不同会话在各自的本地时区显示同一时刻,请使用
TIMESTAMP。 - 如果必须保留输入的时区偏移以用于合规性要求或调试,请使用
TIMESTAMP_TZ。
夏令时调整
启用 enable_dst_hour_fix 后,当夏令时导致某些时间段缺失(跳过)时,Databend 会自动将缺失的时间向后推移。
SET enable_dst_hour_fix = 1;
SETTINGS (timezone = 'America/Toronto')
SELECT to_datetime('2024-03-10 02:01:00');
结果:
┌────────────────────────────────────┐
│ to_datetime('2024-03-10 02:01:00') │
├────────────────────────────────────┤
│ 2024-03-10T03:01:00 │
└────────────────────────────────────┘
如果希望对缺失的时间报错而不是自动调整,可以使用 SET enable_dst_hour_fix = 0 恢复默认行为。
无效值处理
超出支持范围的日期会自动钳制(clamp)到其允许的最小值或最大值。
SELECT
ADD_DAYS(TO_DATE('9999-12-31'), 1) AS overflow_date,
SUBTRACT_MINUTES(TO_DATE('1000-01-01'), 1) AS underflow_timestamp;
结果:
┌───────────────┬──────────────────────────┐
│ overflow_date │ underflow_timestamp │
├───────────────┼──────────────────────────┤
│ 0001-01-01 │ 0999-12-31T18:41:28 │
└───────────────┴──────────────────────────┘
值会回绕到可表示的最小日期或时间戳,而不是抛出错误。
日期和时间格式化
TO_DATE 和 TO_TIMESTAMP 等函数支持显式的格式字符串。你可以通过调整 date_format_style 和 week_start 来控制解析或渲染的方式。
日期格式风格
使用 date_format_style 可在两种格式词汇表之间切换:
- MySQL(默认):使用
%Y、%m、%d等说明符。 - Oracle:使用
YYYY、MM、DD等说明符,以匹配 ANSI 风格的掩码。
-- Oracle 风格掩码
SETTINGS (date_format_style = 'Oracle')
SELECT to_string('2024-04-05'::DATE, 'YYYY-MM-DD');
结果(Oracle):
┌──────────────────────────────────────┐
│ to_string('2024-04-05'::DATE, 'YYYY-MM-DD') │
├──────────────────────────────────────┤
│ 2024-04-05 │
└──────────────────────────────────────┘
-- 切换回 MySQL 风格掩码
SETTINGS (date_format_style = 'MySQL')
SELECT to_string('2024-04-05'::DATE, '%Y-%m-%d');
结果(MySQL):
┌──────────────────────────────────────┐
│ to_string('2024-04-05'::DATE, '%Y-%m-%d') │
├──────────────────────────────────────┤
│ 2024-04-05 │
└──────────────────────────────────────┘
周起始配置
week_start 定义了在使用 WEEK 精度时,DATE_TRUNC 或 TRUNC 等函数将哪一天视为一周的开始。
SETTINGS (week_start = 0) SELECT DATE_TRUNC(WEEK, to_date('2024-04-05')); -- 周日
SETTINGS (week_start = 1) SELECT DATE_TRUNC(WEEK, to_date('2024-04-05')); -- 周一
结果(week_start = 0):
┌────────────────────────────────┐
│ DATE_TRUNC(WEEK, TO_DATE('2024-04-05')) │
├────────────────────────────────┤
│ 2024-03-31 │
└────────────────────────────────┘
结果(week_start = 1):
┌────────────────────────────────┐
│ DATE_TRUNC(WEEK, TO_DATE('2024-04-05')) │
├────────────────────────────────┤
│ 2024-04-01 │
└────────────────────────────────┘
MySQL 格式说明符
为了处理日期和时间格式化,Databend 使用了 chrono::format::strftime 模块,这是 Rust chrono 库提供的标准模块。该模块允许对日期和时间的格式进行精确控制。以下内容摘自 https://docs.rs/chrono/latest/chrono/format/strftime/index.html:
| 说明符 | 示例 | 描述 |
|---|---|---|
| 日期说明符: | ||
| %Y | 2001 | 完整的公历年份,零填充至 4 位。chrono 支持的年份范围为 -262144 到 262143。注意:公元前 1 年之前或公元 9999 年之后的年份需要带符号(+/-)。 |
| %C | 20 | 公历年份除以 100,零填充至 2 位。 |
| %y | 01 | 公历年份对 100 取模,零填充至 2 位。 |
| %m | 07 | 月份(01–12),零填充至 2 位。 |
| %b | Jul | 月份缩写。始终为 3 个字母。 |
| %B | July | 月份全称。解析时也接受相应的缩写。 |
| %h | Jul | 同 %b。 |
| %d | 08 | 日期(01–31),零填充至 2 位。 |
| %e | 8 | 同 %d,但使用空格填充。同 %_d。 |
| %a | Sun | 星期几的缩写。始终为 3 个字母。 |
| %A | Sunday | 星期几的全称。解析时也接受相应的缩写。 |
| %w | 0 | 周日 = 0, 周一 = 1, …, 周六 = 6。 |
| %u | 7 | 周一 = 1, 周二 = 2, …, 周日 = 7。(ISO 8601) |
| %U | 28 | 以周日为一周开始的周数(00–53),零填充至 2 位。 |
| %W | 27 | 同 %U,但第一周从该年的第一个周一开始。 |
| %G | 2001 | 同 %Y,但使用 ISO 8601 周日历的年份。 |
| %g | 01 | 同 %y,但使用 ISO 8601 周日历的年份。 |
| %V | 27 | 同 %U,但使用 ISO 8601 周日历的周数(01–53)。 |
| %j | 189 | 一年中的第几天(001–366),零填充至 3 位。 |
| %D | 07/08/01 | 月-日-年格式。同 %m/%d/%y。 |
| %x | 07/08/01 | 本地化的日期表示(例如 12/31/99)。 |
| %F | 2001-07-08 | 年-月-日格式(ISO 8601)。同 %Y-%m-%d。 |
| %v | 8-Jul-2001 | 日-月-年格式。同 %e-%b-%Y。 |
| 时间说明符: | ||
| %H | 00 | 小时(00–23),零填充至 2 位。 |
| %k | 0 | 同 %H,但使用空格填充。同 %_H。 |
| %I | 12 | 12 小时制的小时(01–12),零填充至 2 位。 |
| %l | 12 | 同 %I,但使用空格填充。同 %_I。 |
| %P | am | 12 小时制的 am 或 pm。 |
| %p | AM | 12 小时制的 AM 或 PM。 |
| %M | 34 | 分钟(00–59),零填充至 2 位。 |
| %S | 60 | 秒(00–60),零填充至 2 位。 |
| %f | 026490000 | 自上一整秒以来的纳秒部分。Databend 建议先将整数字符串转换为 Integer,而不是使用此说明符。参见 Converting Integer to Timestamp 示例。 |
| %.f | .026490 | 类似 .%f 但左对齐。这些都会消耗前导点号。 |
| %.3f | .026 | 类似 .%f 但左对齐并固定长度为 3。 |
| %.6f | .026490 | 类似 .%f 但左对齐并固定长度为 6。 |
| %.9f | .026490000 | 类似 .%f 但左对齐并固定长度为 9。 |
| %3f | 026 | 类似 %.3f 但不带前导点号。 |
| %6f | 026490 | 类似 %.6f 但不带前导点号。 |
| %9f | 026490000 | 类似 %.9f 但不带前导点号。 |
| %R | 00:34 | 时-分格式。同 %H:%M。 |
| %T | 00:34:60 | 时-分-秒格式。同 %H:%M:%S。 |
| %X | 00:34:60 | 本地化的时间表示(例如 23:13:48)。 |
| %r | 12:34:60 AM | 12 小时制的时-分-秒格式。同 %I:%M:%S %p。 |
| 时区说明符: | ||
| %Z | ACST | 本地时区名称。解析时跳过所有非空白字符。 |
| %z | +0930 | 本地时间相对于 UTC 的偏移量(UTC 为 +0000)。 |
| %:z | +09:30 | 同 %z,但带冒号。 |
| %::z | +09:30:00 | 本地时间相对于 UTC 的偏移量,带秒。 |
| %:::z | +09 | 本地时间相对于 UTC 的偏移量,不带分钟。 |
| %#z | +09 | 仅用于解析:同 %z,但允许分钟缺失或存在。 |
| 日期和时间说明符: | ||
| %c | Sun Jul 8 00:34:60 2001 | 本地化的日期和时间(例如 Thu Mar 3 23:05:25 2005)。 |
| %+ | 2001-07-08T00:34:60.026490+09:30 | ISO 8601 / RFC 3339 日期和时间格式。 |
| %s | 994518299 | UNIX 时间戳,自 1970-01-01 00:00 UTC 以来的秒数。Databend 建议先将整数字符串转换为 Integer,而不是使用此说明符。参见 Converting Integer to Timestamp 示例。 |
| 特殊说明符: | ||
| %t | 字面量制表符 (\t)。 | |
| %n | 字面量换行符 (\n)。 | |
| %% | 字面量百分号。 |
可以覆盖数字说明符 %? 的默认填充行为。这不允许用于其他说明符,否则会导致 BAD_FORMAT 错误。
| 修饰符 | 描述 |
|---|---|
| %-? | 抑制任何填充,包括空格和零。(例如 %j = 012, %-j = 12) |
| %_? | 使用空格作为填充。(例如 %j = 012, %_j = 12) |
| %0? | 使用零作为填充。(例如 %e = 9, %0e = 09) |
-
%C, %y: 这是向下取整除法,所以公元前 100 年(年份 -99)将分别打印 -1 和 99。
-
%U: 第 1 周从该年的第一个周日开始。第一个周日之前的日子可能属于第 0 周。
-
%G, %g, %V: 第 1 周是该年中至少有 4 天的第一个周。不存在第 0 周,因此应与 %G 或 %g 一起使用。
-
%S: 考虑到闰秒,所以可能是 60。
-
%f, %.f, %.3f, %.6f, %.9f, %3f, %6f, %9f: 默认的 %f 右对齐并始终用零填充至 9 位,以兼容 glibc 等,因此它总是计算自上一整秒以来的纳秒数。例如,上一秒后 7 毫秒将打印 007000000,解析 7000000 也会得到相同结果。
变体 %.f 左对齐,并根据精度打印 0、3、6 或 9 位小数。例如,上一秒后 70 毫秒在 %.f 下将打印 .070(注意:不是 .07),解析 .07、.070000 等也会得到相同结果。注意,如果小数部分为零或下一个字符不是 .,它们可能什么都不打印或读取。
变体 %.3f、%.6f 和 %.9f 左对齐,并根据 f 前面的数字打印 3、6 或 9 位小数。例如,上一秒后 70 毫秒在 %.3f 下将打印 .070(注意:不是 .07),解析 .07、.070000 等也会得到相同结果。注意,如果小数部分为零或下一个字符不是 .,它们可能什么都读不到,但会按指定长度打印。
变体 %3f、%6f 和 %9f 左对齐,并根据 f 前面的数字打印 3、6 或 9 位小数,但不带前导点号。例如,上一秒后 70 毫秒在 %3f 下将打印 070(注意:不是 07),解析 07、070000 等也会得到相同结果。注意,如果小数部分为零,它们可能什么都读不到。
-
%Z: 偏移量不会从解析的数据中填充,也不会被验证。时区被完全忽略。类似于 glibc strptime 对此格式代码的处理。
无法可靠地从缩写转换为偏移量,例如 CDT 可能意味着中部夏令时间(北美)或中国夏令时间。
-
%+: 同 %Y-%m-%dT%H:%M:%S%.f%:z,即秒的小数部分为 0、3、6 或 9 位,时区偏移带冒号。
此格式也支持用 Z 或 UTC 代替 %:z。它们等同于 +00:00。
注意,所有的 T、Z 和 UTC 均不区分大小写解析。
典型的 strftime 实现对此说明符有不同(且依赖于区域设置)的格式。虽然 Chrono 的 %+ 格式要稳定得多,但如果想控制确切的输出,最好避免使用此说明符。
-
%s: 不带填充,可以是负数。对于 Chrono 而言,它只计算非闰秒,因此与 ISO C strftime 行为略有不同。
Oracle 格式说明符
当 date_format_style 设置为 'Oracle' 时,支持以下格式说明符:
| Oracle 格式 | 描述 | 示例输出(针对 '2024-04-05 14:30:45.123456') |
|---|---|---|
| YYYY | 4 位年份 | 2024 |
| YY | 2 位年份 | 24 |
| MMMM | 月份全称 | April |
| MON | 月份缩写 | Apr |
| MM | 月份数字 (01-12) | 04 |
| DD | 月份中的日期 (01-31) | 05 |
| DY | 星期几缩写 | Fri |
| HH24 | 一天中的小时 (00-23) | 14 |
| HH12 | 一天中的小时 (01-12) | 02 |
| AM/PM | 上午/下午指示符 | PM |
| MI | 分钟 (00-59) | 30 |
| SS | 秒 (00-59) | 45 |
| FF | 小数秒 | 123456 |
| UUUU | ISO 周日历年份 | 2024 |
| TZH:TZM | 时区小时和分钟,带冒号 | +08:00 |
| TZH | 时区小时 | +08 |
比较 MySQL 和 Oracle 格式风格的示例:
-- MySQL 格式风格(默认)
SELECT to_string('2022-12-25'::DATE, '%m/%d/%Y');
┌────────────────────────────────┐
│ to_string('2022-12-25', '%m/%d/%Y') │
├────────────────────────────────┤
│ 12/25/2022 │
└────────────────────────────────┘
-- Oracle 格式风格(数据同上)
SETTINGS (date_format_style = 'Oracle')
SELECT to_string('2022-12-25'::DATE, 'MM/DD/YYYY');
┌────────────────────────────────┐
│ to_string('2022-12-25', 'MM/DD/YYYY') │
├────────────────────────────────┤
│ 12/25/2022 │
└────────────────────────────────┘
SET enable_dst_hour_fix = 1;
SETTINGS (timezone = 'America/Toronto')
SELECT to_datetime('2024-03-10 02:01:00');
结果:
┌────────────────────────────────────┐
│ to_datetime('2024-03-10 02:01:00') │
├────────────────────────────────────┤
│ 2024-03-10T03:01:00 │
└────────────────────────────────────┘
如需恢复严格模式,可 SET enable_dst_hour_fix = 0。
无效值
越界或无效的日期时间会被钳制到最小值:
SELECT
ADD_DAYS(TO_DATE('9999-12-31'), 1) AS overflow_date,
SUBTRACT_MINUTES(TO_DATE('1000-01-01'), 1) AS underflow_timestamp;
结果:
┌───────────────┬──────────────────────────┐
│ overflow_date │ underflow_timestamp │
├───────────────┼──────────────────────────┤
│ 0001-01-01 │ 0999-12-31T18:41:28 │
└───────────────┴──────────────────────────┘
格式化与解析
TO_DATE / TO_TIMESTAMP 等函数支持格式掩码,可通过 date_format_style 设定 MySQL/Oracle 风格;week_start 控制周的起始。
-- Oracle 风格
SETTINGS (date_format_style = 'Oracle')
SELECT to_string('2024-04-05'::DATE, 'YYYY-MM-DD');
结果:
┌──────────────────────────────────────┐
│ to_string('2024-04-05'::DATE, 'YYYY-MM-DD') │
├──────────────────────────────────────┤
│ 2024-04-05 │
└──────────────────────────────────────┘
-- MySQL 风格
SETTINGS (date_format_style = 'MySQL')
SELECT to_string('2024-04-05'::DATE, '%Y-%m-%d');
结果:
┌──────────────────────────────────────┐
│ to_string('2024-04-05'::DATE, '%Y-%m-%d') │
├──────────────────────────────────────┤
│ 2024-04-05 │
└──────────────────────────────────────┘
SETTINGS (week_start = 0) SELECT DATE_TRUNC(WEEK, to_date('2024-04-05'));
SETTINGS (week_start = 1) SELECT DATE_TRUNC(WEEK, to_date('2024-04-05'));
结果:
┌────────────────────────────────┐
│ DATE_TRUNC(WEEK, TO_DATE('2024-04-05')) │
├────────────────────────────────┤
│ 2024-03-31 │
└────────────────────────────────┘
┌────────────────────────────────┐
│ DATE_TRUNC(WEEK, TO_DATE('2024-04-05')) │
├────────────────────────────────┤
│ 2024-04-01 │
└────────────────────────────────┘
有关完整格式符号列表,请参阅 Rust Chrono 库的 strftime 文档。

