摘要:我们深入了解一下通用时区数据库的组织规则,以及时区和夏令时到底是如何维护的,GaussDB(DWS)中又是如何使用的。

本文分享自华为云社区《你应该知道的时区知识之通用时区数据库》,原文作者:leapdb 。

1. 背景介绍

接下来,我们深入了解一下通用时区数据库的组织规则,以及时区和夏令时到底是如何维护的,GaussDB(DWS)中又是如何使用的。

2. 通用时区数据库介绍

各地的时区和夏令时规则由各自的政府独立管理,他们经常在有限的通知下进行变更。而且他们历史数据和未来计划也只是断断续续的记录下来。通用时区数据库试图组织和整理这一领域相关的数据。

时区数据库,通常称为 tz, tzdata 或 zoneinfo,是一组包含大量代码和数据用来表示全球许多有代表性的地点的本地时间的历史信息,他会根据各个政体对时区边界和夏令时规则的改变而不定期的更新。数据库中每一个条目都代表这自1970年以来被广泛认可的民用时钟的时区信息。该数据库被很多项目引用,比如:the GNU C Library (used in GNU/Linux), Android, FreeBSD, NetBSD, OpenBSD, Chromium OS, Cygwin, MariaDB, MINIX, MySQL, webOS, AIX, BlackBerry 10, iOS, macOS, Microsoft Windows, OpenVMS, Oracle Database, 和 Oracle Solaris。GaussDB(DWS)同其它广泛使用的软件产品一样,也是采用了IANA维护的通用时区数据。

该数据库由David Olson创立,由Paul Eggert进行编辑和维护。因而有些地方也将其称作Olson数据库。它的显著特色是由Paul Eggert设计的一套通用时区命名规则,每个时区按照“区域/位置”格式,得到一个独有的名称,例如“America/New_York”。英文地名中的空格用下划线“_”代替,连词符“-”只在英文地名本身包含时使用。时区数据库目前普遍有两个叫法 Olson时区数据库或IANA时区数据库。

奥尔森(Olson)的数据有所变化,部分原因是奥尔森(AD Olson)即将退休,部分原因是针对维护者的版权侵权提起了诉讼(现已撤销)。2011年10月14日,国际互联网名称与名称分配机构(IANA)接管了时区数据库的维护工作。它会定期进行更新以反映各政治实体对时区边界、UTC 差值和夏令时规则的更改。对tz的更新遵循BCP 175流程进行管理。

经常有一些国家变更时区规则,IANA每年都会发布最新的时区数据和解析源码库。IANA提供三种方法对时区数据库的访问:

  1. https://www.iana.org/time-zones
  2. ftp://ftp.iana.org/tz/
  3. rsync://http://rsync.iana.org/tz/

时区数据库中包含各大洲原始的时区定义的文本文件和解析这些文本文件的代码文件。

2.1 通用时区数据库源码

相关资料:

源码托管地址:https://github.com/eggert/tz
时区数据库的介绍 https://data.iana.org/time-zones/tz-link.html
时区数据库原理及使用 https://data.iana.org/time-zones/theory.html

下载方法:

mkdir tzdb
cd tzdb
wget https://www.iana.org/time-zones/repository/tzcode-latest.tar.gz #下载最新解析时区文本定义的代码文件
wget https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz #下载最新时区定义的文本文件
gzip -dc tzcode-latest.tar.gz | tar -xf -
gzip -dc tzdata-latest.tar.gz | tar -xf -
#或者下载代码+数据的完整压缩包
wget https://www.iana.org/time-zones/repository/tzdb-latest.tar.lz #下载代码+数据的完整时区数据库
lzip -dc tzdb-latest.tar.lz | tar -xf -

代码结构:

时区数据库中包含的时区定义文件(文本文件):
africa antarctica asia australasia europe northamerica southamerica

时区数据库中包含的代码文件(用于解析时区定义文件)
asctime.c date.c difftime.c localtime.c strftime.c zdump.c zic.c

安装目录结构:

编译完成后的安装目录结构如下

cd tzdb-2020a
make TOPDIR=$HOME/tzdir install

