一文吃透TDengine上的时区设置

2022-05-16 00:00:00 日期 时间 写入 格式 时区
作者|杨志宇,涛思数据研发工程师

小 T 导读:很多新用户在配置TDengine的时候,偶尔会因为配置了错误的时区(timezone),而导致写入和查询的时间出现错位。今天希望这篇文章,能将T日期时间、时间戳以及TDengine在写入和查询时处理时区的行为等描述清楚。

TDengine是涛思数据专为物联网、车联网、工业互联网、IT运维等设计和优化的大数据平台,核心的时序数据库在处理时序数据上有着十分优异的性能。

一般来说,时序数据就是带有时间序列属性的数据。在处理时序数据时,TDengine有着自己独特的方式。但是如果你没有正确理解TDengine在写入和查询上的行为,极可能会因为配置了错误的时区(timezone),而导致写入和查询的时间出现错位。

下面是一个真实用户的例子:

从上图中可以看到,用户执行的一条SQL写入了“2021-07-23 07:04:00.000"这个时刻的数据,可是在不同的客户端中,查询出的结果却相差了13个小时。

今天希望通过这篇文章,将日期时间、时间戳以及TDengine在写入和查询时处理时区的行为等描述清楚,并给出如何设置timezone参数的意见,供大家参考。

在开始之前,你需要先了解以下这三点

1. TDengine中用时间戳表示日期时间,以标准的Unix元年时间(UTC时区1970年1月1日0点0分0秒)为原点,支持毫秒、微秒、纳秒三种精度;
2. 在写入时,如果SQL中是本地日期时间格式,TDengine的客户端使用当前生效的timezone配置,将SQL中的日期时间转换为timestamp;同时,也支持使用RFC-3339格式的日期时间进行写入;
3. 在shell中查询时,客户端使用当前生效的timezone配置,将TDengine中存储的timestamp转换为日期时间格式进行显示。

本文所用相关概念

  • 本地日期时间:表示当地的日期时间。12:00是中午吃饭的时间,8:00是早上上班的时间,这是人类习惯的一种表示时间的方式,是不带时区信息的日期和时间,可以当成一个String。例如:2021-07-21 12:00:00.000,表示2021年7月21日正午,时间精度以毫秒记,这个日期时间的表示方法,不带任何时区信息。

  • 时区:地理概念,按照UTC/格林威治时区,把地球划分成向东和向西各12个时区,其中东12区和西12区是一个区。时区可以通过'Asia/shanghai'这样的'地区/城市'的方式表示,也可以用UTC偏移的方式表示。例如:UTC+8,代表东八区,当协调世界时(UTC)时间为凌晨2点的时候,当地的时间为2+8点,即早上10点。

  • RFC 3339:一种表示日期时间的标准格式。RFC 3339是带时区信息的格式,即包含日期时间信息,也有时区信息。例如,以下两个时间在地球上是同一时刻:

    2019-10-12T07:20:50+00:00,这个表示2019年10月12日,上午7点20分50秒(UTC+0时区),
    2019-10-12T15:20:50+08:00,这个表示2019年10月12日,下午3点20分50秒(UTC+8时区)。
  • 时间戳:是机器存储和计算时间的方式。以Unix元年(UTC时区1970年1月1日0点0分0秒)开始经过的秒数计算,不同精度的计时方式,可以有不同的时间戳。例如:0,表示UTC时区1970年1月1日凌晨的时间。

本地日期时间、时区信息、时间戳的关系可以参考下面这张图:


TDengine如何处理日期时间?

写入

如果在insert语句中,用一个String表示日期时间,插入到TDengine,存在着将这个String解析成timestamp的过程。这个String存在不同的格式,合法的格式包括:
(1)RFC 3339标准的表示方式
(2)yyyy-MM-dd hh:mm:ss
第1种情况——采用RFC 3339标准,那么这个String是带时区信息的,可以明确地将其转换成timestamp。
例如:
这里,介绍一个小技巧:使用-r参数启动taos shell时,timestamp类型的数据,将会以时间戳(long值)的形式显示。
# taos -r

taos> drop table test.weather;
Query OK, of row(s) in database (0.004202s)

taos> create table test.weather(ts timestamp, f1 float) ;
Query OK, of row(s) in database (0.012690s)

taos> insert into test.weather values('1970-01-01T08:00:00.000+08:00',22.00) ;
Query OK, 1 of 1 row(s) in database (0.002363s)

taos> select * from test.weather;
ts | f1 |
========================================
| 22.00000 |
Query OK, 1 row(s) in set (0.001476s)

可以看到,1970-01-01T08:00:00.000+08:00,代表UTC+8时区1970年1月1日上午8:00,这正好对应UTC时区的凌晨,所以在timestamp是0。

第2种情况——在insert语句中使用yyyy-MM-dd hh:mm:ss格式的时间字符串,不含时区信息。这时,taos客户端会采用当前timezone信息,将字符串转化成timestamp。

例如:

taos> show variables;
name | value |
============================================================
timezone | (CST, +0800) |


taos> insert into test.weather(ts, f1) values('1970-01-01 00:00:00.000', 22.00);
Query OK, 1 of 1 row(s) in database (0.001290s)

