夏令时的坑:你的数据库真的能正确处理时间跳变吗?

简介: 时区是地球上使用相同标准时间的区域。由于地球的自转,为了保证各地的时间与当地的日出日落相协调,全球划分为多个时区。

1. 定义

1.1 时区
时区是地球上使用相同标准时间的区域。由于地球的自转,为了保证各地的时间与当地的日出日落相协调,全球划分为多个时区。

1.2 IANA 时区
IANA(Internet Assigned Numbers Authority)时区数据库,也称为 tz database,提供全球时区信息的标准参考。它是现代各类系统和软件处理时区相关操作的基础。
IANA 使用“区域/城市”格式(如 Europe/Berlin)来明确标识时区。
时序数据库 TDengine 在不同组件中均支持使用 IANA 时区(除 Windows taos.cfg 时区设置外)。

1.3 标准时间与当地时间
标准时间是根据地球上某个固定经线确定的时间。它为各个时区提供了一个统一的参考点。

  • 格林尼治标准时间(GMT):历史上使用的参考时间,位于0°经线。
  • 协调世界时(UTC):现代的时间标准,类似于GMT,但更加精确。
    标准时间与时区的关系:
  • 基准:标准时间(如 UTC)是时区设定的基准点。
  • 偏移量:不同时区通过相对于标准时间的偏移量来定义。例如,UTC+1表示比 UTC 快 1 小时。
  • 区域划分:全球被划分为多个时区,每个时区使用一个或多个标准时间。
    相对于标准时间,每个地区根据其所在时区设定其当地时间:
  • 时区偏移:当地时间等于标准时间加上该时区的偏移量。例如,UTC+2 表示比 UTC 时间快 2 小时。
  • 夏令时(DST):某些地区在特定时间段调整当地时间,例如将时钟拨快一小时。详见下节。

1.4 夏令时
夏令时(Daylight Saving Time,DST)是一种通过将时间提前一小时,以充分利用日光、节约能源的制度。通常在春季开始,秋季结束。夏令时的具体开始和结束时间因地区而异。以下均以柏林时间为例,对夏令时和夏令时的影响做说明。

dd2f75ec18c62545251e1c3e3331f1e4__fallback_source=1&height=1280&mount_node_token=doxcn1XRlzJBTnio50KQas1p4Gd&mount_point=docx_image&policy=equal&width=1280.png

按照这个规则,可以看到:

  • 柏林当地时间 2024 年 3 月 31 日 02:00:00 到 03:00:00 (不含 03:00:00)之间的时间不存在(跳变)。
  • 柏林当地时间 2024 年 10 月 27 日 02:00:00 到 03:00:00 (不含 03:00:00)之间的时间出现了两次。

夏令时与 IANA 时区数据库

  • 记录规则:IANA 时区数据库详细记录了各地的夏令时规则,包括开始和结束的日期与时间。
  • 自动调整:许多操作系统和软件利用 IANA 数据库来自动处理夏令时的调整。
  • 历史变更:IANA 数据库还追踪历史上的夏令时变化,以确保准确性。

夏令时与时间戳转换

  • 时间戳转为当地时间是确定的。例如,1729990654 为柏林时间 夏令时 2024-10-27 02:57:34,1729994254 为柏林时间 冬令时 2024-10-27 02:57:34 (这两个本地时间除时间偏移量外是一样的)。
  • 不指定时间偏移量时,当地时间转为时间戳是不确定的。夏令时跳过的时间不存在会造成无法转换成时间戳,如 柏林时间 2024-03-31 02:34:56 不存在,所以无法转换为时间戳。夏令时结束时重复导致无法确定是哪个时间戳,如 2024-10-27 02:57:34 不指定时间偏移量无法确定 是 1729990654 还是 1729994254。指定时间偏移量才能确定时间戳,如 2024-10-27 02:57:34 CEST(+02:00) ,指定了夏令时 2024-10-27 02:57:34 时间戳 1729990654 。

1.5 RFC3339 时间格式