leap@gaussdb:~> ./sbin/tree -L 3 tzdir/ | more
tzdir/
├── etc
│   └── localtime    #本地时区文件
└── usr
    ├── bin
    │   ├── tzselect #设置时区的工具
    │   └── zdump    #以文本展示某个时区变化历史的工具
    ├── lib
    │   └── libtz.a  #解析时区文件的静态库文件
    ├── sbin
    │   └── zic      #时区编译器,可将时区定义的文本文件编译成二进制时区文件
    └── share
        ├── man
        ├── zoneinfo #编译好的各个时区文件
        ├── zoneinfo-leaps
        └── zoneinfo-posix -> zoneinfo

#IANA的时间数据库被the GNU C Library (used in GNU/Linux)采用,因此install后的目录也按linux的系统目录来组织。

代码中的zic.c是一个将时区定义的原始文本文件解析成二进制时区文件的工具。如果一个软件产品需要从IANA获取最新时区数据的话,需要同时获取源码和数据,并生成zic解析器将时区数据生成时区文件给软件产品使用。时区文件的解析代码在localtime.c中

2.2 时区原始数据的规则及维护方法

时区数据库的RAW文件,是按照一定的规则组织的文本文件。样例如下:
image.png

该规则设计的很科学,能记录时区及夏令时变更的历史信息。使用该通用时区库能自动的转换历史时间,也因此该库被广泛采用。

这里介绍一下莫斯科的时区定义

Zone  NAME              STDOFF  RULES  FORMAT   [UNTIL]
Zone Europe/Moscow       2:30:17 -      LMT     1880
                         2:30:17 -      MMT     1916 Jul  3 # Moscow Mean Time
                         2:31:19 Russia %s      1919 Jul  1  0:00u
                         3:00   Russia  %s      1921 Oct
                         3:00   Russia  MSK/MSD 1922 Oct
                         2:00   -       EET     1930 Jun 21
                         3:00   Russia  MSK/MSD 1991 Mar 31  2:00s
                         2:00   Russia  EE%sT   1992 Jan 19  2:00s
                         3:00   Russia  MSK/MSD 2011 Mar 27  2:00s
                         4:00   -       MSK     2014 Oct 26  2:00s
                         3:00   -       MSK
  • 1992年1月19日凌晨2点到2011年03月27日凌晨2点,采用东三区
  • 2011年3月27日凌晨2点到2014年10月26日凌晨2点,采用东四区
  • 2014年10月26日凌晨2点以后采用东三区

如何阅读时区数据库的RAW文件 https://data.iana.org/time-zo...
VSCode的Zoneinfo插件可以通过语法高亮的方式查看时区数据的RAW文件

时区数据库的基本功能是提供 时区数据原始文件 和一个将 时区数据原始文件 转换成 时区文件 的一个编译器:zic。

可以通过 man zic 了解时区文件编译器(将时区定义文本文件编译成二进制时区数据文件)的使用

3. 通用时区数据库使用

每个时区名的具体定义都存在于时区文件中,比如:/etc/localtime。
操作系统支持的时区文件存储在/usr/share/zoneinfo/目录下。
我们数据库支持的时区文件存储在share/timezone目录下。

时区文件有统一约定的格式要求(来自IANA维护的时区数据库),可以使用info tzfile查看。时区文件固定开头结构:

struct tzhead
{
    char        tzh_magic[4];    /* TZ_MAGIC,固定在开头的特征字符"TZif"来标识时区文件 */
    char        tzh_version[1]; /* '\0' or '2' or '3' as of 2013,版本信息 */
    char        tzh_reserved[15];    /* reserved; must be zero */
    char        tzh_ttisutcnt[4];    /* coded number of trans. time flags 保存在文件中的UTC/local指示器数目*/
    char        tzh_ttisstdcnt[4];    /* coded number of trans. time flags 保存在文件中的standard/wall指示器数目*/
    char        tzh_leapcnt[4]; /* coded number of leap seconds 其值保存在文件中的leap second的数目*/
    char        tzh_timecnt[4]; /* coded number of transition times 其值保存在文件中的"变化时间"数目*/
    char        tzh_typecnt[4]; /* coded number of local time types 其值保存在文件中的"本地时间类型"数目(非零!)*/
    char        tzh_charcnt[4]; /* coded number of abbr. chars 保存在文件中的"时区简写符"数目*/
};