taos> select * from test.weather;
ts | f1 |
========================================
-28800000 | 22.00000 |
Query OK, 1 row(s) in set (0.002220s)

可以看到,insert语句使用了配置文件中的时区信息,和insert语句中的日期时间信息,即“1970-01-01 00:00:00+08:00”,这个值在时间戳中正好代表-28800000。

由此可见,在TDengine中,时间原点是国际通用的Unix元年(UTC时区1970年1月1日凌晨)。

查询

# 在taos.cfg内配置timezone
# cat /etc/taos/taos.cfg | grep timezone
timezone UTC+

# 在shell中查询timezone
taos> show variables;
name | value |
============================================================
timezone | (CST, +0800) |

# taos -s "select * from test.weather" -r
Welcome to the TDengine shell from Linux, Client Version:2.0.20.11
Copyright (c) 2020 by TAOS Data, Inc. All rights reserved.
taos> select * from test.weather
ts | f1 |
========================================
-28800000 | 22.00000 |
Query OK, 1 row(s) in set (0.002564s)

# taos -s "select * from test.weather"
Welcome to the TDengine shell from Linux, Client Version:2.0.20.11
Copyright (c) 2020 by TAOS Data, Inc. All rights reserved.
taos> select * from test.weather;
ts | f1 |
=================================================
1969-12-31 16:00:00.000 | 22.00000 |
Query OK, 1 row(s) in set (0.002306s)

可以看到,select语句在查询时,依然存在着从ts转换为一个string串的情况,Tdengine会将ts转换成当前taos client中的时区。

Timezone配置为UTC-8

有些用户不理解,为什么在TDengine中timezone会被配置为UTC-8?原因是,在POSIX标准中,表示时区偏移量的方式和地理的表示方式不一致。
参考Wikipedia中的定义,在ISO 8601中,UTC+8为东八区,该时区是以中文为主的时区。
那在Unix中,东八区又应该如何表示?请参考下面这个例子:
# date --help
用法:date [选项]... [+格式]
 或:date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
Display the current time in the given FORMAT, or set the system date.
%z +hhmm 数字时区(例如,-0400)
%:z +hh:mm 数字时区(例如,-04:00)
%Z 按字母表排序的时区缩写 (例如,EDT)
Examples:
Show the time on the west coast of the US (use tzselect(1) to find TZ)
$ TZ='America/Los_Angeles' date

# 使用TZ='UTC-8'查看当前时间
# TZ='UTC-8' date +'%Y-%m-%d %H:%M:%S %Z %z'
2021-08-01 22:31:29 UTC +0800

# 使用TZ='UTC'查看当前时间
# TZ='UTC' date +'%Y-%m-%d %H:%M:%S %Z %z'
2021-08-01 14:31:51 UTC +0000

# 使用TZ='UTC+8'查看当前时间
# TZ='UTC+8' date +'%Y-%m-%d %H:%M:%S %Z %z'
2021-08-01 06:32:06 UTC -0800

可见,在POSIX标准中,UTC-8代表东八区,UTC+8代表西八区。这里与地理上表示时区的习惯是不一致的。在taos.cfg中,TDengine使用的是POSIX Timezone标准。

在JDBC中设置Timezone

在使用JDBC Connector连接TDengine时,可以通过3个途径设置timezone参数,分别为:url、properties和taos.cfg配置文件。

// url
Connection conn = DriverManager.getConnection("jdbc:TAOS://taosdemo.com:6030/test?timezone=UTC-8", "root", "taosdata");

// properties
Properties connProps = new Properties();
connProps.setProperty(TSDBDriver.PROPERTY_KEY_USER, "root");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_PASSWORD, "taosdata");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
Connection conn = DriverManager.getConnection("jdbc:TAOS://taosdemo.com:6030/test", connProps);

// 当url和properties中都没有制定timezone的情况下,会使用本地配置文件taos.cfg中timezone的配置参数
参考:https://www.taosdata.com/cn/documentation/connector/java

总结

后,我们再回顾一下前文中描述的用户问题:“为什么在不同的客户端中,日期时间会相差13个小时?”
执行的insert语句SQL为:

INSERT INTO n802344030600001_w21003 USING mnt_factor_item_data TAGS ("N802344030600001"'w21003'VALUES ('2021-7-23 07:04:00:000',3999,'N802344030600002','w21003',19,'COD','mg/L',4,2,'大空港片区',1,'龙翔北路监测控制站',1,'龙翔北路水监测设备',3,'龙翔北路监测终端');

SQL中是以本地日期时间的格式表示时间戳的,客户端使用了本地的timezone,将这个“2021-7-23 07:04:00:000”转换为timestamp;在查询时,Windows上的shell和Linux的shell都会将timestamp,根据当前生效的timezone,转换成日期时间格式。



参考文献:

1. 为什么用UTC+8变成了美国时区?(https://unix.stackexchange.com/questions/104088/why-does-tz-utc-8-produce-dates-that-are-utc8
2. 理解RFC 3339标准(https://medium.com/easyread/understanding-about-rfc-3339-for-datetime-formatting-in-software-engineering-940aa5d5f68a)
3. RFC 3339标准(https://www.ietf.org/rfc/rfc3339.txt 
4. UTC+8时区的定义(https://zh.wikipedia.org/wiki/UTC%2B08:00

相关文章