漫谈时间和时区
一、前言
最近在学习关于时间、时区的知识,参考了网上的一些资料,主要来源是wiki和Linux Manual,现在把阅读过程中的一些心得记录下来。在本文中,简略描述了下列相关内容:
- 时间度量
- 计时系统
- GMT
- UT
- TAI
- UTC
- Unix Time
- Linux time zone setting
- Daylight saving time
二、时间
时间是一个很有趣的东西。远古人们基于太阳运转的昼夜交替,称之为日;观察月亮的盈缺变换,在两次月圆之间的周期定为月;再通过测量太阳南北周期变化,年的概念。
日月年,是最早出现的时间度量,也是历法的基本元素,其中又以日
最为基本。历法是人类社会指导耕牧采集的重要依据,为了完善历法,要求天文学不断进步以对时间做更精确的测量。所以,早期的时间观念,是天文学的产物。
一日
是很长的,生活中用起来还是不方便,还需要进一步细分。中华地区采取的是时辰
加刻
的记录方式,把一日划分为12个时辰,以12地支记录。1个时辰还是太长了,所以又出现了刻。一天的总刻数,出现过100、96、108、120等多种度量,直到清朝时最终确定为96刻,并恰好和西方的"Quarter"对应,1个时辰=8刻=2小时。 在后面的发展中,又对小时进行了细化,出现了分钟、秒,乃至毫秒和微秒等。
以上是一本正经的胡说八道。
三、计时系统, Timing System
1. 本地时间
前面说道的日月年、时分秒都是一个时段,要很好的表示时间,方便交流沟通,必须有一个完善的计时系统。计时系统最重要的就是参考点,最明显的一个参考点,就是正午。正午,也就是一天之中太阳位于最高点的时刻
。中国古代称之为午时
,英语称之为midday
,也就是一天的正中间。以正午为参考点,往前后各推12个小时,作为一天时间范围的度量。
这种基于本地太阳升落制定的时间度量,称为本地时, local time
。本地时很有用,它符合人类社会漫长发展过程中形成的时间观念,便于安排作息:在中国,辰时劳作,酉时归歇;在西方,9点上班,5点下班。
本地时对当地的意义重大,对其他地区可能就毫无意义。地球是圆的,不同地区正午到达时刻相差可以很大。以上帝视角观察地球,会发现:你在正午的时候,我可能还要过2个小时才到正午,他可能是2个小时前刚刚过了正午。所以单纯的正午
这个词没有任何意义,还必须说明:xxx地区的正午
。
2. 标准时间
早期受限于活动范围和运动速度,本地时的特点并不会对实际生活产生影响。18世纪随着铁路运输的发展,人类开始有能力长途快速旅行的时候,确立一个合理的计时方法就很有必要,这样才能指定沿铁路线统一的列车时刻表。
最直观的方法,就是选取某个地区的本地时间作为标准时间(uniform standard time),大家都用它。比如新中国就统一使用北京时间作为参考。
标准时间最大的作用:确立了一个起点
,可以计算当前时刻距离该起点
过去了多长时间段
。时间是单调流逝的,从起点开始算起,流逝的天数总是不断增加,各地都完全一致。
四. 计时系统的变迁
十九世纪,全球大规模交流发展起来之前,特别是电信系统诞生之前,远距离瞬时通信能力缺乏,一些比较强大的国家大都有一套自己的计时系统独立运作。其中一些国家的计时系统随着国力增强,全球影响力增加,逐步输出到世界其他地域,并最终成为全球范围的标准计时系统,
1. GMT Timing System
英国选取了格林威治天文台的一条子午线作为基点,以格林威治区域平均太阳时作为航海运输的参照标准,这就是Greenwich Mean Time(GMT)
。在天文台,有一个实实在在的时钟在运转,如下图。
1884年的Washington Meridian Conference上,GMT被选取作为国际计时标准。全球划分为24个时区
,每个时区都以中心点的平均太阳时作为标准时钟时间(wall-clock time),相邻时区的时钟时间相差1小时。
时区的作用:根据参考原点和当前时区,调整
挂钟时间
的显示。参考原点的时间是单调递增的,但某个具体地点的时区可以人为规定,挂钟时间
也可人为调整。
2. Universal Time
1935年,国际天文联会建议使用Universal Time 替代Greenwich Mean Time,因为后者有多个版本:GMT民用时,从午夜开始作为0点,GMT天文时,从正午开始作为0点。
1955年,国际天文联会基于Universal Time又定义了UT1和UT2三个系统,以消除极移和自转速率季节性变化的影响,原来的UT系统被称为UT0。
- UT0系统即原始GMT民用时系统,由天文观测直接测定的世界时,未经任何改正,曾经得到过广泛应用
- UT1系统是在UT0的基础上加入了极移改正 Δλ,修正地轴摆动的影响。
- UT2系统是在UT1基础上加入了地球自转速率的季节性改正 ΔT
它们之间的关系可以表示为:
- UT1 = UT0 + Δλ
- UT2 = UT1 + ΔT
目前应用最广泛的是UT1。UTx的基本单位是天,1天等分为24小时,每小时等分为60分,1分钟等分为60秒。所以,UTx里面的秒
,并不是基本单位。
UTx系统1天=24小时=86400秒
3. TAI Timing System
1955年,铯原子钟问世。
1958年,制订了一套新的基于原子钟的计时系统: International Atomic Time system, 国际原子时系统。它以 "1958-01-01 00:00:00" 做为起始点和 UT2 同步,能提供非常精确的计时服务。
TAI的计时基元是秒,每天固定86400秒
,这里的秒是物理学定义的国际单位制SI second
:
铯133原子基态的两个超精细能阶间跃迁对应辐射的9,192,631,770个周期的持续时间
4. UTC Timing System
1960年,Coordinated Universal Time 被国际电信联会提出。
1972年,引入闰秒机制,形成了当前使用的UTC体系。简单的说,UTC是以TAI为基础的一种计时体系,它的计时单位是TAI second
,但时刻上又和UT1接近,不会超过1秒的差异,可以理解为Universal Time base on TAI, Coordinated with UT1
。这是当前普遍使用的计时系统。
UT0/UT1/UT2和UTC的比较:
- UTx是基于太阳时的计时体系,计时基元是
天
。时分秒是对天的切分。 - UTC是基于TAI的计时体系,计时基元是
TAI second
(base on TAI) - UTC通过闰秒机制保证和UT1的基本同步(Coordinated with UT1)
与此对应,UT的秒
是太阳日的1/86400
,二者并不完全一致。所以,当TAI按照精确步调往前走的时候,UT1随着地球的自转变慢在逐渐减速,到目前为止,TAI已经领先了37秒左右。
UTC和UT1同步,某一天可能会增加1 TAI second
,这就是闰秒(Leap second)。下面是TAI1999年时,给UTC增加1闰秒的例子,此时最后1分钟有61秒而不是60秒。
TAI (1999-01-01) | UTC (1998-12-31到 1999-01-01) |
1999-01-01T00:00:29.75 | 1998-12-31T23:59:58.75 |
1999-01-01T00:00:30.00 | 1998-12-31T23:59:59.00 |
1999-01-01T00:00:30.25 | 1998-12-31T23:59:59.25 |
1999-01-01T00:00:30.50 | 1998-12-31T23:59:59.50 |
1999-01-01T00:00:30.75 | 1998-12-31T23:59:59.75 |
1999-01-01T00:00:31.00 | 1998-12-31T23:59:60.00 |
1999-01-01T00:00:31.25 | 1998-12-31T23:59:60.25 |
1999-01-01T00:00:31.50 | 1998-12-31T23:59:60.50 |
1999-01-01T00:00:31.75 | 1998-12-31T23:59:60.75 |
1999-01-01T00:00:32.00 | 1999-01-01T00:00:00.00 |
1999-01-01T00:00:32.25 | 1999-01-01T00:00:00.25 |
1999-01-01T00:00:32.50 | 1999-01-01T00:00:00.50 |
1999-01-01T00:00:32.75 | 1999-01-01T00:00:00.75 |
1999-01-01T00:00:33.00 | 1999-01-01T00:00:01.00 |
1999-01-01T00:00:33.25 | 1999-01-01T00:00:01.25 |
5. 小结
年份 | 计时系统 | 基本单位 | 注释 |
1884 | GMT | 太阳日 | 1天=24小时=1440分=86400秒 |
1955 | UT1 | 太阳日 | 1天=24小时=1440分=86400秒 |
1958 | TAI | SI second | 1天=86400 SI seconds |
1972 | UTC | SI second | 平时 1天=86400 SI seconds, 闰秒发生时 1天=86401 SI seconds |
五、Unix time
Unix time是一个计时体系,度量从 UTC 时间 "1970-01-01 00:00:00 +0000" 开始流逝的秒数。但是,Unix time的数值不包含闰秒,所以它既不是对时间的线性度量,也不是UTC的一种合法表示。date +%s
可以查看Unix-like系统的当前Unix time。
因为Unix time是基于Unix epoch("1970-01-01 00:00:00 +0000"),所以又称为"epoch time"。
Unix Time一般表示为有符号十进制数值, "1970-01-01 00:00:00 +0000"之前的时刻,是负值,例如:
$ ./utc_time "UTC+0" -86400
-86400 1969-12-31 00:00:00 +0000
Unix time不包括闰秒,而UTC是有闰秒的,所以闰秒发生的时候,Unix time会发生数值回退
现象。下面是1999年插入闰秒时的情形:
TAI (1 January 1999) | UTC (31 December 1998 to 1 January 1999) |
Unix time |
1999-01-01T00:00:29.75 | 1998-12-31T23:59:58.75 | 915 148 798.75 |
1999-01-01T00:00:30.00 | 1998-12-31T23:59:59.00 | 915 148 799.00 |
1999-01-01T00:00:30.25 | 1998-12-31T23:59:59.25 | 915 148 799.25 |
1999-01-01T00:00:30.50 | 1998-12-31T23:59:59.50 | 915 148 799.50 |
1999-01-01T00:00:30.75 | 1998-12-31T23:59:59.75 | 915 148 799.75 |
1999-01-01T00:00:31.00 | 1998-12-31T23:59:60.00 | 915 148 800.00 |
1999-01-01T00:00:31.25 | 1998-12-31T23:59:60.25 | 915 148 800.25 |
1999-01-01T00:00:31.50 | 1998-12-31T23:59:60.50 | 915 148 800.50 |
1999-01-01T00:00:31.75 | 1998-12-31T23:59:60.75 | 915 148 800.75 |
1999-01-01T00:00:32.00 | 1999-01-01T00:00:00.00 | 915 148 800.00 |
1999-01-01T00:00:32.25 | 1999-01-01T00:00:00.25 | 915 148 800.25 |
1999-01-01T00:00:32.50 | 1999-01-01T00:00:00.50 | 915 148 800.50 |
1999-01-01T00:00:32.75 | 1999-01-01T00:00:00.75 | 915 148 800.75 |
1999-01-01T00:00:33.00 | 1999-01-01T00:00:01.00 | 915 148 801.00 |
1999-01-01T00:00:33.25 | 1999-01-01T00:00:01.25 | 915 148 801.25 |
time()函数返回的是整数部分,观察不到小数部分的回退。
六、Linux 时区设置
Linux 以TZ环境变量设定当前时区,通过在C/C++里面,通过setenv就可以设置,例如:
setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 1);
TZ可以识别3种格式,前两种都是直接指定时区信息,最后一种是基于时区数据库。
1. 无夏令时格式
std offset
参数列表:
- std 是标准时区名字,可以是3个字母以上的英文字母。
- offset 是一个时段数值,把它和本地时间相加,可以得到UTC时间。格式
[+|-]hh[:mm[:ss]]
。它的符号是当前时区的符号相反:- 当前时区为 GMT+hh:mm:ss,则 offset=-hh:mm:ss
- 当前时区为 GMT-hh:mm:ss,则 offset=+hh:mm:ss
下面是几个合法的时区字符串
- UTC+0 : 格林威治本地时间
- UTC-08 : 重庆时间
- ABCDE+5 : 美东时间
- TEST+24
- TEST+10:20:30
2. 夏令时格式
std offset1 dst [offset2],start[/time],end[/time]
参数列表:
- std : 是标准时区名字,可以是3个字母以上的英文字母
- offset1 : 时区偏移
- dst : 是实施夏令时的时区名字
- offset2 : 是夏令时生效时的偏移,默认情况下 offset2=offset1-1hour
- start/end : 本地时间何时进入和退出夏令,格式:
Mm.w.d
,第m个月的第w周的第d天。- d : 0(周日)-6
- w : 1-5, 1是第1周,5的意思是d=该月最后一周的时间(下面有例子)
- m : 1-12
- time : 本地时间进入/退出夏令时的时刻,默认 time=02:00:00
下面是一些合法的夏令时串:
1. `EST+5EDT,M3.2.0/2,M11.1.0/2`
美国纽约时间,从每年3月份的第2个周日开始,到当年11月份的第1个周日,从凌晨2点开始变更
2. `WGT3WGST,M3.5.0/-2,M10.5.0/-1`
3. 指明时区配置文件
:characters
characters是本地文件路径,例如 :
setenv("TZ", ":/usr/share/zoneinfo/US/Eastern", 1);
七、夏令时
夏时制,另译夏令时间(英语:Summer time),又称日光节约时制、日光节约时间(英语:Daylight saving time),是一种为节约能源而 人为规定地方时间 的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮较早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。
美国国会2005年通过的能源法案,自2007年起,夏时制开始日期为3月的第二个星期日,结束日期为11月的第一个星期日。美国夏时制实行与否,完全由各州各郡自己决定。
下面演示一个进入和退出夏时制的例子。
美国纽约时间2016年夏令时起止点 2016-03-13 01:59:59 (+1s) 开始 1457852399/1457852400 2016-11-06 01:59:59 (+1s) 结束 1478411999/1478412000 $ ./utc_time :/usr/share/zoneinfo/US/Eastern 1457852399 1457852399 2016-03-13 01:59:59 -0500 $ ./utc_time :/usr/share/zoneinfo/US/Eastern 1457852400 1457852400 2016-03-13 03:00:00 -0400 $ ./utc_time :/usr/share/zoneinfo/US/Eastern 1478411999 1478411999 2016-11-06 01:59:59 -0400 $ ./utc_time :/usr/share/zoneinfo/US/Eastern 1478412000 1478412000 2016-11-06 01:00:00 -0500 注意,如果这是一个普通挂钟的话,指向凌晨2点时,要把时针回拨1个小时。
代码片段:
setenv("TZ", argv[1], 1); time_t tt = (time_t)strtoll(argv[2], 0, 0); struct tm *ptm = localtime(&tt); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z", ptm);
参考资料
- https://en.wikipedia.org/wiki/Greenwich_Mean_Time
- https://en.wikipedia.org/wiki/Universal_Time
- https://en.wikipedia.org/wiki/International_Atomic_Time
- https://en.wikipedia.org/wiki/Coordinated_Universal_Time
- http://www.hko.gov.hk/gts/time/basicterms-UTandGMTc.htm
- https://www.timeanddate.com/time/time-zones-history.html
- https://en.wikipedia.org/wiki/Unix_time
- https://www.timeanddate.com/time/international-atomic-time.html
- https://zh.wikipedia.org/wiki/秒
- https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html