环境

  • Ubuntu 22.04
  • Db2 11.5.0

时间类型

参见 https://www.ibm.com/docs/en/db2/11.5?topic=list-datetime-values

Db2有3种时间类型:

  • DATE :例如 03/20/2023 ,当前日期的special register为 CURRENT DATE
  • TIME :例如 21:23:23 ,当前时间的special register为 CURRENT TIME
  • TIMESTAMP :例如 2023-03-20-21.23.49.513704 ,当前时间戳的special register为 CURRENT TIMESTAMP

可以通过 values current date ,也可以通过 select current date from sysibm.sysdummy1 的方式来查询当前日期/时间。

➜  ~ db2 values current date

1         
----------
03/20/2023

  1 record(s) selected.
➜  ~ db2 values current time

1       
--------
21:23:23

  1 record(s) selected.
➜  ~ db2 values current timestamp

1                         
--------------------------
2023-03-20-21.23.49.513704

  1 record(s) selected.

时间是可以做加减运算的,以时间戳为例:

➜  ~ db2 "select timestamp('2023-03-22-11.26.39.483219') - timestamp('2023-03-22-11.26.38.065497') from sysibm.sysdummy1"

1
----------------------
              1.417722

  1 record(s) selected.

可见,这2个时间戳相差 1.417722 秒。

但是,当时间戳相差超过1分钟时,情况就不一样了:

➜  ~ db2 "select timestamp('2023-03-22-11.26.39.483219') - timestamp('2023-03-22-11.25.38.065497') from sysibm.sysdummy1"

1
----------------------
            101.417722

  1 record(s) selected.

结果并不是期望的 61.417722 ,这一点要注意。要想获取时间差,需要使用 timestampdiff() 函数,后面会有介绍。

时间转换

字符串时间戳Unix时间戳 之间的相互转换。

注:广义的时间戳,也称为Unix时间戳,是指格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数(或毫秒数)。比如 1679408091745 ,其对应的北京时间是 2023-03-21 22:14:51

为了避免混淆,本文中把广义的时间戳称为 Unix时间戳 ,把Db2的timestamp类型称为 时间戳

准备

首先创建表 t1 ,它有3个字段:

  • c1timestamp 类型
  • c2varchar 类型
  • c3bigint 类型
➜  ~ db2 "create table t1 (c1 timestamp, c2 varchar(50), c3 bigint)"
DB20000I  The SQL command completed successfully.

插入一条记录:

➜  ~ db2 "insert into t1 values ('2023-03-20-21.23.49.513704', '2023-03-20-21.23.49.513704', 1679319723841)"
DB20000I  The SQL command completed successfully.

注意: c1timestamp 类型,实际插入的值是字符串,它能正确识别并隐式转换。

查询记录:

➜  ~ db2 "select * from t1"                                         

C1                         C2                                                 C3                  
-------------------------- -------------------------------------------------- --------------------
2023-03-20-21.23.49.513704 2023-03-20-21.23.49.513704                                1679319723841

  1 record(s) selected.

字符串转时间戳

可以使用 timestamp() 函数来转换,例如:

➜  ~ db2 "select c2, timestamp(c2) from t1"

C2                                                 2                         
-------------------------------------------------- --------------------------
2023-03-20-21.23.49.513704                         2023-03-20-21.23.49.513704

  1 record(s) selected.

timestamp() 函数

参见 https://www.ibm.com/docs/en/db2/11.5?topic=functions-timestamp

timestamp()函数的参数可以是字符串,也可以是date、timestamp类型的数据(不能是time类型的,因为缺少日期),比如:

  • select timestamp(current date) from sysibm.sysdummy1 // 时间会当作0点整
  • select timestamp('03/22/2023') from sysibm.sysdummy1 // 时间会当作0点整
  • select timestamp(current timestamp) from sysibm.sysdummy1
  • select timestamp('2023-03-20-21.23.49.513704') from sysibm.sysdummy1

