在PostgreSQL和Oracle中,有一个数据类型叫作Interval,我称之为时间间隔类型,用于表达一段时间间隔。ISO8601及等价的GBT 7408-2005版也规定了标准的时间间隔类型的格式。在QDB中,我们加入了一个称为TQSQLInterval的类型来描述这个值,结合它及PostgreSQL、Oracle我们来详细了解一下这个类型:
1、Interval类型的作用
Interval类型用于表示一个时间时隔,如歌里唱的:“一百年以后,所有的人都忘了我~~~”,这个一百年就是一个时间间隔。现实生活中也常有这样的场景,如两天后干什么事中的两天后,老人常说的“三十年前,俺XXX”中的三十年前。
2、Interval类型的定义
为了支持Interval类型,我在QDB的QValue单元实现了一个TQInterval类型,使用64位整数来存贮时间间隔。其中,高24位用于记录时间月数,低40位用于记录毫秒数,也就是说QDB中时间间隔的最小精度是毫秒,数据库里如果时间间隔小于1毫秒的部分将被直接舍弃。如1.23456秒,会被记录做1234毫秒。
为什么TQInterval会将时间间隔记录为两个部分呢?这是因为从毫秒到日,都可以相互之间直接换算,如一天是86400000毫秒,我们直接做算术运算就可以轻易互转,但日和月之间却没有办法直接换算,每月的天数在28-31之间浮动,但月与年之间有固定的换算关系(1年=12个月),所以将月份做为一个基础单位,将毫秒做为一个基础单位,两者就分成了两个组成部分。
根据上面的定义:
24位的月份,除去了一位做为符号位,剩下的23位表示范围-8388608~8388607之间,约为±699050年。
40位的毫秒数去除一个符号位后,剩下的39位表示范围为-549755813888~549755813887之间,约为±6362天,约±17年。
从上面的描述表示范围我个人觉得足以满足我们的实际业务需要,如果实际使用中的时间间隔超过了上面的范围,我觉得应该考虑自行解决了。
3、Interval类型的表述形式
Interval类型的表达式有以下几种格式:
(1)、ISO 8601或GBT7504约定的时间间隔格式
以P打头,然后是PnYnMnDTnHnMnS,其中P是必需的,但如果没有时间部分,T不是必需的,如P1Y2M3DT4H5M6S代表1年2个月3天4小时5分6秒的时间间隔值,秒数可以是小数,如PT6.5S这样子。
TQSQLInterval的AsISOString属性提供这种类型的格式编码和解码支持,同时提供TryFromISOString函数来测试是否是该格式。
(2)、PostgreSQL格式
PostgreSQL以更接近自然语言(当然是英文)的格式来描述时间间隔,如上例中的第一个时间间隔就被写为 1 year 2 mons 3 days 4 hours 5 minutes 6 seconds,前面可以加一个@符号,也可以省略。
TQSQLInterval的AsPgString属性提供这种类型的格式编码和解码支持,同时提供TryFromPgString函数来测试是否能够转换。
(3)、SQL格式
SQL格式的Interval将值分为两个部分,年月部分使用“年-月”的形式来表述,剩下的部分使用“日 时:分:秒.毫秒”的形式来表述。如上面的等价写法就是 1-2 3 4:5:6。
TQInterval的AsSQLString属性提供这种类型的格式编码和解码支持,同时提供TryFromSQLString函数来测试是否能够转换。
(4)、Oracle格式
好吧,我们喜欢和微软一样特立独行的Oracle大师来了。它的格式同样是分为两个部分:年月和时间部分,但它的格式是:
INTERVAL ‘字符串’ 格式描述
首先INTERVAL是必需的,然后后面的字符串要根据后面的格式描述来确定其每个部分的意义:有YEAR TO MONTH和DAY TO SECOND两种大的形式,如果是YEAR TO MONTH部分,其格式为’y-m’,如果是DAY TO SECOND,则是’DAY Hour:Minute:Second.MilliSecond’的格式。具体格式可以参考Oracle的英文文档。
根据上面的描述,我们可以将前面的值描述为两个值,分别是INTERVAL ‘1-2’ YEAR TO MONTH和INTERVAL ‘3 4:5:6’ DAY TO SECOND,我看起来就头大了,不知道你头大没。
TQSQLInterval的AsOracleString属性提供这种类型的格式编码和解码支持,同时提供TryFromOracleString函数来测试是否能够转换。
4、大小比较
(1)、相等判断
这个好说,两个值完全一样就是相等,不完全一样就是不相等。
(2)、大小判断
我们知道一年的真正大小是365.24219天,而1年是12个月,所以一个月实际上是30.43684916666667天,所以,当进行大小判断时,我们可以简单的将高24位月数×30.43684916666667×86400000+低40位的毫秒值的结果进行比较,TQSQLInterval使用的就是这一算法,当然其科学性可能不一定很严格,但用于比较间隔的大小,我觉得还是合适的。如果大家有更好的想法,欢迎提出。
好了,我们前面说了很多,那么TQSQLInterval的AsString属性用的是啥格式?首先,写值时,上面的格式任选其一,读值时,与AsSQLString等价(因为我觉得那个描述最简洁)。
接下来,我来解释一个问题:如何描述13个月前这种间隔呢?
13个月前记录做-13月,但转换成字符串时,由于满足了12个月,所以我们可以描述为一年前的前一个月,即-1年-1月,但我不喜欢这样的描述,我更喜欢-2年11月形式,保证全部只需要年月和时间部分,都只有最前面的年和日是负数,剩下的都是正数即可。如果脑袋转不过来,TQSQLInterval会自动给你转过来。