↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1
历史版本。 PostgreSQL 13 已结束支持。 2025-11-13. 请参阅 当前版本手册.

8.5. 日期/时间类型 #

PostgreSQL 支持完整的一组 SQL 日期和时间类型,如表 8.9 所示。这些数据类型上可用的操作见第 9.9 节。日期按格里高利历计算,即使是在该历法启用之前的年份也是如此(更多信息见第 B.6 节)。

表 8.9. 日期/时间类型

名字存储尺寸描述最小值最大值解析度
timestamp [ (p) ] [ without time zone ]8 字节包括日期和时间(无时区)4713 BC294276 AD1 微秒
timestamp [ (p) ] with time zone8 字节包括日期和时间,有时区4713 BC294276 AD1 微秒
date4 字节日期(没有一天中的时间)4713 BC5874897 AD1 日
time [ (p) ] [ without time zone ]8 字节一天中的时间(无日期)00:00:0024:00:001 微秒
time [ (p) ] with time zone12 字节一天中的时间(无日期),带时区00:00:00+155924:00:00-15591 微秒
interval [ fields ] [ (p) ]16 字节时间间隔-178000000 年178000000 年1 微秒

注意

SQL 标准要求仅写 timestamp 时,应等效于 timestamp without time zone,而 PostgreSQL 也遵循这种行为。timestamptz 被接受为 timestamp with time zone 的简写,这是 PostgreSQL 的扩展。

time、timestamp 和 interval 都接受一个可选精度值 p,用来指定在秒字段中保留多少位小数。默认情况下,对精度没有显式上界。p 的允许范围是 0 到 6。

interval 类型还有一个附加选项,可以通过写出下面这些短语之一来限制所存储字段的集合:

YEAR
MONTH
DAY
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND

注意,如果同时指定了 fields 和 p,那么 fields 必须包含 SECOND,因为精度只作用于秒。

time with time zone 类型由 SQL 标准定义,但其定义具有一些会让人怀疑其实用性的特性。在大多数情况下,date、time、timestamp without time zone 和 timestamp with time zone 的组合,就足以提供任何应用所需的完整日期/时间功能。

8.5.1. 日期/时间输入 #

日期和时间输入几乎接受任何合理的格式,包括 ISO 8601、与 SQL 兼容的格式、传统 POSTGRES 格式等。对于某些格式,日期输入中日、月、年的顺序可能存在歧义,因此支持指定这些字段的预期顺序。将 DateStyle 参数设置为 MDY,表示采用“月-日-年”解释;设置为 DMY 表示采用“日-月-年”解释;而 YMD 表示采用“年-月-日”解释。

PostgreSQL 在处理日期/时间输入方面,比 SQL 标准要求的更灵活。有关日期/时间输入的精确解析规则,以及可识别的文本字段(包括月份、星期几和时区),请参见附录 B。

请记住,任何日期或时间字面值输入都必须像文本字符串一样用单引号括起来。更多信息请参见第 4.1.2.7 节。SQL 要求使用下列语法:

type [ (p) ] 'value'

其中 p 是可选的精度说明,给出秒字段中保留的小数位数。精度可用于 time、timestamp 和 interval 类型,范围是 0 到 6。如果在常量声明中没有指定精度,则默认采用该字面值本身的精度(但不超过 6 位)。

8.5.1.1. 日期 #

表 8.10 显示了 date 类型可能的输入方式。

表 8.10. 日期输入

示例描述
1999-01-08ISO 8601;任何模式下的 1 月 8 日(推荐格式)
January 8, 1999在任何 datestyle 输入模式下都无歧义
1/8/1999MDY 模式中的 1 月 8 日;DMY 模式中的 8 月 1 日
1/18/1999MDY 模式中的 1 月 18 日;在其他模式中被拒绝
01/02/03MDY 模式中的 2003 年 1 月 2 日;DMY 模式中的 2003 年 2 月 1 日;YMD 模式中的 2001 年 2 月 3 日
1999-Jan-08任何模式下的 1 月 8 日
Jan-08-1999任何模式下的 1 月 8 日
08-Jan-1999任何模式下的 1 月 8 日
99-Jan-08YMD 模式中的 1 月 8 日,否则报错
08-Jan-991 月 8 日,但在 YMD 模式中会报错
Jan-08-991 月 8 日,但在 YMD 模式中会报错
19990108ISO 8601;任何模式中的 1999 年 1 月 8 日
990108ISO 8601;任何模式中的 1999 年 1 月 8 日
1999.008年和一年中的第几天
J2451187儒略日
January 8, 99 BC公元前 99 年

