PostgreSQL支持SQL中所有的日期和时间类型,如表 8.9所示。这些数据类型上可用的操作如第 9.9 节所述。日期根据公历来计算,即使对于该历法被引入之前的年份也一样(见第 B.6 节获取更多信息)。
表 8.9. 日期/时间类型
| 名字 | 存储大小 | 描述 | 最小值 | 最大值 | 分辨率 |
|---|---|---|---|---|---|
timestamp [ ( | 8 bytes | 包括日期和时间(无时区) | 4713 BC | 294276 AD | 1微秒 |
timestamp [ ( | 8 bytes | 包括日期和时间,有时区 | 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的组合就应该能提供任何应用所需的全范围的日期/时间功能。
日期和时间的输入可以接受几乎任何合理的格式,包括 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.10显示了date类型可能的输入方式。
表 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。
这些类型的有效输入包括一个时间,后面跟着一个可选的时区。
(参见表 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 节可以了解如何指定时区。
时间戳类型的有效输入由一个日期和时间的串接组成,后面跟着一个可选的时区,
后面跟着一个可选的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 '2004-10-19 10:23:54'
是一个无时区的时间戳,而
TIMESTAMP '2004-10-19 10:23:54+02'
是一个带时区的时间戳。
PostgreSQL从不在确定类型之前检查字面字符串的内容,
因此会将上述两者都视为无时区的时间戳。为了确保字面量被视为
带时区的时间戳,请为其指定正确的显式类型:
TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
在一个被确定为无时区时间戳的值中,
PostgreSQL会默默忽略任何时区指示。
也就是说,结果值是从输入字符串中的日期/时间字段派生的,
并且不会根据时区进行调整。
对于带时区的时间戳值,包含明确时区的输入字符串将使用该时区的
适当偏移量转换为UTC
(协调世界时)。
如果输入字符串中未指定时区,则假定其位于系统的
TimeZone参数指示的时区,并使用
timezone区域的偏移量转换为UTC。
无论哪种情况,该值都会以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。
为了方便,PostgreSQL支持一些特殊日期/时间输入值,如表 8.13所示。这些值中infinity和-infinity在系统内部以特殊方式表示并且将被原封不动地显示。但是其他的仅仅只是概念上的速写,当被读到的时候会被转换为普通的日期/时间值(特别是,now及相关字符串在被读到时立刻被转换为一个指定的时间值)。在作为常量在SQL命令中使用时,所有这些值需要被包括在单引号内。
表 8.13. 特殊日期/时间输入
| 输入字符串 | 有效类型 | 描述 |
|---|---|---|
epoch | date, timestamp | 1970-01-01 00:00:00+00 (Unix系统时间零) |
infinity | date, timestamp, interval | 晚于所有其他时间戳 |
-infinity | date, timestamp, interval | 早于所有其他时间戳 |
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更安全。
时间/日期类型的输出格式可以设成四种风格之一: ISO 8601、SQL(Ingres)、传统的POSTGRES(Unix的date格式)或
德国。缺省是ISO格式(ISO标准要求使用 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 |
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 | 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)显示为带符号数字偏移量。
这些样式中显示的字母缩写取自当前由 TimeZone 运行时参数选择的 IANA 时区数据库条目;它们不受 timezone_abbreviations 设置的影响。
日期/时间风格可以由用户使用 SET datestyle 命令选取,在 postgresql.conf 配置文件里的参数 DateStyle 设置或者在服务器或客户端的 PGDATESTYLE 环境变量里设置。
格式化函数to_char(见第 9.8 节)也可以作为一个更灵活的方式来格式化日期/时间输出。
时区和时区习惯不仅仅受地球几何形状的影响,还受到政治决定的影响。到了1900年代,全球的时区变得稍微标准化了些,但是还是易于遭受随意的修改,部分是因为夏时制规则。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视图中(参见第 53.34 节)。PostgreSQL用广泛使用的IANA时区数据来实现该目的,因此相同的时区名字也可以在其他软件中被识别。
一个时区缩写,例如PST。这样一种声明仅仅定义了到UTC的一个特定偏移,而不像完整时区名那样指出整套夏令时转换规则。能被识别的缩写被列在pg_timezone_abbrevs视图中(参见第 53.33 节)。你不能将配置参数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),不管在那个日期夏令时是否生效。
POSIX 风格时区规范中的符号与 ISO-8601 日期时间值中的符号含义相反。
例如,2014-06-04 12:00+04 的 POSIX 时区将是 UTC-4。
为了使问题更加复杂,一些司法管辖区使用相同的时区缩写在不同时间表示
不同的 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 的语法。
libpq 客户端使用 PGTZ 环境变量来通过连接发送一个 SET TIME ZONE 命令给服务器。
interval 值可以使用下列语法书写:
[@]quantityunit[quantityunit...] [direction]
其中 quantity 是一个数字(可能是有符号的);
unit 是 微秒、
毫秒、秒、
分钟、小时、天、
周、月、年、
十年、世纪、千年,
或者这些单位的缩写或复数;
direction 可以是 ago 或者为空。@ 符号(@)是一个可选的噪声。不同单位的数量通过合适的符号计数被隐式地添加。ago 对所有域求反。如果 IntervalStyle 被设置为 postgres_verbose,该语法也被用于间隔输出。
日、小时、分钟和秒的数量可以不适用显式的单位标记指定。例如,'1 12:59:10'被读作'1 day 12 hours 59 min 10 sec'。同样,一个年和月的组合可以使用一个横线指定,例如'200-10'被读作'200年10个月'(这些较短的形式事实上是SQL标准唯一许可的形式,并且在IntervalStyle被设置为sql_standard时用于输出)。
间隔值也可以被写成 ISO 8601 时间间隔,使用该标准4.4.3.2小节的“带标志符的格式”或者4.4.3.3小节的“替代格式”。带标志符的格式看起来像这样:
Pquantityunit[quantityunit...] [ T [quantityunit...]]
该串必须以一个P开始,并且可以包括一个引入时间单位的T。可用的单位缩写在表 8.16中给出。单位可以被忽略,并且可以以任何顺序指定,但是小于一天的单位必须出现在T之后。特别地,M的含义取决于它出现在T之前还是之后。
表 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解释。为了避免混淆,我们推荐在任何字段为负值时为每一个字段都附加一个显式的符号。
在内部,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周'或'01:02:03.45'。然而,因为interval内部仅存储整数字段,小数值必须转换为更小的单位。大于月份的单位的小数部分将四舍五入为整数个月,例如'1.5年'变为'1年6个月'。周和天的小数部分被计算为整数天和微秒,假设每月30天,每天24小时,例如,'1.75个月'变为1个月22天12:00:00。仅秒将在输出时显示为小数。
表 8.17显示了一些有效interval输入的例子。
表 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 “替代格式”:含义同上 |
如前所述,PostgreSQL将interval值存储为月、日和微秒。
对于输出,月份字段通过除以12转换为年和月。天数字段按原样显示。
微秒字段转换为小时、分钟、秒和分数秒。因此,月份、分钟和秒永远不会显示超过0–11、0–59和0–59的范围,
而显示的年、天和小时字段可以非常大。(如果希望将大的天数或小时值转置为下一个更高的字段,则可以使用
justify_days和
justify_hours函数。)
间隔类型的输出格式可以被设置为四种风格之一:sql_standard、postgres、postgres_verbose或iso_8601,设置方法使用SET intervalstyle命令。默认值为postgres格式。表 8.18展示了每种输出风格的例子。
如果间隔值符合SQL标准的限制(仅年-月或仅日-时间,没有正负值部分的混合),sql_standard风格为间隔文字串产生符合SQL标准规范的输出。否则输出将看起来像一个标准的年-月文字串跟着一个日-时间文字串,并且带有显式添加的符号以区分混合符号的间隔。
当DateStyle参数被设置为ISO时,postgres风格的输出匹配PostgreSQL 8.4版本以前的输出。
当DateStyle参数被设置为非ISO输出时,postgres_verbose风格的输出匹配PostgreSQL 8.4版本以前的输出。
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 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 |