跟在上面这些头部后的是tzh_timecnt 个"标准"字节顺序的四字节 long 类型值, 以升序排序. 每个值均作为一个变化时间(就像 time(2) 的返回), 系统依赖这些值来计算本地时间变化.

而在此之后的是 tzh_timecnt 个 unsigned char 类型的一字节值, 这些值指出了文件中描述的多种"本地时间"类型中哪一个与具有相同索引的变化时间相关. 这些值可作为 ttinfo 结构数组的索引.

而 ttinfo 结构在文件中随后就有定义, 描述如下:

struct ttinfo {
long tt_gmtoff;
int tt_isdst;
unsigned int tt_abbrind;
};
结构包括一个"标准"字节顺序的四字节 long 类型值 tt_gmtoff,
以及一个一字节的 tt_isdst
和一个一字节的 tt_abbrind.
在每个结构里, tt_gmtoff 给出了要被加到UTC的时间, 以秒为单位,
tt_isdst 表明 tm_isdst 是否可通过 localtime (3) 设置,
而 tt_abbrind 可作为时区简写符的数组索引, 该数组在文件中跟在 ttinfo 结构后面.

这样就有 tzh_leapcnt 个标准字节顺序的四字节对, 每个四字节对的第一个值给出一个leap second发生的时间, 就如 time(2) 的返回; 每个四字节对的第二个值给出给定时间之后所实现的总的 leap second数. 四字节对按时间的升序排序.

同样有 tzh_ttisstdcnt 个standard/wall指示器, 每个保存了一个一字节值; 这些指示器指出了变化时间(与本地时间类型相关)是否被说明为standard time或者wall clock time, 以及当一个时区文件被用于处理POSIX 格式时区环境变量时是否使用变化时间.

最后, 有 tzh_ttisgmtcnt 个UTC/local指示器, 每个保存了一个一字节值; 这些指示器指出了变化时间(与本地时间类型相关)是否被说明为UTC 或者local time, 以及当一个时区文件被用于处理 POSIX格式时区环境变量时是否使用变化时间.

如果 tzh_timecnt 等于零或者时间参数比文件记录的第一个变化时间小的话, Localtime 就使用文件中的第一个标准时间 ttinfo, 或者在没有标准时间结构是就直接使用第一个 ttinfo 结构.

可以通过 man zdump dump二进制时区数据文件,了解如何查看一个时区的变更历史

./zdump -V Asia/Chongqing | more
Asia/Chongqing  Sat Dec 31 16:53:39 1927 UT = Sat Dec 31 23:59:59 1927 LMT isdst=0 gmtoff=25580
Asia/Chongqing  Sat Dec 31 16:53:40 1927 UT = Sat Dec 31 23:53:40 1927 LONT isdst=0 gmtoff=25200
Asia/Chongqing  Wed Apr 30 16:59:59 1980 UT = Wed Apr 30 23:59:59 1980 LONT isdst=0 gmtoff=25200
Asia/Chongqing  Wed Apr 30 17:00:00 1980 UT = Thu May  1 01:00:00 1980 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat May  3 15:59:59 1986 UT = Sat May  3 23:59:59 1986 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat May  3 16:00:00 1986 UT = Sun May  4 01:00:00 1986 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 13 14:59:59 1986 UT = Sat Sep 13 23:59:59 1986 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 13 15:00:00 1986 UT = Sat Sep 13 23:00:00 1986 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 11 15:59:59 1987 UT = Sat Apr 11 23:59:59 1987 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 11 16:00:00 1987 UT = Sun Apr 12 01:00:00 1987 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 12 14:59:59 1987 UT = Sat Sep 12 23:59:59 1987 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 12 15:00:00 1987 UT = Sat Sep 12 23:00:00 1987 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr  9 15:59:59 1988 UT = Sat Apr  9 23:59:59 1988 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr  9 16:00:00 1988 UT = Sun Apr 10 01:00:00 1988 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 10 14:59:59 1988 UT = Sat Sep 10 23:59:59 1988 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 10 15:00:00 1988 UT = Sat Sep 10 23:00:00 1988 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 15 15:59:59 1989 UT = Sat Apr 15 23:59:59 1989 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 15 16:00:00 1989 UT = Sun Apr 16 01:00:00 1989 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 16 14:59:59 1989 UT = Sat Sep 16 23:59:59 1989 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 16 15:00:00 1989 UT = Sat Sep 16 23:00:00 1989 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 14 15:59:59 1990 UT = Sat Apr 14 23:59:59 1990 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 14 16:00:00 1990 UT = Sun Apr 15 01:00:00 1990 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 15 14:59:59 1990 UT = Sat Sep 15 23:59:59 1990 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 15 15:00:00 1990 UT = Sat Sep 15 23:00:00 1990 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 13 15:59:59 1991 UT = Sat Apr 13 23:59:59 1991 CST isdst=0 gmtoff=28800
Asia/Chongqing  Sat Apr 13 16:00:00 1991 UT = Sun Apr 14 01:00:00 1991 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 14 14:59:59 1991 UT = Sat Sep 14 23:59:59 1991 CDT isdst=1 gmtoff=32400
Asia/Chongqing  Sat Sep 14 15:00:00 1991 UT = Sat Sep 14 23:00:00 1991 CST isdst=0 gmtoff=28800