8.5.1.2. 时间 #

一天中的时间类型包括 time [ (p) ] without time zone 和 time [ (p) ] with time zone。单独写 time 等效于 time without time zone。

这些类型的有效输入由一个一天中的时间,加上一个可选时区组成(见表 8.11 和表 8.12)。如果在 time without time zone 的输入中指定了时区,它会被静默忽略。你也可以指定一个日期,但它同样会被忽略,除非你使用了涉及夏令时规则的时区名称,例如 America/New_York。在这种情况下,必须指定日期,以便确定应适用标准时间还是夏令时。相应的时区偏移会被记录到 time with time zone 值中,并按存储时的样子输出;它不会根据当前活动时区进行调整。

表 8.11. 时间输入

示例描述
04:05:06.789ISO 8601
04:05:06ISO 8601
04:05ISO 8601
040506ISO 8601
04:05 AM和 04:05 一样,AM 并不影响值
04:05 PM和 16:05 一样,输入的小时必须为 <= 12
04:05:06.789-8ISO 8601,时区以 UTC 偏移表示
04:05:06-08:00ISO 8601,时区以 UTC 偏移表示
04:05-08:00ISO 8601,时区以 UTC 偏移表示
040506-08ISO 8601,时区以 UTC 偏移表示
040506+0730ISO 8601,时区的 UTC 偏移不是整小时
040506+07:30:00UTC 偏移精确到秒(ISO 8601 中不允许)
04:05:06 PST缩写指定的时区
2003-04-12 04:05:06 America/New_York全名指定的时区

表 8.12. 时区输入

示例描述
PST缩写(太平洋标准时间)
America/New_York完整时区名
PST8PDTPOSIX 风格的时区声明
-8:00:00PST 的 UTC 偏移
-8:00PST 的 UTC 偏移量(ISO 8601 扩展格式)
-800PST 的 UTC 偏移量(ISO 8601 基本格式)
-8PST 的 UTC 偏移量(ISO 8601 基本格式)
zuluUTC 的军方缩写
zzulu 的缩写形式(也在 ISO 8601 中)

关于如何指定时区,参见第 8.5.3 节。

8.5.1.3. 时间戳 #

时间戳类型的有效输入由一个日期和时间的串接组成,后面跟着一个可选时区,以及一个可选的 AD 或 BC(另外,AD/BC 也可以出现在时区前面,但这种顺序并不推荐)。因此:

1999-01-08 04:05:06

和:

1999-01-08 04:05:06 -8:00

都是遵循 ISO 8601 标准的有效值。另外,广泛使用的下列格式:

January 8 04:05:06 1999 PST

也被支持。

按照 SQL 标准,timestamp without time zone 和 timestamp with time zone 字面量的区别在于,时间后是否有“+”或“-”符号及其后的时区偏移。因此,按照该标准,

TIMESTAMP '2004-10-19 10:23:54'

是 timestamp without time zone,而

TIMESTAMP '2004-10-19 10:23:54+02'

是 timestamp with time zone。PostgreSQL 在确定字符串字面量的类型之前,从不检查其内容,因此会把上述两者都视为 timestamp without time zone。为确保字面量被视为 timestamp with time zone,应为它显式指定正确类型:

TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'

对于已经确定为 timestamp without time zone 的值,PostgreSQL 会静默忽略任何时区指示。也就是说,结果值直接取自输入字符串中的日期/时间字段,而不会因时区而调整。

对于 timestamp with time zone 值,如果输入字符串中包含显式时区,则会使用该时区对应的偏移将其转换为协调世界时(UTC)。如果输入字符串中没有给出时区,则假定它位于系统 TimeZone 参数指定的时区,并使用 timezone 时区的偏移将其转换为 UTC。无论哪种情况,该值在内部都以 UTC 存储,原先声明或假定的时区都不会被保留。

