8.5. 日期/时间类型#
PostgreSQL支持SQL日期和时间类型的全套类型,如表 8.9所示。这些数据类型可用的操作在第 9.9 节中进行了描述。日期按照格里高利历进行计算,即使是在该历法引入之前的年份(有关详细信息,请参阅第 B.6 节)。
表 8.9. 日期/时间类型
名称 | 存储大小 | 说明 | 低值 | 高值 | 分辨率 |
---|---|---|---|---|---|
timestamp [ ( | 8 字节 | 日期和时间(无时区) | 公元前 4713 年 | 公元 294276 年 | 1 微秒 |
timestamp [ ( | 8 字节 | 日期和时间,带时区 | 公元前 4713 年 | 公元 294276 年 | 1 微秒 |
date | 4 字节 | 日期(无时间) | 公元前 4713 年 | 公元 5874897 年 | 1 天 |
时间 [ ( | 8 字节 | 时间(无日期) | 00:00:00 | 24:00:00 | 1 微秒 |
时间 [ ( | 12 字节 | 时间(无日期),含时区 | 00:00:00+1559 | 24:00:00-1559 | 1 微秒 |
间隔 [ | 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-08 | ISO 8601;任何模式下的 1 月 8 日(推荐格式) |
1999 年 1 月 8 日 | 在任何 datestyle 输入模式下都不含糊 |
1/8/1999 | 在 MDY 模式下为 1 月 8 日;在 DMY 模式下为 8 月 1 日 |
1/18/1999 | 在 MDY 模式下为 1 月 18 日;在其他模式下被拒绝 |
01/02/03 | 在 MDY 模式下为 2003 年 1 月 2 日;在 DMY 模式下为 2003 年 2 月 1 日;在 YMD 模式下为 2001 年 2 月 3 日 |
1999-01-08 | 在任何模式下均为 1 月 8 日 |
01-08-1999 | 在任何模式下均为 1 月 8 日 |
08-01-1999 | 在任何模式下均为 1 月 8 日 |
99-01-08 | 在 YMD 模式下为 1 月 8 日,否则报错 |
08-01-99 | 1 月 8 日,但 YMD 模式下会报错 |
01-08-99 | 1 月 8 日,但 YMD 模式下会报错 |
19990108 | ISO 8601;在任何模式下均为 1999 年 1 月 8 日 |
990108 | ISO 8601;在任何模式下均为 1999 年 1 月 8 日 |
1999.008 | 年份和一年中的天数 |
J2451187 | 儒略日 |
公元前 99 年 1 月 8 日 | 公元前 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.789 | ISO 8601 |
04:05:06 | ISO 8601 |
04:05 | ISO 8601 |
040506 | ISO 8601 |
04:05 AM | 与 04:05 相同;AM 不影响值 |
04:05 PM | 与 16:05 相同;输入小时数必须 <= 12 |
04:05:06.789-8 | ISO 8601,时区为 UTC 偏移 |
04:05:06-08:00 | ISO 8601,时区为 UTC 偏移 |
04:05-08:00 | ISO 8601,时区为 UTC 偏移 |
040506-08 | ISO 8601,时区为 UTC 偏移 |
040506+0730 | ISO 8601,时区为 UTC 偏移的小时数 |
040506+07:30:00 | UTC 偏移指定为秒(ISO 8601 中不允许) |
04:05:06 PST | 由缩写指定的时区 |
2003-04-12 04:05:06 America/New_York | 由全名指定的时区 |
表 8.12。时区输入
示例 | 说明 |
---|---|
PST | 缩写(太平洋标准时间) |
America/New_York | 完整时区名称 |
PST8PDT | POSIX 风格时区规范 |
-8:00:00 | PST 的 UTC 偏移量 |
-8:00 | PST 的 UTC 偏移量(ISO 8601 扩展格式) |
-800 | PST 的 UTC 偏移量(ISO 8601 基本格式) |
-8 | PST 的 UTC 偏移量(ISO 8601 基本格式) |
zulu | UTC 的军事缩写 |
z | zulu 的简写(也用于 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
是有效值,它们遵循ISO8601 标准。此外,还支持常见格式
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(世界协调时间,传统上称为格林尼治标准时间,GMT)。具有指定显式时区的输入值将使用该时区的适当偏移量转换为 UTC。如果输入字符串中未指定时区,则假定它位于系统TimeZone参数指示的时区中,并使用timezone
时区的偏移量转换为 UTC。
输出timestamp with time zone
值时,它总是从 UTC 转换为当前timezone
时区,并以该时区的本地时间显示。要查看另一个时区的时间,请更改timezone
或使用AT TIME ZONE
结构(请参阅第 9.9.4 节)。
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. 特殊日期/时间输入
输入字符串 | 有效类型 | 说明 |
---|---|---|
epoch | date 、timestamp | 1970-01-01 00:00:00+00(Unix 系统时间零) |
infinity | date 、timestamp | 晚于所有其他时间戳 |
-infinity | date 、timestamp | 早于所有其他时间戳 |
now | date 、time 、timestamp | 当前事务的开始时间 |
today | date 、timestamp | 今天的午夜(00:00 ) |
tomorrow | date 、timestamp | 明天的午夜(00:00 ) |
yesterday | date 、timestamp | 昨天的午夜(00:00 ) |
allballs | time | 00:00:00.00 UTC |
以下SQL兼容函数也可用于获取相应数据类型的当前时间值:CURRENT_DATE
、CURRENT_TIME
、CURRENT_TIMESTAMP
、LOCALTIME
、LOCALTIMESTAMP
。(请参阅第 9.9.5 节。)请注意,这些是 SQL 函数,在数据输入字符串中不识别。
警告
虽然输入字符串now
、today
、tomorrow
和yesterday
在交互式 SQL 命令中使用时很好,但在将命令保存为稍后执行时,例如在预处理语句、视图和函数定义中,它们可能会产生令人惊讶的行为。该字符串可以转换为特定时间值,并且在它变得陈旧后很长时间内继续使用。在这种情况下,请改用其中一个 SQL 函数。例如,CURRENT_DATE + 1
比'tomorrow'::date
更安全。
8.5.2. 日期/时间输出#
日期/时间类型的输出格式可以设置为四种样式之一:ISO 8601、SQL(Ingres)、传统POSTGRES(Unixdate格式)或德语。默认格式为ISO格式。(SQL标准要求使用 ISO 8601 格式。““SQL”” 输出格式的名称是一个历史意外。)表 8.14显示了每种输出样式的示例。date
和time
类型的输出通常仅为日期或时间部分,符合给定的示例。但是,POSTGRES样式以ISO格式输出仅日期值。
表 8.14. 日期/时间输出样式
样式规范 | 说明 | 示例 |
---|---|---|
ISO | ISO 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 |
德语 | 区域样式 | 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 | day /month /year | 17/12/1997 15:37:16.00 CET |
SQL,MDY | month /day /year | 12/17/1997 07:37:16.00 PST |
Postgres,DMY | day /month /year | 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
类型可以。现实世界中的时区如果没有与日期和时间相关联,几乎没有意义,因为偏移量可能会随着夏令时边界而全年变化。默认时区指定为从 的恒定数字偏移量。因此,在跨 边界进行日期/时间算术时,不可能适应夏令时。
为了解决这些困难,我们建议在使用时区时使用同时包含日期和时间的日期/时间类型。我们不建议使用类型time with time zone
(尽管PostgreSQL为旧版应用程序和符合SQL标准而支持它)。PostgreSQL假设您仅包含日期或时间的任何类型的本地时区。
所有与时区相关联的日期和时间都以UTC内部存储。在显示给客户端之前,它们会根据TimeZone配置参数指定的区域转换为本地时间。
PostgreSQL允许您以三种不同的形式指定时区
一个完整的时区名称,例如
America/New_York
。已识别的时区名称列在pg_timezone_names
视图中(参见 第 54.32 节)。PostgreSQL 为此使用广泛使用的 IANA 时区数据,因此其他软件也识别相同的时区名称。时区缩写,例如
PST
。这种规范仅定义了与 UTC 的特定偏移量,而完整的时区名称还可以暗示一组夏令时转换规则。已识别的缩写列在pg_timezone_abbrevs
视图中(参见 第 54.31 节)。您无法将配置参数 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
示例一样,这不一定与该日期的当地民用时间相同。
在所有情况下,时区名称和缩写都以不区分大小写的方式识别。(这是对 8.2 之前的PostgreSQL版本的更改,在某些情况下区分大小写,而在其他情况下不区分大小写。)
时区名称和缩写都没有硬编码到服务器中;它们是从安装目录的.../share/timezone/
和.../share/timezonesets/
下存储的配置文件中获取的(请参见第 B.4 节)。
可以在文件postgresql.conf
中设置TimeZone配置参数,也可以在第 20 章中描述的任何其他标准方式中设置。还有一些特殊的方法可以设置它
命令
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解释。为避免歧义,建议为每个字段附加显式符号(如果任何字段为负数)。
字段值可以有小数部分:例如,'1.5 weeks'
或'01:02:03.45'
。但是,由于间隔在内部仅存储三个整数单位(月、天、微秒),因此必须将分数单位溢出到较小的单位。大于月份的单位的分数部分将被四舍五入为月份的整数,例如'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-2 | SQL 标准格式:1 年 2 个月 |
3 4:05:06 | SQL 标准格式:3 天 4 小时 5 分钟 6 秒 |
1 年 2 个月 3 天 4 小时 5 分钟 6 秒 | 传统 Postgres 格式:1 年 2 个月 3 天 4 小时 5 分钟 6 秒 |
P1Y2M3DT4H5M6S | ISO 8601 “带设计器的格式”:与上述含义相同 |
P0001-02-03T04:05:06 | ISO 8601 “备用格式”:与上述含义相同 |
在内部,interval
值存储为月、日和微秒。这样做是因为一个月中的天数是可变的,如果涉及夏令时调整,一天可能有 23 或 25 小时。月和日字段为整数,而微秒字段可以存储小数秒。由于间隔通常从常量字符串或timestamp
减法创建,因此这种存储方法在大多数情况下都能正常工作,但可能会导致意外结果
SELECT EXTRACT(hours from '80 minutes'::interval);
date_part
-----------
1
SELECT EXTRACT(days from '80 hours'::interval);
date_part
-----------
0
函数justify_days
和justify_hours
可用于调整超出其正常范围的天数和小时数。
8.5.5. 间隔输出#
间隔类型的输出格式可以设置为四种样式之一sql_standard
、postgres
、postgres_verbose
或iso_8601
,使用命令SET intervalstyle
。默认值为postgres
格式。表 8.18显示了每种输出样式的示例。
如果间隔值符合标准的限制(仅年-月或仅日-时,没有正负分量的混合),sql_standard
样式会生成符合 SQL 标准间隔文字字符串规范的输出。否则,输出看起来像一个标准的年-月文字字符串,后跟一个日-时文字字符串,并添加显式符号以消除符号混合间隔的歧义。
当DateStyle参数设置为ISO
时,postgres
样式的输出与 8.4 之前的PostgreSQL版本的输出相匹配。
当DateStyle
参数设置为非ISO
输出时,postgres_verbose
样式的输出与 8.4 之前的PostgreSQL版本的输出相匹配。
iso_8601
样式的输出与 ISO 8601 标准第 4.4.3.2 节中描述的““带设计器的格式””相匹配。
表 8.18. 间隔输出样式示例
样式规范 | 年-月间隔 | 日-时间间隔 | 混合间隔 |
---|---|---|---|
sql_standard | 1-2 | 3 4:05:06 | -1-2 +3 -4:05:06 |
postgres | 1 年 2 个月 | 3 天 04:05:06 | -1 年 -2 个月 +3 天 -04:05:06 |
postgres_verbose | @ 1 年 2 个月 | @ 3 天 4 小时 5 分钟 6 秒 | @ 1 年 2 个月 -3 天 4 小时 5 分钟 6 秒前 |
iso_8601 | P1Y2M | P3DT4H5M6S | P-1Y-2M3DT-4H-5M-6S |