- 安装
- 文档
- 入门
- 连接
- 数据导入
- 客户端 API
- 概览
- ADBC
- C
- C++
- CLI
- Dart
- Go
- Java (JDBC)
- Julia
- Node.js (已弃用)
- Node.js (Neo)
- ODBC
- PHP
- Python
- R
- Rust
- Swift
- Wasm
- SQL
- 介绍
- 语句
- 概览
- 分析
- 修改表
- 修改视图
- ATTACH 和 DETACH
- 调用
- 检查点
- 评论
- 复制
- 创建索引
- 创建宏
- 创建模式
- 创建密钥
- 创建序列
- 创建表
- 创建视图
- 创建类型
- 删除
- 描述
- 删除
- EXPORT 和 IMPORT DATABASE
- 插入
- 加载 / 安装
- 透视
- 性能分析
- 选择
- 设置 / 重置
- 设置变量
- 汇总
- 事务管理
- 反透视
- 更新
- 使用
- 清理
- 查询语法
- 数据类型
- 表达式
- 函数
- 概览
- 聚合函数
- 数组函数
- 位字符串函数
- Blob 函数
- 日期格式化函数
- 日期函数
- 日期部分函数
- 枚举函数
- 间隔函数
- Lambda 函数
- 列表函数
- 映射函数
- 嵌套函数
- 数值函数
- 模式匹配
- 正则表达式
- 结构体函数
- 文本函数
- 时间函数
- 时间戳函数
- 带时区时间戳函数
- 联合函数
- 实用函数
- 窗口函数
- 约束
- 索引
- 元查询
- DuckDB 的 SQL 方言
- 示例
- 配置
- 扩展
- 核心扩展
- 概览
- 自动补全
- Avro
- AWS
- Azure
- Delta
- DuckLake
- 编码
- Excel
- 全文搜索
- httpfs (HTTP 和 S3)
- Iceberg
- ICU
- inet
- jemalloc
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI
- VSS
- 指南
- 概览
- 数据查看器
- 数据库集成
- 文件格式
- 概览
- CSV 导入
- CSV 导出
- 直接读取文件
- Excel 导入
- Excel 导出
- JSON 导入
- JSON 导出
- Parquet 导入
- Parquet 导出
- 查询 Parquet 文件
- 使用 file: 协议访问文件
- 网络和云存储
- 概览
- HTTP Parquet 导入
- S3 Parquet 导入
- S3 Parquet 导出
- S3 Iceberg 导入
- S3 Express One
- GCS 导入
- Cloudflare R2 导入
- 通过 HTTPS / S3 使用 DuckDB
- Fastly 对象存储导入
- 元查询
- ODBC
- 性能
- Python
- 安装
- 执行 SQL
- Jupyter Notebooks
- marimo Notebooks
- Pandas 上的 SQL
- 从 Pandas 导入
- 导出到 Pandas
- 从 Numpy 导入
- 导出到 Numpy
- Arrow 上的 SQL
- 从 Arrow 导入
- 导出到 Arrow
- Pandas 上的关系型 API
- 多个 Python 线程
- 与 Ibis 集成
- 与 Polars 集成
- 使用 fsspec 文件系统
- SQL 编辑器
- SQL 功能
- 代码片段
- 故障排除
- 术语表
- 离线浏览
- 操作手册
- 开发
- 内部结构
- 为什么选择 DuckDB
- 行为准则
- 发布日历
- 路线图
- 站点地图
- 在线演示
时间戳表示时间点。因此,它们结合了DATE
和TIME
信息。它们可以使用类型名称后跟符合 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 ZONE
和WITH 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 ZONE
和WITHOUT 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
,增量以秒、毫秒、微秒或纳秒为单位,具体取决于特定的数据类型。
时间分桶
分桶是连续数据的一种常见做法:将一系列可能的值分解为连续的子集,并且分桶操作将实际值映射到它们所属的“桶”。时间分桶仅仅是将这种做法应用于瞬时;例如,通过将瞬时分桶为年、月和日。
时间分桶规则很复杂,通常分为两组:时区和日历。对于大多数任务,日历将只是广泛使用的公历,但时区应用特定于区域的规则,并且差异很大。例如,以下是'America/Los_Angeles'
时区在纪元附近的分桶情况:
最常见的时间分桶问题发生在夏令时更改时。下面的示例包含一个夏令时更改,其中“小时”桶长达两小时。为了区分这两个小时,需要另一个包含 UTC 偏移量的桶范围。
时区支持
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 扩展时,才需要执行INSTALL
和LOAD
步骤。
INSTALL icu;
LOAD icu;
SET Calendar = 'japanese';
然后,TIMESTAMPTZ
的时间分桶操作将使用给定的日历来实现。在此示例中,era
部分现在将报告日本的帝国纪元编号。
可用日历列表可以从icu_calendar_names()
表函数中获取。
SELECT name
FROM icu_calendar_names()
ORDER BY 1;
设置
TimeZone
和Calendar
设置的当前值由 ICU 启动时确定。它们可以从duckdb_settings()
表函数中查询。
SELECT *
FROM duckdb_settings()
WHERE name = 'TimeZone';
名称 | 值 | 描述 | 输入类型 |
---|---|---|---|
时区 | 欧洲/阿姆斯特丹 | 当前时区 | VARCHAR |
SELECT *
FROM duckdb_settings()
WHERE name = 'Calendar';
名称 | 值 | 描述 | 输入类型 |
---|---|---|---|
日历 | 公历 | 当前日历 | VARCHAR |
如果您发现分桶操作未按预期进行,请检查
TimeZone
和Calendar
值,并在需要时进行调整。