PostgreSQL 支持完整的一组 SQL 日期和时间类型,如 Table 8.9 所示。这些数据类型上可用的 操作见 Section 9.9。日期按格里高利历 计算,即使是在该历法启用之前的年份也是如此(更多信息见 Section B.6)。
Table 8.9. 日期/时间类型
| 名字 | 存储尺寸 | 描述 | 最小值 | 最大值 | 解析度 |
|---|---|---|---|---|---|
timestamp [ ( |
8字节 | 包括日期和时间(无时区) | 4713 BC | 294276 AD | 1微秒 |
timestamp [ ( |
8字节 | 包括日期和时间,有时区 | 4713 BC | 294276 AD | 1微秒 |
date |
4字节 | 日期(没有一天中的时间) | 4713 BC | 5874897 AD | 1日 |
time [ ( |
8字节 | 一天中的时间(无日期) | 00:00:00 | 24:00:00 | 1微秒 |
time [ ( |
12字节 | 一天中的时间(无日期),带时区 | 00:00:00+1559 | 24:00:00-1559 | 1微秒 |
interval [ |
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 的组合,就足以提供任何应用 所需的完整日期/时间功能。
类型abstime和reltime是内部使用的低精度类型。不推荐在应用中使用这些类型;这些内部类型可能在将来的版本中消失。
日期和时间输入几乎接受任何合理的格式,包括 ISO 8601、 与 SQL 兼容的格式、传统 POSTGRES 格式等。对于某些格式, 日期输入中日、月、年的顺序可能存在歧义,因此支持指定这些字段的 预期顺序。将 DateStyle 参数设置为 MDY,表示采用“月-日-年”解释; 设置为 DMY 表示采用“日-月-年”解释; 而 YMD 表示采用“年-月-日”解释。
PostgreSQL 在处理日期/时间输入方面, 比 SQL 标准要求得更灵活。有关日期/时间输入的 精确解析规则,以及可识别的文本字段(包括月份、星期几和时区), 请参见 Appendix B。
请记住,任何日期或时间字面值输入都必须像文本字符串一样用单引号 括起来。更多信息请参见 Section 4.1.2.7。 SQL 要求使用下列语法:
type[ (p) ] 'value'
其中 p 是可选的精度说明,给出秒字段中 保留的小数位数。精度可用于 time、 timestamp 和 interval 类型, 范围是 0 到 6。这与前文描述的取值一致。如果在常量声明中没有指定 精度,则默认采用该字面值本身的精度(但不超过 6 位)。
Table 8.10显示了date类型可能的输入方式。
Table 8.10. 日期输入
| 示例 | 描述 |
|---|---|
| 1999-01-08 | ISO 8601; 任何模式下的1月8日 (推荐格式) |
| January 8, 1999 | 在任何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-Jan-08 | 任何模式下的1月8日 |
| Jan-08-1999 | 任何模式下的1月8日 |
| 08-Jan-1999 | 任何模式下的1月8日 |
| 99-Jan-08 | YMD模式中的1月8日,否则错误 |
| 08-Jan-99 | 1月8日,除了在YMD模式中错误 |
| Jan-08-99 | 1月8日,除了在YMD模式中错误 |
| 19990108 | ISO 8601; 任何模式中的1999年1月8日 |
| 990108 | ISO 8601; 任何模式中的1999年1月8日 |
| 1999.008 | 年和一年中的日子 |
| J2451187 | 儒略日期 |
| January 8, 99 BC | 公元前99年 |
一天中的时间类型包括 time [ ( 和 p) ] without time zonetime [ (。 单独写 p) ] with time zonetime 等效于 time without time zone。
这些类型的有效输入由一个一天中的时间,加上一个可选时区组成 (见 Table 8.11 和 Table 8.12)。如果在 time without time zone 的输入中指定了时区,它会被 静默忽略。你也可以指定一个日期,但它同样会被忽略,除非你使用了 涉及夏令时规则的时区名称,例如 America/New_York。在这种情况下,必须指定日期, 以便确定应适用标准时间还是夏令时。相应的时区偏移会被记录到 time with time zone 值中,并按存储时的样子输出; 它不会根据当前活动时区进行调整。
Table 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 |
全名指定的时区 |
Table 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中) |
关于如何指定时区,参见 Section 8.5.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 构造(见 Section 9.9.3)。
在 timestamp without time zone 和 timestamp with time zone 之间转换时,通常假定 timestamp without time zone 值应被解释为,或输出为, timezone 本地时间。要为该转换指定不同的时区, 可以使用 AT TIME ZONE。
为了方便起见,PostgreSQL 支持若干特殊的 日期/时间输入值,如 Table 8.13 所示。infinity 和 -infinity 在系统内部有特殊表示,并且输出时会保持不变;其余值则只是记法上的 简写,在读取时会被转换成普通日期/时间值。(特别是, now 及相关字符串在被读取后会立刻转换成某个 特定的时间值。)这些值在 SQL 命令中作为常量使用时,都必须用 单引号括起来。
Table 8.13. 特殊日期/时间输入
| 输入串 | 合法类型 | 描述 |
|---|---|---|
epoch |
date, timestamp |
1970-01-01 00:00:00+00(Unix系统时间0) |
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。(参见 Section 9.9.4。)请注意,这些是 SQL 函数,不会在日期/时间输入字符串中被识别。
虽然输入字符串 now、today、 tomorrow 和 yesterday 可用于交互式 SQL 命令,但当命令被保存以便稍后执行时,例如在预备 语句、视图和函数定义中,它们的行为可能令人意外。这些字符串可能在 读取时就被转换成某个具体时间值,而该值即使后来已经过时,也会继续 被使用。在这类上下文中,应改用某个 SQL 函数。例如, CURRENT_DATE + 1 比 'tomorrow'::date 更安全。
日期/时间类型的输出格式可以设为四种样式之一:ISO 8601、 SQL(Ingres)、传统的 POSTGRES(Unix date 格式)或 German。默认是 ISO 格式。(SQL 标准要求 使用 ISO 8601 格式。之所以存在名为 “SQL” 的输出格式, 只是历史原因。)Table 8.14 展示了各种输出样式的示例。date 和 time 类型的输出通常只包含示例中的日期部分或时间部分。不过, POSTGRES 样式对纯日期值仍会采用 ISO 格式输出。
Table 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 |
German |
地区样式 | 17.12.1997 07:37:16.00 PST |
ISO 8601规定使用大写字母T来分隔日期和时间。 PostgreSQL在输入时接受该格式,但在输出时使用空格而不是T,如上所示。 这是为了可读性和与RFC 3339以及其他一些数据库系统的一致性。
SQL和POSTGRES风格中,如果DMY域顺序被指定,“日”将出现在“月”之前,否则“月”出现在“日”之前(有关该设置如何影响输入值的解释,请参考Section 8.5.1)。Table 8.15给出了示例。
Table 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(见Section 9.8)也可以作为一个更灵活的方式来格式化日期/时间输出。
时区及其约定不仅受地球几何形状影响,也受政治决定影响。世界各地的 时区在 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 视图中(见 Section 52.90)。 PostgreSQL 为此使用广泛采用的 IANA 时区数据,因此同样的时区名称通常也会被其他软件识别。
时区缩写,例如 PST。与完整时区名称不同,这种 指定方式只定义了一个特定的 UTC 偏移,而完整时区名称还可能 隐含一套夏令时切换规则。识别到的缩写列在 pg_timezone_abbrevs 视图中(见 Section 52.89)。你不能把 TimeZone 或 log_timezone 配置参数设置为时区缩写, 但可以在日期/时间输入值中以及与 AT TIME ZONE 操作符一起使用缩写。
除了时区名称和缩写之外, PostgreSQL 还接受 POSIX 风格的 时区说明,见 Section 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/ 子目录中的数据 (见 Section B.4)。
TimeZone 配置参数可以在 postgresql.conf 文件中设置,也可以通过 Chapter 19 中说明的其他标准方式设置。 另外,还有一些特殊的设置方法:
SQL 命令 SET TIME ZONE 用于设置会话的时区。它是 SET TIMEZONE TO 的另一种写法,语法上更符合 SQL 规范。
PGTZ 环境变量会被 libpq 客户端用于在连接到服务器时 发送一条 SET TIME ZONE 命令。
interval值可以使用下列语法书写:
[@]quantityunit[quantityunit...] [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 节的 “替代格式”。带标志符的格式如下:
Pquantityunit[quantityunit...] [ T [quantityunit...]]
字符串必须以 P 开头,并且可以包含一个 T 来引出一天中时间单位。可用的单位缩写见 Table 8.16。单位可以省略, 也可以按任意顺序出现,但小于一天的单位必须出现在 T 之后。特别是,M 的含义 取决于它是在 T 之前还是之后。
Table 8.16. ISO 8601 间隔单位缩写
| 缩写 | 含义 |
|---|---|
| Y | 年 |
| M | 月(在日期部分中) |
| W | 周 |
| D | 日 |
| H | 小时 |
| M | 分钟 (在时间部分中) |
| S | 秒 |
如果使用替代格式:
P [years-months-days] [ Thours: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。输出时,只有秒会显示小数部分。
Table 8.17展示了一些有效interval输入的示例。
Table 8.17. 间隔输入
| 示例 | 描述 |
|---|---|
| 1-2 | SQL标准格式:1年2个月 |
| 3 4:05:06 | SQL标准格式:3日4小时5分钟6秒 |
| 1 year 2 months 3 days 4 hours 5 minutes 6 seconds | 传统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可用于调整超出正常范围的日和小时。
间隔类型的输出格式可以通过 SET intervalstyle 命令设置为以下四种风格之一:sql_standard、 postgres、postgres_verbose 或 iso_8601。默认值是 postgres 格式。 Table 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 节描述的“带标志符格式”。
Table 8.18. 间隔输出风格示例
| 风格声明 | 年-月间隔 | 日-时间间隔 | 混合间隔 |
|---|---|---|---|
sql_standard |
1-2 | 3 4:05:06 | -1-2 +3 -4:05:06 |
postgres |
1 year 2 mons | 3 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_8601 |
P1Y2M | P3DT4H5M6S | P-1Y-2M3DT-4H-5M-6S |