当输出一个 timestamp with time zone 值时,它总会从 UTC 转换到当前 timezone 时区,并显示为该时区的本地时间。若要查看其他时区的时间,可以修改 timezone,或者使用 AT TIME ZONE 构造(见第 9.9.3 节)。

在 timestamp without time zone 和 timestamp with time zone 之间转换时,通常假定 timestamp without time zone 值应被解释为,或输出为,timezone 本地时间。要为该转换指定不同的时区,可以使用 AT TIME ZONE。

8.5.1.4. 特殊值 #

为了方便起见,PostgreSQL 支持若干特殊的日期/时间输入值,如表 8.13 所示。infinity 和 -infinity 在系统内部有特殊表示,并且输出时会保持不变;其余值则只是记法上的简写,在读取时会被转换成普通日期/时间值。(特别是,now 及相关字符串在被读取后会立刻转换成某个特定的时间值。)这些值在 SQL 命令中作为常量使用时,都必须用单引号括起来。

表 8.13. 特殊日期/时间输入

输入串合法类型描述
epochdate, timestamp1970-01-01 00:00:00+00(Unix 系统时间 0)
infinitydate, timestamp晚于所有其他时间戳
-infinitydate, timestamp早于所有其他时间戳
nowdate, time, timestamp当前事务的开始时间
todaydate, timestamp今天午夜(00:00)
tomorrowdate, timestamp明天午夜(00:00)
yesterdaydate, timestamp昨天午夜(00:00)
allballstime00:00:00.00 UTC

以下与 SQL 兼容的函数也可用于获取相应数据类型的当前时间值:CURRENT_DATE、CURRENT_TIME、CURRENT_TIMESTAMP、LOCALTIME 和 LOCALTIMESTAMP。(参见第 9.9.4 节。)请注意,这些是 SQL 函数,不会在日期/时间输入字符串中被识别。

小心

虽然输入字符串 now、today、tomorrow 和 yesterday 可用于交互式 SQL 命令,但当命令被保存以便稍后执行时,例如在预备语句、视图和函数定义中,它们的行为可能令人意外。这些字符串可能在读取时就被转换成某个具体时间值,而该值即使后来已经过时,也会继续被使用。在这类上下文中,应改用某个 SQL 函数。例如,CURRENT_DATE + 1 比 'tomorrow'::date 更安全。

8.5.2. 日期/时间输出 #

日期/时间类型的输出格式可以设为四种样式之一:ISO 8601、SQL(Ingres)、传统的 POSTGRES(Unix date 格式)或 German。默认是 ISO 格式。(SQL 标准要求使用 ISO 8601 格式。之所以存在名为“SQL”的输出格式,只是历史原因。)表 8.14 展示了各种输出样式的示例。date 和 time 类型的输出通常只包含示例中的日期部分或时间部分。不过,POSTGRES 样式对纯日期值仍会采用 ISO 格式输出。

表 8.14. 日期/时间输出风格

风格声明描述示例
ISOISO 8601,SQL 标准1997-12-17 07:37:16-08
SQL传统样式12/17/1997 07:37:16.00 PST
Postgres原始样式Wed Dec 17 07:37:16 1997 PST
German地区样式17.12.1997 07:37:16.00 PST

注意

ISO 8601 规定使用大写字母 T 来分隔日期和时间。PostgreSQL 在输入时接受该格式,但在输出时使用空格而不是 T,如上所示。这是为了可读性和与 RFC 3339 以及其他一些数据库系统的一致性。

SQL 和 POSTGRES 风格中,如果指定了 DMY 字段顺序,“日”将出现在“月”之前,否则“月”出现在“日”之前(有关该设置如何影响输入值的解释,请参考第 8.5.1 节)。表 8.15 给出了示例。

表 8.15. 日期顺序习惯