RFC 3339 是一种互联网时间格式标准,用于表示日期和时间。它基于 ISO 8601 标准,但更具体地规定了一些格式细节。
其格式如下:

  • 基本格式:YYYY-MM-DDTHH:MM:SSZ
  • 时区表示:
    • Z 表示协调世界时(UTC)。
    • 偏移量格式,例如 +02:00,表示与 UTC 的时差。
      通过明确的时区偏移,RFC 3339 格式可以在全球范围内准确地解析和比较时间。
      RFC 3339 的优势:
  • 标准化:提供统一的格式,方便跨系统数据交换。
  • 清晰性:明确时区信息,避免时间误解。
    TDengine 在 REST API 和 Explorer UI 中,均使用 RFC3339 格式进行展示。在 SQL 语句中,可使用 RFC3339 格式写入时间戳数据:
    ` sinsert into t1 values('2024-10-27T01:59:59.000Z', 0); select * from t1 where ts >= '2024-10-27T01:59:59.000Z';

1.6 未定义行为

未定义行为(Undefined Behavior)是指特定代码或操作没有明确规定的结果,也不会对该结果作出兼容性的保证。在 TDengine 中,用户不可依赖当前未定义的行为进行判断或应用。

2. 夏令时在 TDengine 中的写入与查询

我们使用下表来展示夏令时在写入和查询中的影响。
QQ截图20250321145002.jpg
2.1 表头说明

  • TIMESTAMP:TDengine 中使用 64位整数来存储原始时间戳。
  • UTC:时间戳对应的 UTC 时间表示。
  • Europe/Berlin:表示时区 Europe/Berlin 对应的 RFC3339 格式时间。
  • Local:表示时区 Europe/Berlin 对应的当地时间(不含时区)。

2.2 表格分析

  • 在夏令时开始(柏林时间 3月31日 02:00 )时,时间直接从 02:00 跳到 03:00(往后跳一小时)。
    • 浅绿色是夏令时开始前一小时的时间戳;
    • 深绿色是夏令时开始后一小时的时间戳;
    • 红色为 TDengine 数据库中插入了不存在的当地时间:
      • 使用 SQL INSERT INTO t1 values('2024-03-31 02:59:59',..) 插入 2024-03-31 02:00:00 到 2024-03-31 02:59:59 的数据会被自动调整为 -1000(在 TDengine 中属于未定义行为,当前该值与数据库精度 precision 有关,毫秒数据库为 -1000,微秒数据库为 -1000000,纳秒数据库为 -1000000000),因为那一时刻在本地时间中不存在;
  • 在夏令时结束(柏林时间 10 月 27 日 03:00)时,时间从 03:00 跳到 02:00 (往前跳一小时)。
    • 浅蓝色表示时钟跳变前一小时的时间戳;
    • 深蓝色表示时钟跳变后一小时内的时间戳,其无时区的当地时间与上一小时一致。
    • 紫色表示时钟跳变一小时后的时间戳;
  • 当地时间变化:可见,由于夏令时的调整而导致了当地时间的变化,可能导致某些时间段出现重复或缺失。
  • UTC时间不变:UTC 时间保持不变,确保了时间的一致性和顺序性。
  • RFC3339:RFC3339 格式时间显示了时间偏移量的变化,在夏令时开始后变为 +02:00,结束后变为 +01:00 。
  • 条件查询:
    • 夏令时开始时,跳过的时间([03-31 02:00:00,03-31 03:00:00))不存在,所以在使用该时间进行查询时,行为不确定:SELECT ts FROM t1 WHERE ts BETWEEN '2024-03-31 02:00:00' AND '2024-03-31 02:59:59' (不存在的本地时间戳被转换为 -1000):
      taos> SELECT ts FROM t1 WHERE ts BETWEEN '2024-03-31 02:00:00' AND  '2024-03-31 02:59:59';
         ts       |
      =================
      -1000 |
      Query OK, 1 row(s) in set (0.003635s)
      
      当不存在的时间戳与存在的时间戳共同使用时,其结果同样不符合预期,以下为起始本地时间不存在:

taos> SELECT ts, to_iso8601(ts,'Z') FROM t1 WHERE ts BETWEEN '2024-03-31 02:00:00' AND '2024-03-31 03:59:59'; ts | to_iso8601(ts,'Z') |================================================== -1000 | 1969-12-31T23:59:59.000Z | 1711843200000 | 2024-03-31T00:00:00.000Z | 1711846799000 | 2024-03-31T00:59:59.000Z | 1711846800000 | 2024-03-31T01:00:00.000Z | 1711846801000 | 2024-03-31T01:00:01.000Z | Query OK, 5 row(s) in set (0.003339s)

以下语句中第一个 SQL 查询截止时间不存在,第二个截止时间存在,第一个 SQL 查询结果不符合预期:

 taos> SELECT ts, to_iso8601(ts,'Z') FROM t1 WHERE ts BETWEEN '2024-03-31 01:00:00' AND  '2024-03-31 02:00:00';
Query OK, 0 row(s) in set (0.000930s)

taos> SELECT ts, to_iso8601(ts,'Z') FROM t1 WHERE ts BETWEEN '2024-03-31 01:00:00' AND  '2024-03-31 01:59:59';
       ts       |       to_iso8601(ts,'Z')       |
==================================================
 1711843200000 | 2024-03-31T00:00:00.000Z       |
 1711846799000 | 2024-03-31T00:59:59.000Z       |
Query OK, 2 row(s) in set (0.001227s)
  • 夏令时结束时,跳变的时间([10-27 02:00:00,10-27 03:00:00) 不包含 10-27 03:00:00)重复了两次,TDengine 在使用该区间内的时间戳进行查询时,也属于未定义行为。
    • 查询 [2024-10-27 02:00:00, 2024-10-27 03:00:00] 之间的数据结果,包含了两次重复的时间戳和 2024-10-27 03:00:00 这个时间点的数据:
taos> SELECT ts, to_iso8601(ts,'Z'), TO_CHAR(ts, 'YYYY-MM-DD HH:mi:ss') FROM t1 WHERE ts BETWEEN '2024-10-27 02:00:00' AND  '2024-10-27 03:00:00';
       ts       |       to_iso8601(ts,'Z')       | to_char(ts, 'YYYY-MM-DD HH:mi:ss') |
=======================================================================================
 1729987200000 | 2024-10-27T00:00:00.000Z       | 2024-10-27 02:00:00                |
 1729990799000 | 2024-10-27T00:59:59.000Z       | 2024-10-27 02:59:59                |
 1729990800000 | 2024-10-27T01:00:00.000Z       | 2024-10-27 02:00:00                |
 1729994399000 | 2024-10-27T01:59:59.000Z       | 2024-10-27 02:59:59                |
 1729994400000 | 2024-10-27T02:00:00.000Z       | 2024-10-27 03:00:00                |
Query OK, 5 row(s) in set (0.001370s)
  • 但以下查询 [2024-10-27 02:00:00.000,2024-10-27 02:57:34.999] 区间只能查询到第一个2024-10-27 02:00:00 时间点的数据:
taos> SELECT ts, to_iso8601(ts,'Z'), TO_CHAR(ts, 'YYYY-MM-DD HH:mi:ss') FROM t1 WHERE ts >= '2024-10-27 02:00:00' AND ts <= '2024-10-27 02:57:00.999';
       ts       |       to_iso8601(ts,'Z')       | to_char(ts, 'YYYY-MM-DD HH:mi:ss') |
=======================================================================================
 1729987200000 | 2024-10-27T00:00:00.000Z       | 2024-10-27 02:00:00                |
Query OK, 1 row(s) in set (0.004480s)
  • 以下查询 [2024-10-27 02:00:01,2024-10-27 02:57:35] 却能查到 3 条数据(包含一条 02:59:59 的当地时间数据):
taos> SELECT ts, to_iso8601(ts,'Z'), TO_CHAR(ts, 'YYYY-MM-DD HH:mi:ss') FROM t1 WHERE ts >= '2024-10-27 02:00:00' AND ts <= '2024-10-27 02:57:35';;
           ts            |       to_iso8601(ts,'Z')       | to_char(ts, 'YYYY-MM-DD HH:mi:ss') |
================================================================================================
 2024-10-27 02:00:00.000 | 2024-10-27T00:00:00.000Z       | 2024-10-27 02:00:00                |
 2024-10-27 02:59:59.000 | 2024-10-27T00:59:59.000Z       | 2024-10-27 02:59:59                |
 2024-10-27 02:00:00.000 | 2024-10-27T01:00:00.000Z       | 2024-10-27 02:00:00                |
Query OK, 3 row(s) in set (0.004428s)

3. 总结与建议

3.1 总结
仅针对使用当地时间带来的影响作说明,使用 UNIX 时间戳或 RFC3339 无影响。

  • 写入:
    • 无法写入夏令时跳变时不存在的时间数据。
    • 写入夏令时跳变时重复的时间是未定义行为。
  • 查询:
    • 查询条件指定夏令时开始时跳变的时间,其查询结果为未定义行为。
    • 查询条件指定夏令时结束时重复的时间,其查询结果为未定义行为。
  • 显示:
    • 带时区显示不受影响。
    • 显示当地时间是准确的,但夏令时结束时重复的时间会无法区分。
    • 用户应谨慎使用不带时区的时间进行展示和应用。

3.2 建议
为避免夏令时给查询和写入造成不必要的影响,在 TDengine 中,建议使用明确的时间偏移量进行写入和查询。

  • 使用 UNIX 时间戳:使用 UNIX 时间戳可避免时区问题。
    QQ截图20250321151604.jpg
taos> insert into t1 values(1711846799000, 1)(1711846800000, 2);
Insert OK, 2 row(s) affected (0.001434s)

taos> select * from t1 where ts between 1711846799000 and 1711846800000;
       ts       |     v1      |
===============================
 1711846799000 |           1 |
 1711846800000 |           2 |
Query OK, 2 row(s) in set (0.003503s)
  • 使用 RFC3339 时间格式:带时区偏移量的 RFC3339 时间格式可以有效避免夏令时的不确定性。
    QQ截图20250321151707.jpg
taos> insert into t1 values ('2024-10-27T02:00:00.000+02:00', 1)
                            ('2024-10-27T02:59:59.000+02:00', 2)
                            ('2024-10-27T02:00:00.000+01:00', 3)
                            ('2024-10-27T02:59:59.000+01:00', 4);
Insert OK, 4 row(s) affected (0.001514s)

taos> SELECT *,
             to_iso8601(ts,'Z'),
             to_char(ts, 'YYYY-MM-DD HH:mi:ss') FROM t1
      WHERE ts >= '2024-10-27T02:00:00.000+02:00'
        AND ts <= '2024-10-27T02:59:59.000+01:00';
       ts      |     v1  |       to_iso8601(ts,'Z')       | to_char(ts, 'YYYY-MM-DD HH:mi:ss') |
=====================================================================================================
 1729987200000 |       1 | 2024-10-27T00:00:00.000Z       | 2024-10-27 02:00:00                |
 1729990799000 |       2 | 2024-10-27T00:59:59.000Z       | 2024-10-27 02:59:59                |
 1729990800000 |       3 | 2024-10-27T01:00:00.000Z       | 2024-10-27 02:00:00                |
 1729994399000 |       4 | 2024-10-27T01:59:59.000Z       | 2024-10-27 02:59:59                |
Query OK, 4 row(s) in set (0.004275s)

taos> SELECT *,
             to_iso8601(ts,'Z'),
             to_char(ts, 'YYYY-MM-DD HH:mi:ss') FROM t1
      WHERE ts >= '2024-10-27T02:00:00.000+02:00'
        AND ts <= '2024-10-27T02:59:59.000+02:00';
       ts      |     v1  |       to_iso8601(ts,'Z')       | to_char(ts, 'YYYY-MM-DD HH:mi:ss') |
=====================================================================================================
 1729987200000 |       1 | 2024-10-27T00:00:00.000Z       | 2024-10-27 02:00:00                |
 1729990799000 |       2 | 2024-10-27T00:59:59.000Z       | 2024-10-27 02:59:59                |
Query OK, 2 row(s) in set (0.004275s)
  • 查询时注意时区设定:在查询和显示时,如果需要本地时间,务必考虑夏令时的影响。
    • taosAdapter:使用 REST API 时,支持设置 IANA 时区,结果使用 RFC3339 格式返回。
$ curl -uroot:taosdata 'localhost:6041/rest/sql?tz=Europe/Berlin'\
  -d "select ts from tz1.t1"
{"code":0,"column_meta":[["ts","TIMESTAMP",8]],"data":[["1970-01-01T00:59:59.000+01:00"],["2024-03-31T01:00:00.000+01:00"],["2024-03-31T01:59:59.000+01:00"],["2024-03-31T03:00:00.000+02:00"],["2024-03-31T03:00:01.000+02:00"],["2024-10-27T02:00:00.000+02:00"],["2024-10-27T02:59:59.000+02:00"],["2024-10-27T02:00:00.000+01:00"],["2024-10-27T02:59:59.000+01:00"],["2024-10-27T03:00:00.000+01:00"]],"rows":10}
目录
相关文章
|
5月前
|
监控 安全 Linux
Linux系统提权之计划任务(Cron Jobs)提权
在Linux系统中,计划任务(Cron Jobs)常用于定时执行脚本或命令。若配置不当,攻击者可利用其提权至root权限。常见漏洞包括可写的Cron脚本、目录、通配符注入及PATH变量劫持。攻击者通过修改脚本、创建恶意任务或注入命令实现提权。系统管理员应遵循最小权限原则、使用绝对路径、避免通配符、设置安全PATH并定期审计,以防范此类攻击。
1171 1
|
6月前
|
人工智能 自然语言处理 安全
如何让 AI 工具更懂你,更听话?
你是否也曾被AI“气到吐血”?明明说的是A,AI却给了B?别沮丧,2025年的AI也需要“正确沟通”。本文教你五大提示技巧:动态提示、多模态输入、Few-shot示例、任务分解与安全边界,让AI从“人工智障”变身“贴心助手”。学会“说AI的语言”,释放创造力,提升效率,开启智能生活新时代!
1640 0
|
11月前
|
存储 运维 监控
百万指标,秒级查询,零宕机——时序数据库 TDengine 在 AIOps 中的硬核实战
本篇文章详细讲述了七云团队在运维平台中如何利用 TDengine 解决海量时序数据存储与查询的实际业务需求。内容涵盖了从数据库选型、方案落地到业务挑战及解决办法的完整过程,特别是分享了升级 TDengine 3.x 时的实战经验,给到有需要的小伙伴参考阅读。
481 1
|
11月前
|
物联网 测试技术 API
时序数据库 InfluxDB 3.0 版本性能实测报告:写入吞吐量提升效果验证
TSBS 测试表明,对于少于 100 万台设备的数据集,InfluxDB OSS 3.0 的数据写入速度实际上比 InfluxDB OSS 1.8 更慢。 对于 100 万台及以上设备的数据集,InfluxDB OSS 3.0 的数据写入性能才开始超过 InfluxDB OSS 1.8。 InfluxDB OSS 3.0 的数据写入接口与 InfluxDB 1.8 并不兼容,用户无法顺利迁移。
1017 7
|
12月前
|
存储 数据挖掘 数据处理
2600 万表流计算分析如何做到? 时序数据库 TDengine 助力数百家超市智能化转型
在生鲜超市的高效运营中,实时数据分析至关重要。万象云鼎的“云鲜生”通过智能秤+网关+软件系统的组合,实现了销售数据的精准管理与优化。而在数据处理方面,TDengine 的流计算能力成为了这一方案的核心支撑。本文详细分享了“云鲜生”如何利用 TDengine 高效存储和分析海量销售数据,在优化超市运营、提升用户体验的同时,解决高基数分组、高并发查询等技术挑战。
315 1
|
11月前
|
存储 NoSQL MongoDB
从 MongoDB 到 时序数据库 TDengine,沃太能源实现 18 倍写入性能提升
沃太能源是国内领先储能设备生产厂商,数十万储能终端遍布世界各地。此前使用 MongoDB 存储时序数据,但随着设备测点增加,MongoDB 在存储效率、写入性能、查询性能等方面暴露出短板。经过对比,沃太能源选择了专业时序数据库 TDengine,生产效能显著提升:整体上,数据压缩率超 10 倍、写入性能提升 18 倍,查询在特定场景上也实现了数倍的提升。同时减少了技术架构复杂度,实现了零代码数据接入。本文将对 TDengine 在沃太能源的应用情况进行详解。
533 0
|
数据挖掘 数据库
GEE——降水数据分析(半天)图表分析含(IANA(IANA Time Zone Database) 时区名称的定义)
GEE——降水数据分析(半天)图表分析含(IANA(IANA Time Zone Database) 时区名称的定义)
401 1
|
iOS开发 索引 MacOS
python文件处理-Excel自动处理(使用 openpyxl)
python文件处理-Excel自动处理(使用 openpyxl)
716 1
python文件处理-Excel自动处理(使用 openpyxl)
|
SQL 监控 NoSQL
乐观锁在分布式系统中如何实现
乐观锁在分布式系统中如何实现
246 0
|
存储 边缘计算 物联网
阿里云物联网平台:推动万物互联的智能化解决方案
随着物联网技术的快速发展,阿里云物联网平台为企业提供了一体化的解决方案,包括设备接入、数据管理和智能应用等核心功能。平台支持海量设备接入、实时数据采集与存储、边缘计算,并具备大规模设备管理、高安全性和开放生态等优势。广泛应用于智能制造、智慧城市和智能家居等领域,助力企业实现数字化转型。
1612 5

热门文章

最新文章