⌘+k ctrl+k
1.4 (LTS)
搜索快捷键 cmd + k | ctrl + k
时间戳类型

时间戳代表时间点。因此,它们结合了 DATE(日期)和 TIME(时间)信息。它们可以使用类型名称后跟符合 ISO 8601 格式的字符串来创建,格式为 YYYY-MM-DD hh:mm:ss[.zzzzzzzzz][+-TT[:tt]],这也是我们在本文档中使用的格式。超出支持精度的小数位将被忽略。

时间戳类型

名称 别名 描述
TIMESTAMP_NS   纳秒精度的朴素时间戳 (Naive timestamp)
TIMESTAMP DATETIME, TIMESTAMP WITHOUT TIME ZONE 微秒精度的朴素时间戳
TIMESTAMP_MS   毫秒精度的朴素时间戳
TIMESTAMP_S   秒精度的朴素时间戳
TIMESTAMPTZ TIMESTAMP WITH TIME ZONE 带时区信息的微秒精度时间戳

警告:由于目前尚无 TIMESTAMP_NS WITH TIME ZONE 数据类型,带有纳秒精度和 WITH TIME ZONE 语义的外部列(例如 设置了 isAdjustedToUTC=true 的 Parquet 时间戳列)在被 DuckDB 读取时会转换为 TIMESTAMP WITH TIME ZONE,从而丢失精度。

SELECT TIMESTAMP_NS '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123456789
SELECT TIMESTAMP '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123456
SELECT TIMESTAMP_MS '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123
SELECT TIMESTAMP_S '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00
SELECT TIMESTAMPTZ '1992-09-20 11:30:00.123456789';
1992-09-20 11:30:00.123456+00
SELECT TIMESTAMPTZ '1992-09-20 12:30:00.123456789+01:00';
1992-09-20 11:30:00.123456+00

DuckDB 区分 WITHOUT TIME ZONE(不带时区)和 WITH TIME ZONE(带时区)的时间戳(目前该类唯一的代表是 TIMESTAMP WITH TIME ZONE)。

尽管名称如此,TIMESTAMP WITH TIME ZONE 实际上并不存储时区信息。相反,它只存储从 Unix 纪元 1970-01-01 00:00:00+00 开始的非闰微秒数的 INT64 值,因此它能明确标识一个绝对时间点,或称 瞬时 (instant)。之所以称为“时区感知 (time zone aware)”和 WITH TIME ZONE,是因为该类型的时间戳算术、分箱 (binning) 以及字符串格式化都是在配置的时区中执行的,默认为系统时区,在上述示例中即为 UTC+00:00

相应的 TIMESTAMP WITHOUT TIME ZONE 存储相同的 INT64 数值,但其算术运算、分箱和字符串格式化遵循协调世界时 (UTC) 的直接规则,不含偏移量或时区。因此,TIMESTAMP 可以被解释为 UTC 时间戳,但更常见的情况是,它们用于表示在未指定时区下记录的本地时间观测值,对这些类型的操作可以理解为遵循名义时间逻辑的元组字段处理。将此类观测值(也可能存储在没有时区说明或 UTC 偏移量的原始字符串中)消除歧义并转换为明确的 TIMESTAMP WITH TIME ZONE 瞬时值,是一个常见的数据清洗问题。一种解决方案是在字符串后附加 UTC 偏移量,然后显式转换为 TIMESTAMP WITH TIME ZONE。或者,也可以先创建 TIMESTAMP WITHOUT TIME ZONE,然后结合时区说明来获取时区感知的 TIMESTAMP WITH TIME ZONE

字符串与朴素/时区感知时间戳之间的转换

不带 UTC 偏移量或 IANA 时区名称的字符串与 WITHOUT TIME ZONE 类型之间的转换是明确且直接的。在带有 UTC 偏移量或时区名称的字符串与 WITH TIME ZONE 类型之间的转换也是明确的,但需要 ICU 扩展来处理时区名称。

不带 UTC 偏移量或时区名称的字符串转换为 WITH TIME ZONE 类型时,该字符串会根据配置的时区进行解释。当带有 UTC 偏移量的字符串传递给 WITHOUT TIME ZONE 类型时,偏移量或时区说明会被忽略。当带有非 UTC 时区名称的字符串传递给 WITHOUT TIME ZONE 类型时,会抛出错误。

最后,当 WITH TIME ZONEWITHOUT TIME ZONE 类型通过显式或隐式转换相互转换时,转换过程将使用配置的时区。若要使用其他时区,可以使用 ICU 扩展提供的 timezone 函数。

SELECT
    timezone('America/Denver', TIMESTAMP '2001-02-16 20:38:40') AS aware1,
    timezone('America/Denver', TIMESTAMPTZ '2001-02-16 04:38:40') AS naive1,
    timezone('UTC', TIMESTAMP '2001-02-16 20:38:40+00:00') AS aware2,
    timezone('UTC', TIMESTAMPTZ '2001-02-16 04:38:40 Europe/Berlin') AS naive2;