datestyle 设置输入顺序示例输出
SQL, DMY日/月/年17/12/1997 15:37:16.00 CET
SQL, MDY月/日/年12/17/1997 07:37:16.00 PST
Postgres, DMY日/月/年Wed 17 Dec 07:37:16 1997 PST

在 ISO 样式中,时区总是显示为相对于 UTC 的有符号数字偏移,格林尼治以东的时区使用正号。如果偏移是整小时,就显示为 hh;如果是整分钟,则显示为 hh:mm;否则显示为 hh:mm:ss。(第三种情况在任何现代时区标准下都不可能出现,但在处理标准化时区被采用之前的时间戳时可能会看到。)在其他日期样式中,如果当前时区有通用的字母缩写,就会显示该缩写;否则会以 ISO 8601 基本格式的有符号数字偏移显示(hh 或 hhmm)。

用户可以通过 SET datestyle 命令、postgresql.conf 配置文件中的 DateStyle 参数,或者服务器端或客户端上的 PGDATESTYLE 环境变量来选择日期/时间样式。

格式化函数 to_char(见第 9.8 节)也可以作为一个更灵活的方式来格式化日期/时间输出。

8.5.3. 时区 #

时区及其约定不仅受地球几何形状影响,也受政治决定影响。世界各地的时区在 20 世纪逐渐趋于标准化,但仍然容易发生任意变化,尤其是夏令时规则方面。PostgreSQL 使用广泛采用的 IANA(Olson)时区数据库来获取历史时区规则信息。对于未来时间,则假定某个时区最新已知的规则会无限期地持续下去。

PostgreSQL 努力在典型用法上与 SQL 标准定义保持兼容。不过,SQL 标准在日期和时间类型及其能力方面存在一些奇怪的混搭。两个显而易见的问题是:

  • 尽管 date 类型不能有关联的时区,time 类型却可以。但现实世界中的时区如果不同时关联日期和时间,几乎没有意义,因为偏移量可能会随着夏令时切换而在一年中发生变化。

  • 默认时区被指定为相对于 UTC 的一个固定数值偏移。因此,在跨越 DST 边界做日期/时间算术时,根本无法适应夏令时变化。

为了克服这些困难,我们建议在使用时区时采用同时包含日期和时间的日期/时间类型。我们不建议使用 time with time zone 类型(尽管 PostgreSQL 出于兼容旧应用以及遵循 SQL 标准的考虑而支持它)。PostgreSQL 对于任何只包含日期或时间的类型,都会假定其使用本地时区。

在系统内部,所有带时区的日期和时间都以 UTC 存储。显示给客户端之前,它们会被转换为由 TimeZone 配置参数指定的本地时间。

PostgreSQL 允许使用三种不同形式来指定时区:

  • 完整时区名称,例如 America/New_York。识别到的时区名称列在 pg_timezone_names 视图中(见第 51.92 节)。PostgreSQL 为此使用广泛采用的 IANA 时区数据,因此同样的时区名称通常也会被其他软件识别。

  • 时区缩写,例如 PST。与完整时区名称不同,这种指定方式只定义了一个特定的 UTC 偏移,而完整时区名称还可能隐含一套夏令时切换规则。识别到的缩写列在 pg_timezone_abbrevs 视图中(见第 51.91 节)。你不能把 TimeZone 或 log_timezone 配置参数设置为时区缩写,但可以在日期/时间输入值中以及与 AT TIME ZONE 操作符一起使用缩写。

  • 除了时区名称和缩写之外,PostgreSQL 还接受 POSIX 风格的时区说明,见第 B.5 节。这个选项通常不如使用具名时区更合适,但如果没有合适的 IANA 时区条目可用,它可能就是必需的。

简而言之,这就是缩写和全名的区别:缩写表示某个特定的 UTC 偏移,而很多完整名称隐含了当地的夏令时规则,因此可能对应两个不同的 UTC 偏移。例如,2014-06-04 12:00 America/New_York 表示纽约当地时间中午;在这个具体日期,它属于东部夏令时(UTC-4),所以 2014-06-04 12:00 EDT 指定的是同一个时刻。但 2014-06-04 12:00 EST 指定的是东部标准时中午(UTC-5),无论当天名义上是否正在实行夏令时。