其参数还可以是 GENERATE_UNIQUE() 函数的结果。先来看一下该函数:

➜  ~ db2 "select GENERATE_UNIQUE() from sysibm.sysdummy1"

1                            
-----------------------------
x'20230322004530944340000000'

  1 record(s) selected.

其类型是 CHAR FOR BIT DATA ,它用16进制数来表示精度到皮秒(10的-12次方秒)的时间戳。在本例中,该时间戳为 2023-03-22-00.45.30.944340 。我想计算机的精度可能到不了皮秒,只能到微秒,所以最后6位都是 0

这么做的好处是省空间,只用了13个字节(在16进制中,每2个数字占用1个字节)

该数值也可以作为 timestamp() 函数的参数:

➜  ~ db2 "select timestamp(x'20230322004530944340000000') from sysibm.sysdummy1"

1                         
--------------------------
2023-03-22-00.45.30.944340

  1 record(s) selected.

timestamp() 函数的参数还可以是14个数字,代表 yyyyxxddhhmmss ,比如:

➜  ~ db2 "select timestamp('20230322092651') from sysibm.sysdummy1"

1                         
--------------------------
2023-03-22-09.26.51.000000

  1 record(s) selected.

此外, timestamp() 函数还可以有第2个参数,根据第2个参数的类型,又分为两种情况:

  • 非数字:第1个参数必须是date类型,或者能表示date的字符串,第2个参数必须是time类型,或者能表示time的字符串,比如:
➜  ~ db2 "select timestamp('03/22/2023', '09:26:51') from sysibm.sysdummy1"

1                         
--------------------------
2023-03-22-09.26.51.000000

  1 record(s) selected.
  • 数字:第1个参数必须是date、timestamp或者能表示date、timestamp的字符串,第2个参数必须是0到12之间的整数,代表时间戳的精度(也就是以秒为单位,小数点后面保留几位)。比如:
➜  ~ db2 "select timestamp('2023-03-20-21.23.49.513704', 12) from sysibm.sysdummy1"

1                               
--------------------------------
2023-03-20-21.23.49.513704000000

  1 record(s) selected.

时间戳转字符串

to_char() 函数(也叫 varchar_format() 函数)可以把date或者timestamp转换为varchar,参见 https://www.ibm.com/docs/en/db2/11.5?topic=functions-varchar-format

例如:

➜  ~ db2 "select c1, to_char(c1, 'YYYY-MM-DD HH24:MI:SS.NNNNNN') from t1"

C1                         2                                                                                                                                                                                                                                                             
-------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2023-03-20-21.23.49.513704 2023-03-20 21:23:49.513704                                                                                                                                                                                                                                    

  1 record(s) selected.

to_char() 函数有很多参数选项,没有深入研究,等有需要的时候再查官方文档。

时间戳转Unix时间戳

前面提到,两个时间戳直接相减,并不能得到期望的结果,而要使用 timestampdiff() 函数。参见 https://www.ibm.com/docs/en/db2/11.5?topic=functions-timestampdiff

例如:

➜  ~ db2 "select timestampdiff(1, char(timestamp('2023-03-22-11.26.39.483219') - timestamp('2023-03-22-11.25.38.065497'))) from sysibm.sysdummy1"

1
-----------
   61417722

  1 record(s) selected.

这回得到了期望的 61 秒( 61417722 的单位是微秒)。

timestampdiff() 函数有2个参数:

  • 第1个参数是一个整数,意义如下:
  • 1:Microseconds
  • 2: Seconds
  • 4: Minutes
  • 8: Hours
  • 16: Days
  • 32: Weeks
  • 64: Months
  • 128: Quarters
  • 256: Years
  • 第2个参数是一个字符串(若不是字符串则会隐式转换为字符串),表示两个时间戳相减的结果。

