日期/时间类型 ============= 下表显示了 OushuDB 支持的SQL中所有日期和时间类型。 这些数据类型上可以进行的操作在 `日期/时间函数和操作符 <./date-time-functions-and-operators.html>`_ 中描述。 **表.日期/时间类型** .. list-table:: :widths: auto :header-rows: 1 * - 名字 - 存储空间 - 描述 - 最低值 - 最高值 - 分辨率 * - timestamp [ (p) ] [ without time zone ] - 8 字节 - 日期和时间(无时区) - 4713 BC - 294276 AD - 1 毫秒 / 14 位 * - timestamp [ (p) ] with time zone - 8 字节 - 日期和时间,有时区 - 4713 BC - 294276 AD - 1 毫秒 / 14 位 * - date - 4 字节 - 只用于日期 - 4713 BC - 5874897 AD - 1 天 * - time [ (p) ] [ without time zone ] - 8 字节 - 只用于一日内时间 - 00:00:00 - 24:00:00 - 1 毫秒 / 14 位 * - time [ (p) ] with time zone - 12 字节 - 只用于一日内时间,带时区 - 00:00:00+1459 - 24:00:00-1459 - 1 毫秒 / 14 位 * - interval [ fields ] [ (p) ] - 12 字节 - 时间间隔 - -178000000 年 - 178000000 年 - 1 毫秒 / 14 位 time, timestamp和interval接受一个可选的精度值 p以指明秒域中小数部分的位数。没有明确的缺省精度, p的范围对timestamp和interval类型是从0到6。 .. note:: 如果timestamp 数值是以双精度浮点数(目前缺省)的方式存储的, 那么有效精度会小于 6。timestamp值是以 2000-01-01 午夜之前或之后的秒数存储的。 当timestamp值用浮点数实现时,微秒的精度是为那些在 2000-01-01 前后几年的日期实现的, 对于那些远一些的日子,精度会下降。如果timestamp数值是以8字节整数(一个编译选项)的方式存储的, 那么微秒的精度就可以在数值的全部范围内都可以获得。同一个编译选项也决定time和interval值是保存成浮点数还是八字节整数。在以浮点数存储的时候,随着时间间隔的增加,大的 interval数值的精度会降低。 对于time类型,如果使用了八字节的整数存储,那么p 允许的范围是从 0 到 6 ,如果使用的是浮点数存储,那么这个范围是 0 到 10 。 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就按照"年-月-日"解析。 请记住任何日期或者时间的文本输入需要由单引号包围,就像一个文本字符串一样。 参考 `其他类型的常量 <./sql-lexical-structure.html#id9>`_ 获取更多信息。 SQL要求使用下面的语法 :: type [ (p) ] 'value' 可选的精度声明中的p是一个整数,表示在秒域中小数部分的位数, 我们可以对time,timestamp,interval类型声明精度。 允许的精度在上面已经说明。如果在常量声明中没有声明精度,缺省是文本值的精度。 日期 ++++++ 下表显示了date类型可能的输入方式。 **表.Date Input** .. list-table:: :widths: auto :header-rows: 1 * - 例子 - 描述 * - January 8, 1999 - 在任何datestyle输入模式下都无歧义 * - 1999-01-08 - ISO 8601格式(建议格式),任何方式下都是 1999 年 1 月 8 号 * - 1/8/1999 - 有歧义,在MDY下是一月八号;在DMY模式下是八月一日 * - 1/18/1999 - MDY模式下是一月十八日,其它模式下被拒绝 * - 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 - 一月八日,除了在YMD模式下是错误的之外 * - Jan-08-99 - 一月八日,除了在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 zone 和time [ (p) ] with time zone。 只写time等效于time without time zone。 这些类型的有效输入由当日时间后面跟着可选的时区组成(参阅下表)。 如果在time without time zone类型的输入 同样指定的日期也会被忽略,除非使用了一个包括夏令时规则的时区名,比如 America/New_York,在这种情况下, 必须指定日期以确定这个时间是标准时间还是夏令时。时区偏移将记录在 time with time zone中。 **表.时间输入** .. list-table:: :widths: auto :header-rows: 1 * - 例子 - 描述 * - 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 * - 04:05:06-08:00 - ISO 8601 * - 04:05-08:00 - ISO 8601 * - 040506-08 - ISO 8601 * - 04:05:06 PST - 缩写的时区 * - 2003-04-12 04:05:06 America/New_York - 用名字声明的时区 **表.时区输入** .. list-table:: :widths: auto :header-rows: 1 * - 例子 - 描述 * - PST - 太平洋标准时间(Pacific Standard T * - America/New_York - 完整时区名称 * - PST8PDT - POSIX 风格的时区 * - -8:00 - ISO-8601 与 PST 的偏移 * - -800 - ISO-8601 与 PST 的偏移 * - -8 - ISO-8601 与 PST 的偏移 * - zulu - 军方对 UTC 的缩写 * - z - zulu的缩写 时间戳 +++++++++ 时间戳类型的有效输入由一个日期和时间的连接组成,后面跟着一个可选的时区, 一个可选的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。OushuDB 从来不会在确定文本的类型之前检查文本内容,因此会把上面两个都看做是 timestamp without time zone。因此要保证把上面的第二个当作 timestamp with time zone看待,就要给它明确的类型: :: TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02' 如果一个文本已被确定是timestamp without time zone,OushuDB 将悄悄忽略任何文本中指出的时区。因此,生成的日期/时间值是从输入值的日期/时间字段衍生出来的, 并且没有就时区进行调整。 对于timestamp with time zone,内部存储的数值总是 UTC(全球统一时间, 以前也叫格林威治时间GMT)。如果一个输入值有明确的时区声明, 那么它将用该时区合适的偏移量转换成 UTC 。如果在输入字符串里没有时区声明, 那么它就假设是在系统的TimeZone参数里的那个时区, 然后使用这个timezone时区转换成 UTC 。 当输出timestamp with time zone时,它始终从UTC转换为当前时区,并显示为该区域中的本地时间。要查看其他时区的时间,请更改时区或使用AT TIME ZONE结构(请参阅 `AT TIME ZONE <./date-time-functions-and-operators.html#at-time-zone>`_ ) 在timestamp without time zone和timestamp with time zone 之间的转换通常假设timestamp without time zone数值应该以 timezone本地时间的形式接受或者写出。其它的时区可以用 AT TIME ZONE的方式为转换声明。 间隔 ++++++++ 间隔值可以用下面的语法写: :: [@] quantity unit [quantity unit...] [direction] 这里quantity是一个数字(可能已标记);unit 可以是microsecond,millisecond,second, minute,hour,day, week,month,year, decade,century,millennium 或这些单位的缩写或复数。direction可以是ago或为空。 @标记是可选的。不同的单位的数量被隐式地添加适当的计算符号。 可以在没有明确单位标记的情况下声明天,小时,分钟和秒。例如,'1 12:59:10' 等同于'1 day 12 hours 59 min 10 sec'。 可选的秒的精确度p应该取0-6,而且其缺省值与输入文本的精确度一致。 内部,interval值被存储为月,日,秒的格式, 这是因为月中包含天数不同,并且如果进行了夏令时调整,那么一天可以有23或25小时。 当秒字段可以存储分数时,月和天字段可以是整数型。 由于时间间隔通常是由常量字符串或timestamp减法来定义的, 这种存储方法在大多数情况下很有效。justify_days和justify_hours 函数可用于调整溢出正常范围值的天和小时。 特殊值 ++++++++ OushuDB 为方便起见支持在下表里面显示的几个特殊输入值。 值infinity和-infinity是特别在系统内部表示的, 并且将按照同样的方式显示;但是其它的都只是符号缩写, 在读取的时候将被转换成普通的日期/时间值。特别是now 和相关的字符串在读取的时候就被转换成对应的数值。 所有这些值在 SQL 命令里当作普通常量对待时,都需要包围在单引号里面。 **表.特殊日期/时间输入** .. list-table:: :widths: auto :header-rows: 1 * - 输入字符串 - 适用类型 - 描述 * - epoch - date, timestamp - 1970-01-01 00:00:00+00 (Unix 系统零时) * - infinity - date, timestamp - 比任何其它时间戳都晚 * - -infinity - date, timestamp - 比任何其它时间戳都早 * - now - date, time, timestamp - 当前事务的开始时间 * - today - date, timestamp - 今日午夜 * - tomorrow - date, timestamp - 明日午夜 * - yesterday - date, timestamp - 昨日午夜 * - allballs - time - 00:00:00.00 UTC 下列SQL兼容函数也可以用于获取对应数据类型的当前时间值: CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME, LOCALTIMESTAMP。后四个接受一个可选的精度声明( `当前日期/时间 <./date-time-functions-and-operators.html#id4>`_ )。不过,请注意这些 SQL 函数不是 被当作数据输入字符串识别的。 日期/时间输出 -------------- 日期/时间类型的输出格式可以通过SET datestyle 命令设成 ISO 8601(默认)、SQL(Ingres)、 传统的POSTGRES(Unix date格式)或German四种风格之一。缺省的是ISO 格式。(SQL标准要求使用 ISO 8601 格式。) 下表显示了每种输出风格的例子。 date和time类型的输出当然只是给出的例子里面的日期和时间部分。 **表. 日期/时间输出风格** .. list-table:: :widths: auto :header-rows: 1 * - 风格 - 描述 - 例子 * - 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 如果声明了 DMY 顺序,那么在SQL和 POSTGRES 风格里, 日期在月份之前出现,否则月份出现在日期之前(参阅 `日期/时间输入 <./date-time-types.html#id2>`_ 看看这个设置如何影响对输入值的解释)。下表显示了一个例子。 **表. 日期顺序习惯** .. list-table:: :widths: auto :header-rows: 1 * - 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 间隔输出格式看起来像输入格式,除了century或week单位被转换成了years和days,而且ago被转换成一个合适的标记。在ISO 模式中,输入看起来像: :: [ quantity unit [ ... ] ] [ days ] [ hours:minutes:seconds ] 用户可以用SET datestyle命令选取日期/时间的风格, 也可以在配置文件中的DateStyle参数中设置,或者在服务器或客户端的 PGDATESTYLE环境变量中设置。也可以用格式化函数to_char(参见 `数据类型格式化参数 <./data-type-formatting-functions.html>`_ ) 来更灵活地控制时间/日期地输出。 时区 ------ 时区和时区习惯不仅仅受地球几何形状的影响,还受到政治决定的影响。 到了 19 世纪,全球的时区变得稍微标准化了些,但是还是易于遭受随意的修改, 尤其是因为夏时制规则。OushuDB 支持1902年到2038年间的夏时令规则(与传统Unix 系统时间的全范围一致)。不属于这个范围的时间被看作选定时区的“标准时间”,无论它落入一年中的哪个部分。 OushuDB 在典型应用中尽可能与SQL 的定义相兼容。但SQL标准在日期/时间类型和功能上有一些奇怪的混淆。 两个显而易见的问题是: * date类型与时区没有联系,而time类型却有。 然而,现实世界的时区只有在与时间和日期都关联时才有意义, 因为时间偏移量(时差)可能因为实行类似夏时制这样的制度而在一年里有所变化。 * 缺省的时区用一个数字常量表示与UTC的偏移(时差)。因此, 当跨DST(夏时制)界限做日期/时间算术时, 我们根本不可能把夏时制这样的因素计算进去。 为了克服这些困难,我们建议在使用时区的时候,使用那些同时包含日期和时间的日期/时间类型。 我们建议不要使用time with time zone类型(尽管 OushuDB 出于合理应用以及为了与SQL 标准兼容的考虑支持这个类型)。OushuDB 假设你用于任何类型的本地时区都只包含日期或时间(而不包含时区)。 在系统内部,所有时区已知的日期和时间都用全球统一时间UTC格式存储, 时间在发给客户前端前由数据库服务器根据TimeZone 配置参数声明的时区转换成本地时间。 OushuDB 允许你用三种方法指定时区: * 完整的时区名。例如Asia/Shanghai。所有可以识别的时区名在 pg_timezone_names视图中列出。OushuDB 使用广泛使用的zic 时区数据, 所以这些时区名在其它软件里也能被轻松的识别。 * 时区缩写。例如PST。这种缩写名通常只是定义了相对于 UTC 的偏移量, 不同于完整的时区名可能还隐含着一组夏时制转换规则。 所有可以识别的时区缩写在pg_timezone_abbrevs视图中列出。你不能设置 TimeZone或log_timezone 配置参数为时区缩写,但是你可以在日期/时间输入值中结合AT TIME ZONE 操作符使用时区缩写。 * 除完整的时区名及其缩写之外,OushuDB 还接受 POSIX 风格的 STDoffset或STDoffsetDST 格式的时区,其中的STD是时区缩写、offset 是一个相对于 UTC 的小时偏移量、DST是一个可选的夏时制时区缩写,假定相对于给定的偏移量提前一小时。例如,如果EST5EDT不是一个已识别的时区名, 那么它将等同于美国东部时间。如果存在夏时制时区名是当前时区名,根据zic 时区数据库的 posixrules条目中相同的夏时制事务规则,可以考虑使用这个特性。 在一个OushuDB 标准安装中,posixrules与US/Eastern 相同,因此POSIX格式的时区声明遵循USA夏时制规则。如果需要,可以通过替换posixrules 文件来调整该习惯。 这就是完整的时区名与时区缩写之间的差异:时区缩写总是代表一个相对于 UTC 的固定偏移量, 然而大多数完整的时区名隐含着一个本地夏令时规则,因此就有可能有两个相对于 UTC 的不同偏移量。 需要警惕的是,由于没有合理的时区缩写检查,POSIX格式的时区特点能导致静默的伪输入。 例如,使用SET TIMEZONE TO FOOBAR0时,实际上系统使用的是一个很特别的UTC缩写。时区名是大小写不敏感的。 可以在postgresql.conf文件里设置timezone 配置参数, 除此之外,还有好几种特殊方法可以设置它: * 如果在postgresql.conf中没有声明时区或时区没有作为服务器命令行的一个选择,服务器会尝试使用TZ 环境变量作为缺省的时区。如果TZ 没有被定义或没有任何其他对 OushuDB 已知的时区,服务器会试图通过检查C语言库函数localtime() 得到服务器操作系统的缺省值。该缺省值被选作在OushuDB 中所有已知的时区中最易匹配的。 * 使用SQL命令SET TIME ZONE为会话设置时区, 这是SET TIMEZONE TO的一个可选的拼写方式,更加兼容标准。 * 如果在客户端设置了PGTZ环境变量,那么libpq 在连接时将使用这个环境变量给后端发送一个SET TIME ZONE命令。 内部 ------- OushuDB 使用 Julian 日期进行所有的日期/时间计算。假设一年的长度是 365.2425 天。这个方法可以很精确地预计/计算从 4713 BC(公元前 4713 年)到很久的未来中任意一天的日期。 19 世纪以前的日期传统(历法)只在一些趣味读物上出现,因此在日期/时间控制器中并没有考虑。