因为我国从1991年开始取消了夏令时,所以就不再变化。

4. GaussDB(DWS)中如何使用时区数据

为了国内用户的使用方便,GaussDB(DWS)根据IANA的定义的语法规则,在内部帮助用户定义了 Asia/Beijing 时区,其定义与 PRC 时区定义一致。位于按装目录下的 timezone/Asia/Beijing

查看Asia/Beijing的具体定义:

./zdump -V Asia/Beijing
Asia/Beijing  Sat May  3 15:59:59 1986 UT = Sat May  3 23:59:59 1986 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat May  3 16:00:00 1986 UT = Sun May  4 01:00:00 1986 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 13 14:59:59 1986 UT = Sat Sep 13 23:59:59 1986 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 13 15:00:00 1986 UT = Sat Sep 13 23:00:00 1986 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 11 15:59:59 1987 UT = Sat Apr 11 23:59:59 1987 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 11 16:00:00 1987 UT = Sun Apr 12 01:00:00 1987 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 12 14:59:59 1987 UT = Sat Sep 12 23:59:59 1987 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 12 15:00:00 1987 UT = Sat Sep 12 23:00:00 1987 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr  9 15:59:59 1988 UT = Sat Apr  9 23:59:59 1988 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr  9 16:00:00 1988 UT = Sun Apr 10 01:00:00 1988 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 10 14:59:59 1988 UT = Sat Sep 10 23:59:59 1988 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 10 15:00:00 1988 UT = Sat Sep 10 23:00:00 1988 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 15 15:59:59 1989 UT = Sat Apr 15 23:59:59 1989 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 15 16:00:00 1989 UT = Sun Apr 16 01:00:00 1989 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 16 14:59:59 1989 UT = Sat Sep 16 23:59:59 1989 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 16 15:00:00 1989 UT = Sat Sep 16 23:00:00 1989 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 14 15:59:59 1990 UT = Sat Apr 14 23:59:59 1990 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 14 16:00:00 1990 UT = Sun Apr 15 01:00:00 1990 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 15 14:59:59 1990 UT = Sat Sep 15 23:59:59 1990 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 15 15:00:00 1990 UT = Sat Sep 15 23:00:00 1990 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 13 15:59:59 1991 UT = Sat Apr 13 23:59:59 1991 CST isdst=0 gmtoff=28800
Asia/Beijing  Sat Apr 13 16:00:00 1991 UT = Sun Apr 14 01:00:00 1991 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 14 14:59:59 1991 UT = Sat Sep 14 23:59:59 1991 CDT isdst=1 gmtoff=32400
Asia/Beijing  Sat Sep 14 15:00:00 1991 UT = Sat Sep 14 23:00:00 1991 CST isdst=0 gmtoff=28800

现在GaussDB(DWS)中所有关于时区的知识都介绍完了,希望从原理和维护角度的深入学习彻底扫除时区使用的疑惑,如果有相关问题欢迎在论坛中提问。

5. 总结

综上,GaussDB(DWS)作为一款面向全球用户的高性能分析型数据库产品,对时区数据的支持也是符合工业界标准规范的。

想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技~

点击关注,第一时间了解华为云新鲜技术~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量