如何在计算机中表示时间?(上)
0、前言
笔者没有很深入地了解过各种时间标准。要精确的表示一个时间是真的挺复杂的,其中涉及到的知识太多,笔者做不到完全掌握,也没有必要。
本文尽量讲的通俗易懂,但定义可能就不精准,所以请以怀疑的眼光看待此文。如果有错误的地方,也恳请指出。
1、时间的定义
首先,回想一下,国际单位制中时间的基本单位是什么?答:秒。
那么,“秒”的定义是什么?一般来说,我们说1天对应24小时,1小时对应60分钟,1分钟对应60秒。而”天“指的就是地球自转一周的时间。
所以1秒就是1/(24*60*60)=1/86400天,就是地球自转一周时间的1/86,400。
但是,地球自转一周的时间是变化的(貌似地球自转在加快,自转一周的时间越来越短)。如果采用原来的定义,那么1秒和1秒的时间长度可能是不同的。这对于某些对时间要求很苛刻的工作来说是不行的。
原子时
目前国际上对秒的定义是:
秒,符号为s,国际单位制时间单位。其定义是,将铯-133原子不受扰动的基态超精细能级跃迁频率ΔνCs的值固定为9192631770赫兹,赫兹等于s−1。
看不懂吗?我也看不懂。为了方便起见,下文中就称它为”原子秒“吧。(”原子秒“这个称呼我不确定是否标准,但你get到意思就行)
当然,肯定没有绝对精准的东西,不过嘛:
1999年12月29日,NIST-F1 铯原子钟其不确定度为1.5×10−15,相当于2000万年不差一秒。
2020年10月27日,国际计量局(BIPM)报导,德国ptb-csf2铯原子钟,在1亿8千7百万年的时间内的误差不会超过1秒钟(1.7 × 10−16) 。法国syrte-fo2铯原子钟,在1亿4千4百万年的时间内的误差不会超过1秒钟(2.2 × 10−16)。 俄罗斯su-csfo2铯原子钟,在1亿4千4百万年的时间内的误差不会超过1秒钟(2.2 × 10−16)。
看这些数据就知道误差有多小了,是真的可以忽略不计。所以,我们就简单的认为两个原子秒的时间长度就是完全一样的好了。
那么既然有了原子秒,也就有了原子时
国际原子时(英语:International Atomic Time、法语:Temps Atomique International, TAI[1])
其起点为世界时1958年的开始。
貌似国际原子时的简称一般是TAI而不是IAT,不过这个不重要。
总之我们就简单的认为原子时就是从1958年1月1日0时0分0秒(UT)以来经过的原子秒数。(这里的UT是世界时,就是以英国格林尼治那旮沓为准)
到这里我不免畅想一下,如果大家都用国际原子时那该有多好啊……
当然,那是不可能的。如果你问我现在是什么时间?我告诉你XXXXXXXXX秒,那你一定会想打死我。
正常表示法
接下来我们仍然要回到正常的时间表示法,也就是采用年、月、日、时、分、秒的表示法。
建议阅读下面这篇文章。
格林尼治平均时间(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
当我查GMT的时候,出现了“平太阳时”,当我查“平太阳时”的时候,又出现了各种奇奇怪怪的名词,干脆我就不管了。
简单地说,GMT就是以格林尼治那旮沓为准的时间,也就是我们目前的时间标准。
尽管GMT也想了各种办法让自己的时间尽量精确,但如前所述,地球自转时间在变,这就导致GMT不可能绝对精准。
所以接下来就出现了UTC。
协调世界时(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于**格林威治标准时间**。
UTC采用了原子秒,这样就很精准了。UTC也把时间分为天、小时、分钟和秒。
但是,出现了问题,如果我们很简单的这样做了。那么由于原子秒和普通秒的差,不断累计下去,在未来,一个人一看手表显示14:00,却处在黑夜中的情况,这样就不利于人们交流了。
所以,UTC中还有一个“闰秒”的机制,让它来尽量接近GMT时间。
协调世界时把时间分为天、小时、分钟和秒。通常,天是使用格里历(公历)定义的,但也能使用儒略日。每天包含24小时,每小时包含60分钟。一分钟通常有60秒,但加入了随机的闰秒后,一分钟可能是61秒或59秒[12]。因此,在UTC系统的时间尺度中,秒和比秒小的单位(毫秒、微秒等)其长度是固定的,但是对于分钟和比分还大的单位(小时、天、周等),其长度是可变的。国际地球自转服务组织(IERS)做出插入闰秒的决定,并至少在加入前6个月发布在该组织的“公告C”中[13][14] 。闰秒是无法提前很早预知的,因为地球的自转速率是不可预测的[15]。
UTC时间中“1秒”就是1原子秒,但1分钟可能有61原子秒或59原子秒。闰秒是全世界大家一起加或减的,所以UTC时间仍然不会出现偏差。
不管怎样,“闰秒”的出现保证了保证协调世界时(UTC)与世界时(UT1)相差不超过0.9秒[9]。
(麻烦把UT1视作GMT就好,不要再管这里面的差别了)
总结
UTC采用原子秒,更加准确。但如果你是一个普通人,真的就不用在意这些细节,因为此时的UTC和GMT相差不会超过0.9秒,基本可以认为是一样的。
当然,如果你从事的行业对时间要求很苛刻,比如科学、金融、军事等领域。那么请忘掉我上面说的所有东西(因为我可能已经误导了你),请专业人士对你进行培训。
2、时间与计算机
在计算机上我们肯定也要跟时间打交道,如何处理、存储时间类型的数据?接下来介绍这方面的内容。
建议
那么,首先我要告诉你一件非常重要的事:
如果大概了解了UTC、GMT的区别,知道了闰秒的概念。那么,请忘记它。
请把GMT视作UTC,把1天就看做86400秒,把每天和每秒的长度看作是等长的。
为什么?
对于普通程序员
因为几乎所有现代操作系统都假设在所有情况下1天= 24×60×60 = 86400秒。如Windows,Linux,Apple,Android都对闰秒无知。
而且就算要设计,也非常麻烦,因为什么时候出现闰秒,取决于地球自身转速的变化,是不能预测的。所以干脆就不设计了。
所以如果你的程序,是建立在一个不准确的操作系统之上的,那么你也不可能得到一个准确的时间值。
简单地说,如果你是普通应用型程序员,就别考虑那么多了。
说实在的,个人计算机的时间本来就不怎么准确,时间的变动本来就有点随意,所以影响不是很大。
如果你问我,那为什么感觉我的计算机时间很准呢?最主要是,为什么我的多台PC之间、PC与手机、手机与iPad之间的时间基本都是同步的呢?
这是通过NTP(Network Time Protocol)来达到的,简单的说就是时不时地用网络进行对时,所以对人来说,感觉上大概就是准的。
如果你打开电脑的时间设置可以看到:

电脑是可以自动调节时间甚至是时区的。
而手机上也有:

这里我真的试了一下,关闭了NTP,把电脑的时间调到1个月前。电脑还是正常运行,网页也能正常浏览。并没有什么问题。

我们再用程序测试一下:
1 | import java.util.Date; |

这里我将电脑的时间改成了1991年,然后又改了回来,所以你会看到数字变小又变大。
可以看到,以上用Java程序执行的代码中,因为依赖于系统时间,所以后执行的代码得到的时间反而变小了。
这就说明系统时间确实有一定的不可靠性。用看到的一句话总结:
程序本来就应该认为本机的时间是不准的。
对于特殊程序员
虽说一般程序员可能不用考虑这么多问题,对于某些特殊的程序员还是比较麻烦的。
如果你设计的程序跑在一台连接着原子钟的计算机上,作为NTP的上游,那么我不知道你要做什么。
但如果你的程序跑在NTP的下游,比如说,你就是一个系统程序员,那我大概知道你要做什么。
当要增加正闰秒时,这一秒是增加在第二天的00:00:00之前,效果是延缓UTC第二天的开始。当天23:59:59的下一秒被记为23:59:60,然后才是第二天的00:00:00。如果是负闰秒的话,23:59:58的下一秒就是第二天的00:00:00了,但目前还没有负闰秒调整的需求。
让我们回顾一下,闰秒是为了协调UTC和GMT时间的,它可以加1秒(正闰秒)也可以减1秒(负闰秒,没发生过)。
再看一下NTP:
可以看到,NTP是以UTC时间为准的。我们不考虑NTP上游怎么处理的,只考虑下游。
事实上,下游的计算机处理不好时,就会出现“闰秒危机”:
闰秒危机的问题是当NTP传入23:59:60这个时间,秒的位置出现了60。而下游计算机看到了这个60,无法理解,不知道怎么处理,可能就会发生异常。
我们可以看到,闰秒危机的原因是下游计算机系统无法理解这个60。
(“负闰秒”应该不会出现闰秒危机,因为只是从23:59:58直接到00:00:00,下游计算机完全可以把这看做自己的表走慢了,然后纠正就好。)
所以如果你是一个系统程序员,那么对你来说最重要的就是在处理NTP接收到的时间时考虑60这个秒数,认为它是合法的,只要不触发异常,不崩溃就行,自己消化掉,不影响上层应用。仍然表现出1天86400秒的样子。
那么如果我现在用的操作系统就是不能处理60这个数字怎么办,也没办法迁移软件怎么办?
其实这应该是运维的事了:
- 在闰秒出现前关闭NTP,结束后重新开启NTP。
- Well,解决不了问题,就解决提出问题的人:从上游解决。
下面说说2:
阿里云ECS云服务器采用的方案是,将多出来的这一秒分平均分配到24小时(即86400秒)中,在闰秒时刻前12小时开始,闰秒后12小时结束。