要格外注意的是,timestampdiff() 函数的结果是不精准的。它认为1年就是365天,1个月就是30天,例如:

➜  ~ db2 "select timestampdiff(16, char(timestamp('2023-03-01-00.00.00.000000') - timestamp('2023-02-01-00.00.00.000000'))) from sysibm.sysdummy1"

1
-----------
         30

  1 record(s) selected.

从2月1日到3月1日,期望结果是 28 天,而实际结果是 30 天。

既然 timestampdiff() 函数不精准,就要想其它办法。比如我们可以来“硬算”时间,具体方法为,对于给定的时间戳,先计算它和 1970-01-01 之间的日期差别,乘以一天的秒数 86400 ,再加上秒数即可。

例如,对于时间戳 2023-01-01 06:12:34.567

➜  ~ db2 "select 86400 * (days(timestamp('2023-01-01 06:12:34.567')) - days('1970-01-01')) + midnight_seconds(timestamp('2023-01-01 06:12:34.567')) from sysibm.sysdummy1"

1
-----------
 1672553554

  1 record(s) selected.

在表 t1 中,c1 是时间戳,其值为 2023-03-20-21.23.49.513704 ,对应的Unix时间戳为 1679347429513 (精确到毫秒),我们来验证一下:

➜  ~ db2 "select c1,  86400 * (days(c1) - days('1970-01-01')) + midnight_seconds(c1) from t1"

C1                         2
-------------------------- -----------
2023-03-20-21.23.49.513704  1679347429

  1 record(s) selected.

注意:该方法的精度只到秒级别。

Unix时间戳转时间戳

只需把Unix时间戳加到 1970-01-01 00:00:00.000 上,就可以得到其对应的时间戳,例如:

➜  ~ db2 "select timestamp('1970-01-01 00:00:00.000') + 120.123456 SECONDS from sysibm.sysdummy1"

1
--------------------------
1970-01-01-00.02.00.123456

  1 record(s) selected.

在表 t1 中,c3 是Unix时间戳(类型为bigint),其值为 1679319723841 ,对应的时间戳为 2023-03-20 13:42:03.841 ,我们来验证一下:

➜  ~ db2 "select c3, timestamp('1970-01-01 00:00:00.000') + (c3 / 1000.0) SECONDS from t1"

C3                   2
-------------------- --------------------------
       1679319723841 2023-03-20-13.42.03.841000

  1 record(s) selected.

注意: 1679319723841 是毫秒精度的,要得到秒级的数值,需要除以 1000 ,但整数运算的结果还是整数,为了保留小数,需要除以 1000.0

时区

前面的例子,都假设是UTC时间,没有做任何时区转换的处理。

对于时间戳和Unix时间戳而言:

  • Unix时间戳:它只是两个时间戳之间的差值,我们可以认为它不涉及时区问题。
  • 时间戳:就涉及到时区问题了,显然中国的3月22号20点和美国的3月22号20点是不同的时间。

比如 2023-03-20 13:42:03.841 ,假定这是UTC的时间,则在中国和美国表现为:

  • 中国(东8区): 2023-03-20 21:42:03.841
  • 美国太平洋时区(西8区): 2023-03-20 06:42:03.841 (夏令时时间)

前面提到, current timestamp special register表示当前时间戳,这个时间戳是带时区的,也就是数据库本地的时间。

Db2还提供了 current timezone special register,表示所在时区,例如,对于美国太平洋时区:

➜  ~ db2 "select current timezone from sysibm.sysdummy1"

1
--------
 -70000.

  1 record(s) selected.

虽然是西8区,但现在是美国的夏令时,所以是和UTC时间差7个小时。

对于带时区信息的时间戳,若想获取对应的UTC时间戳,只需减去 current timezone special register。

➜  ~ db2 "select current timestamp - current timezone from sysibm.sysdummy1"

1
--------------------------
2023-03-23-02.55.39.808973

  1 record(s) selected.