aware1 naive1 aware2 naive2
2001-02-17 04:38:40+01 2001-02-15 20:38:40 2001-02-16 21:38:40+01 2001-02-16 03:38:40

请注意,TIMESTAMP 在结果显示时不带时区说明(遵循 ISO 8601 本地时间规则),而时区感知的 TIMESTAMPTZ 则会显示所配置时区(示例中为 'Europe/Berlin')的 UTC 偏移量。'America/Denver''Europe/Berlin' 在所涉所有瞬时的 UTC 偏移量分别为 -07:00+01:00

特殊值

可以使用三个特殊字符串来创建时间戳

输入字符串 描述
epoch 1970-01-01 00:00:00[+00] (Unix 系统时间零点)
infinity 晚于所有其他时间戳
-infinity 早于所有其他时间戳

infinity-infinity 为特殊情况,显示时保持不变,而 epoch 仅作为一种记号简写,在读取时会转换为相应的时间戳值。

SELECT '-infinity'::TIMESTAMP, 'epoch'::TIMESTAMP, 'infinity'::TIMESTAMP;
Epoch(纪元)
-infinity 1970-01-01 00:00:00 infinity

函数

请参阅 时间戳函数

时区

要理解时区和 WITH TIME ZONE 类型,最好从两个概念入手:瞬时 (instants)时间分箱 (temporal binning)

瞬时 (Instants)

瞬时是指绝对时间中的一个点,通常表示为从固定时间点(称为 epoch,纪元)开始的某个时间增量的计数值。这类似于地球表面位置的使用方式,即相对于赤道和格林威治子午线的经纬度。在 DuckDB 中,该固定点是 Unix 纪元 1970-01-01 00:00:00+00:00,增量单位取决于具体的数据类型(秒、毫秒、微秒或纳秒)。

时间分箱 (Temporal Binning)

分箱是连续数据处理中的一种常用做法:将一系列可能的值划分为连续的子集,分箱操作将实际值映射到它们所属的 箱 (bin) 中。时间分箱仅仅是将这种做法应用于瞬时;例如,将瞬时分箱为年、月和日。

Time Zone Instants at the Epoch Time Zone Instants at the Epoch

时间分箱规则非常复杂,通常分为两类:时区历法。对于大多数任务,历法通常是广泛使用的公历,但时区应用了特定区域的规则,差异很大。例如,下面是 'America/Los_Angeles' 时区在纪元附近的分箱情况:

Two Time Zones at the Epoch Two Time Zones at the Epoch

最常见的时间分箱问题发生在夏令时转换时。下例包含一次夏令时转换,其中“小时”箱长为两小时。为了区分这两个小时,需要另一个包含 UTC 偏移量的箱范围:

Two Time Zones at a Daylight Savings Time transition Two Time Zones at a Daylight Savings Time transition

时区支持

TIMESTAMPTZ 类型可以使用合适的扩展分箱为历法和时钟箱。内置的 ICU 扩展使用 International Components for Unicode (ICU) 时区和历法函数实现了所有的分箱和算术函数。

要设置使用的时区,请先加载 ICU 扩展。ICU 扩展随多个 DuckDB 客户端(包括 Python、R、JDBC 和 ODBC)预先打包,因此在这些情况下可以跳过此步骤。在其他情况下,您可能需要先安装并加载 ICU 扩展。

INSTALL icu;
LOAD icu;

接下来,使用 SET TimeZone 命令

SET TimeZone = 'America/Los_Angeles';

之后,TIMESTAMPTZ 的时间分箱操作将使用指定的时区实现。

可用时区列表可以从 pg_timezone_names() 表函数获取

SELECT
    name,
    abbrev,
    utc_offset
FROM pg_timezone_names()
ORDER BY
    name;

您还可以找到一份可用时区的参考表。

历法支持

ICU 扩展还支持使用 SET Calendar 命令的非公历历法。请注意,仅当 DuckDB 客户端未打包 ICU 扩展时,才需要执行 INSTALLLOAD 步骤。

INSTALL icu;
LOAD icu;
SET Calendar = 'japanese';

之后,TIMESTAMPTZ 的时间分箱操作将使用指定的历法实现。在此示例中,era(纪元)部分现在将报告日本天皇纪元编号。

可用历法列表可以从 icu_calendar_names() 表函数获取

SELECT name
FROM icu_calendar_names()
ORDER BY 1;

设置

TimeZoneCalendar 设置的当前值由 ICU 在启动时确定。它们可以从 duckdb_settings() 表函数中查询

SELECT *
FROM duckdb_settings()
WHERE name = 'TimeZone';
name value description 输入类型
TimeZone 欧洲/阿姆斯特丹 当前时区 VARCHAR
SELECT *
FROM duckdb_settings()
WHERE name = 'Calendar';
name value description 输入类型
Calendar gregorian(公历) 当前历法 VARCHAR

如果您发现分箱操作的表现与预期不符,请检查 TimeZoneCalendar 的值,并根据需要进行调整。

© 2025 DuckDB 基金会,阿姆斯特丹,荷兰
行为准则 商标使用指南