⌘+k ctrl+k
1.3 (稳定版)
搜索快捷键 cmd + k | ctrl + k
时间戳类型

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

时间戳类型

名称 别名 描述
TIMESTAMP_NS   纳秒精度的朴素时间戳
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 ZONEWITH TIME ZONE时间戳(其中目前唯一代表是TIMESTAMP WITH TIME ZONE)。

尽管有其名称,但TIMESTAMP WITH TIME ZONE并不存储时区信息。相反,它只存储自 Unix 纪元1970-01-01 00:00:00+00以来的非闰微秒数的INT64值,从而明确标识一个绝对时间点,或称“瞬时”。“时区感知”和WITH TIME ZONE标签的原因是,此类型的时间戳算术、分桶和字符串格式化是在配置的时区中执行的,该时区默认为系统时区,在上述示例中仅为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类型时,将存储字符串指定瞬时在配置时区中的本地时间。

最后,当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则显示配置时区的 UTC 偏移量,在示例中为'Europe/Berlin'。在所有相关瞬时,'America/Denver''Europe/Berlin'的 UTC 偏移量分别为-07:00+01:00

特殊值

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

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

infinity-infinity属于特殊情况,显示时保持不变,而值epoch只是一个符号缩写,在读取时会转换为相应的时间戳值。

SELECT '-infinity'::TIMESTAMP, 'epoch'::TIMESTAMP, 'infinity'::TIMESTAMP;
负数 纪元 正数
负无限远 1970-01-01 00:00:00 无限远

函数

请参阅时间戳函数

时区

要理解时区和WITH TIME ZONE类型,首先了解两个概念会有所帮助:瞬时和时间分桶。

瞬时

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

时间分桶

分桶是连续数据的一种常见做法:将一系列可能的值分解为连续的子集,并且分桶操作将实际值映射到它们所属的“桶”。时间分桶仅仅是将这种做法应用于瞬时;例如,通过将瞬时分桶为年、月和日。

Time Zone Instants at the Epoch

时间分桶规则很复杂,通常分为两组:时区和日历。对于大多数任务,日历将只是广泛使用的公历,但时区应用特定于区域的规则,并且差异很大。例如,以下是'America/Los_Angeles'时区在纪元附近的分桶情况:

Two Time Zones at the Epoch

最常见的时间分桶问题发生在夏令时更改时。下面的示例包含一个夏令时更改,其中“小时”桶长达两小时。为了区分这两个小时,需要另一个包含 UTC 偏移量的桶范围。

Two Time Zones at a Daylight Savings Time transition

时区支持

TIMESTAMPTZ类型可以使用合适的扩展分桶到日历和时钟桶中。内置的ICU 扩展使用国际 Unicode 组件的时区和日历函数实现了所有的分桶和算术函数。

要设置要使用的时区,首先加载 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';
名称 描述 输入类型
时区 欧洲/阿姆斯特丹 当前时区 VARCHAR
SELECT *
FROM duckdb_settings()
WHERE name = 'Calendar';
名称 描述 输入类型
日历 公历 当前日历 VARCHAR

如果您发现分桶操作未按预期进行,请检查TimeZoneCalendar值,并在需要时进行调整。