更复杂的是,一些司法辖区在不同时间使用同一时区缩写来表示不同的 UTC 偏移;例如在莫斯科,MSK 在某些年份表示 UTC+3,在另一些年份则表示 UTC+4。PostgreSQL 会按照该缩写在所给日期上的含义(或最近一次的含义)来解释这类缩写;但与上面的 EST 例子一样,这并不一定等同于该日期的当地民用时间。

无论哪种形式,时区名称及其缩写都不区分大小写。(这是对 PostgreSQL 8.2 之前版本的一项改动;在那些版本中,时区名在某些环境下区分大小写,而在另一些环境下则不区分。)

时区名称和缩写并不是硬编码在服务器中的;它们来自安装目录下 .../share/timezone/ 和 .../share/timezonesets/ 子目录中的配置文件(见第 B.4 节)。

TimeZone 配置参数可以在 postgresql.conf 文件中设置,也可以通过第 19 章中说明的其他标准方式设置。另外,还有一些特殊的设置方法:

  • SQL 命令 SET TIME ZONE 用于设置会话的时区。它是 SET TIMEZONE TO 的另一种写法,语法上更符合 SQL 规范。

  • PGTZ 环境变量会被 libpq 客户端用于在连接到服务器时发送一条 SET TIME ZONE 命令。

8.5.4. 间隔输入 #

interval 值可以使用下列详细语法书写:

[@] quantity unit [quantity unit...] [direction]

其中 quantity 是一个数字(可以带有符号);unit 是 microsecond、millisecond、second、minute、hour、day、week、month、year、decade、century、millennium 或它们的缩写或复数形式;direction 可以是 ago 或为空。at 符号(@)只是可选的噪声。不同单位的数量会按适当的符号规则隐式相加。ago 会将所有字段取反。如果 IntervalStyle 被设置为 postgres_verbose,该语法也会用于间隔输出。

日、小时、分钟和秒的数量也可以不写显式单位标记。例如,'1 12:59:10' 会被读作 '1 day 12 hours 59 min 10 sec'。同样,年和月的组合也可以用一个连字符表示,例如 '200-10' 会被读作 '200 years 10 months'。(事实上,这些较短形式正是 SQL 标准唯一允许的形式,并且在 IntervalStyle 被设置为 sql_standard 时也用于输出。)

间隔值也可以写成 ISO 8601 时间间隔,使用标准第 4.4.3.2 节的“带标志符的格式”,或第 4.4.3.3 节的“替代格式”。带标志符的格式如下:

P quantity unit [ quantity unit ...] [ T [ quantity unit ...]]

字符串必须以 P 开头,并且可以包含一个 T 来引出一天中的时间单位。可用的单位缩写见表 8.16。单位可以省略,也可以按任意顺序出现,但小于一天的单位必须出现在 T 之后。特别是,M 的含义取决于它是在 T 之前还是之后。

表 8.16. ISO 8601 间隔单位缩写

缩写含义
Y年
M月(在日期部分中)
W周
D日
H小时
M分钟(在时间部分中)
S秒

如果使用替代格式:

P [ years-months-days ] [ T hours:minutes:seconds ]

串必须以 P 开始,并且一个 T 分隔间隔的日期和时间部分。其值按照类似于 ISO 8601 日期的数字给出。

当编写带有 fields 说明的间隔常量,或者把字符串赋给一个定义时带有 fields 说明的间隔列时,未标记数量的解释方式取决于 fields。例如,INTERVAL '1' YEAR 会被读作 1 年,而 INTERVAL '1' 表示 1 秒。此外,位于 fields 说明所允许的最小字段“右侧”的字段值会被静默丢弃。例如,写 INTERVAL '1 day 2:03:04' HOUR TO MINUTE 会导致秒字段被丢弃,而不是日字段。

根据 SQL 标准,一个间隔值的所有字段都必须带有相同符号,因此一个前导负号会作用于所有字段;例如间隔字面值 '-1 2:03:04' 中的负号会同时作用于日、小时、分钟和秒。PostgreSQL 允许各字段具有不同符号,并且传统上认为文本表示中的每个字段都带有各自独立的符号,因而在这个例子里,小时、分钟和秒部分会被视为正值。如果 IntervalStyle 被设置为 sql_standard,则会把前导符号视为作用于所有字段(但前提是没有出现额外符号);否则将采用传统的 PostgreSQL 解释。为了避免混淆,我们建议只要有任何字段为负值,就为每个字段都显式写出符号。

在内部,interval 值以三个整数字段存储:月、日和微秒。之所以将它们分开保存,是因为一个月中的天数会变化,而如果涉及夏令时切换,一天也可能只有 23 或 25 小时。使用其他单位的间隔输入字符串会先规范化为这种格式,然后再以标准化方式重建输出,例如:

SELECT '2 years 15 months 100 weeks 99 hours 123456789 milliseconds'::interval;
               interval
---------------------------------------
 3 years 3 mons 700 days 133:17:36.789

这里,周按“7 天”计算并单独保留,而较小和较大的时间单位则被合并并规范化了。

输入字段值可以带有小数部分,例如 '1.5 weeks' 或 '01:02:03.45'。然而,由于 interval 在内部只存储整数字段,小数值必须转换成更小的单位。大于月的单位的小数部分会被截断为整数个月,例如 '1.5 years' 会变成 '1 year 6 mons'。周和天的小数部分会在假定每月 30 天、每天 24 小时的前提下,计算成整数天和微秒,例如 '1.75 months' 会变成 1 mon 22 days 12:00:00。只有秒在输出时才可能显示为小数。

表 8.17 展示了一些有效 interval 输入的示例。

表 8.17. 间隔输入

示例描述
1-2SQL 标准格式:1 年 2 个月
3 4:05:06SQL 标准格式:3 日 4 小时 5 分钟 6 秒
1 year 2 months 3 days 4 hours 5 minutes 6 seconds传统 Postgres 格式:1 年 2 个月 3 日 4 小时 5 分钟 6 秒钟
P1Y2M3DT4H5M6SISO 8601 “带标志符的格式”:含义同上
P0001-02-03T04:05:06ISO 8601 的“替代格式”:含义同上

8.5.5. 间隔输出 #

如前所述,PostgreSQL 将 interval 值存储为月、日和微秒。输出时,月字段通过除以 12 转换为年和月;日字段按原样显示;微秒字段则转换为小时、分钟、秒和小数秒。因此,月、分钟和秒的显示值永远不会超出 0–11、0–59 和 0–59 的范围,而显示出来的年、日和小时字段则可能相当大。(如果希望把较大的日数或小时数进位到更高一级字段,可以使用 justify_days 和 justify_hours 函数。)

间隔类型的输出格式可以通过 SET intervalstyle 命令设置为以下四种风格之一:sql_standard、postgres、postgres_verbose 或 iso_8601。默认值是 postgres 格式。表 8.18 展示了每种输出风格的示例。

如果间隔值满足 SQL 标准的限制条件(仅有年-月或仅有日-时间,且不混合正负分量),sql_standard 风格会生成符合 SQL 标准的间隔字面值输出。否则,输出将表现为一个标准的年-月字面值串后跟一个日-时间字面值串,并显式加上符号,以消除正负混合间隔的歧义。

postgres 样式的输出与 PostgreSQL 8.4 之前版本在 DateStyle 参数设为 ISO 时的输出一致。

postgres_verbose 样式的输出与 PostgreSQL 8.4 之前版本在 DateStyle 参数设为非 ISO 输出时的结果一致。

iso_8601 风格的输出符合 ISO 8601 标准 4.4.3.2 节描述的“带标志符的格式”。

表 8.18. 间隔输出风格示例

风格声明年-月间隔日-时间间隔混合间隔
sql_standard1-23 4:05:06-1-2 +3 -4:05:06
postgres1 year 2 mons3 days 04:05:06-1 year -2 mons +3 days -04:05:06
postgres_verbose@ 1 year 2 mons@ 3 days 4 hours 5 mins 6 secs@ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago
iso_8601P1Y2MP3DT4H5M6SP-1Y-2M3D​T-4H-5M-6S

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.