计算机时间捡拾

什么是时间

1
2
3
4
5
6
7
8
9
1. 中国古代最小的计时单位是什么
2. 你知道沙俄时的“十月革命”实际发生在11月么
3. 农历一年最多385天,但西方为什么曾出现过一年445天
4. 历史上为什么会出现2月30号?Gregorian calendar(格里历)、儒略历、公历,阳历、阴历、农历、佛历、伊斯兰历都分别是什么
5. GregorianCalendar的1582年10月15日(对应epoch为-12219292800000)和1582年10月4日真的相差11*86400秒么
6. UTC(协调世界时)、GMT(世界时/格林威治时)、Unix Epoch、TAI(国际原子时)区别是什么,原子时精度高,但为什么日常生活不直接用原子时?
7. 闰秒是怎么产生的,Unix Epoch转为日期时间格式(UTC时间)时需要考虑闰秒么
8. 上海一定比UTC+0或GMT或London早8小时么
9. 南北极时区应该是多少

当我们谈论时间时候我们实际在谈论什么呢?
时间是度量一切变化的尺子,但我们日常理解的时间并非真实的物理时间,而是客观时间的一种度量,也是宇宙时空的度量。
我们可以通过计时工具算出电影时长,可以通过帧数和帧率计算影片时长,这也是时间的度量。
人类珍惜时间是因为万事万物、生老病死都和时间正相关,并非是时间本身珍贵,时间对所有人平等的、均衡的流逝不复返, 这是时间的单向性,比如通过电影片头片尾或剧中人走路方式,我们可以判断是正着放还是倒着放;又比如看地球绕日运动的视频,我们可以通过向东或向西区分是否倒着放–热力学的Arrow of time,时间之矢的理论就关注这些,比如看杯子从地上碎片变为桌子上完整杯子视频,正常人很容易断言这是倒放的。
所以想象一下,如果没有对“时间”的测量,人类对生命及万物认知又是如何?“时间”和空间/物质一样是客观存在的,但哲学上认为“时间”这个概念是人类独有的,爱因斯坦认为时间的本质是空间中物质的运动变化,常规时空观不过是大脑里的幻像,所以跟老妇人和跟年轻女郎相处时间感受快慢不一致的例子不仅仅是幽默。

好在自从人类掌握测量时间的工具后,工具的精准度已经是人类日常不可或缺,就像《霸王别姬》里”说好了是一辈子差一年差一个月差一个时辰都不是一辈子”。
上面是玩笑,但对于现代天文测量来说可能差之毫秒,确实会谬之600千里了,而如今“时间”作为现代科技中测量准确度最高的基本物理量,已经被用来转化测量其他基本物理量。所以搞清楚计算机时间可以先看看人类在度量时间这方面的努力和智慧和其重要性,涉及的天文或数学物理知识不必细究。
人类对时间的认知来自四季和昼夜
我们先看感受比较明显的昼夜、日月、四季和年:

年、四季、二十四节气

地球自转产生昼夜,公转产生一年四季,之后才有年月日时分秒。
中国在秦汉时已经总结出较为成熟的24节气,24节气源于对太阳在黄道上的位置的划分,两个冬至的间隔就是古人的一年(即回归年)。古人可以通过测量日影极长极短来确定冬夏至、春秋分,之后把地球公转24等分(当然是地球为中心的角度),但公转是椭圆且速度快慢不一,我国在隋唐时从实践中发现误差并提出改进方法,确保对农业生产的指导作用,不过在没有当今科学测量工具下,节气存在一定范围的偏差。
在西方也有类似“节气”的概念,进行农事、宗教、税赋等活动,比如基督教的复活节就对应北半球春分月圆后的第一个星期日。

昼夜、月、时、分、秒

仅仅区分四季、二十四节气远不够,人需要更细粒度,比如感知昼夜交替。
太阳就是月亮循环运动就是天生计时工具,全世界的文明几乎都不约而同选择这二者,太阳周而复始,最直观感受是处于正上方是一次循环,于是有了日,有了阳历。同样日月交替也是一天,月缺月圆为一个月是人在生产活动中最直观的感受,所以世界各地很早就有阴历,即月亮每经历一次从圆到缺的循环月相变化,就是一个月。
而昼夜一日人们需要劳作和休息,所以需要更细粒度的划分,最简单就是日晷根据影子角度等分,夜晚根据恒星转角等分,时间也从一日逐渐精确到时分秒。

历法、公历、农历

人类很容易知道最近过了几天,也会用几十天前描述某事发生,但这个时间是相对且变化的,如果描述更久远的呢?或者描述今天是时何年何月呢?这就需要一个标量,需要一种标识约定,比如过去多少个月相周期,四季周期,所以历法就被发明了,但世界各地历法不同,、随着人类交往版图的扩大,一个统一历法即公历被推广了

理解计算机的计时,需要先了解人类的计时系统,可以先从如下几个概念了解。需要指出如下除非明确或涉及原子钟一段,为便于理解,暂且先不必深究 24小时/86400秒的具体含义以及是否精准(统计意义上的时间均值),原子钟部分再介绍。

恒星日(Sidereal Day)

  • 恒星日是地球上某点对某个恒星连续两次经过地球正上方(上中天)的时间间隔
  • 地球自转的恒星周期,因为恒星通常被假设是不动的,所以他是地球真正的自转周期,实际和地球自转误差在8.4毫秒
  • 参考恒星计算时间和日晷类似,虽然彼时不清楚原理也不像航海时代有自带公式的仪器可以估算相对精确的数值,但经验累积大量数据足以让人作为参考
  • 恒星日和我们理解的一天24小时(见下文太阳日)有区别,1恒星日=0.99726958天=23小时56分4.0916秒, 所以恒星日与太阳日的误差将近4分钟(3.9291884)也就是每一年会差一天

太阳日(solar day)

  • 依据太阳运动所定义的时间,就是常规理解的一“天”或一“昼夜”,简单理解是昨天正午时太阳正在我的上方,那么今天正午时也应该在正上方,即地球、人、太阳两次三点一线时间间隔,这是是通常理解的一天,即为了弥补地球一天公转的1/365圈,还需要在自转3分56秒
  • 这个”一日“真正的对人类日常生活有参考意义,通用历法也是约定俗成,也对应我们通常理解的理解的一天24小时86400秒
  • 分为:视觉太阳日(apparent solar day)和平均太阳日,视太阳日就是真太阳日
  • 平太阳日(mean solar day)是以平太阳即假想的匀速在黄道上运行的太阳运转为参考点,以平太阳连续两次经过某地之下中天的时间间隔, 可以认为是视太阳日的mean
  • 平太阳日真正对应86400秒,因为平太阳就是为了日常时间均匀假想的概念,所以86400秒也是概念上的86400,真实世界是存在偏差的

恒星年 (Sidereal year)

  • 即太阳在天球上返回到对恒星而言的相同位置上的时间。即地球围绕太阳公转的真正周期,也即绕日公转360°
  • 一恒星年等于365.25636042 平太阳日,即365日6小时9分钟10秒。一个真实的周期数总与两个天体相对的周期数相差整整一周。回归年比恒星年短20分钟又24秒
  • 一恒星年大致等于1.0000385回归年(1 + 1/26000回归年)。如果以恒星年为基础的日历,会逐渐和季节不同步,大约每71年相差一天
  • 下文的儒略历就是把恒星年当作太阳年,约定365.25天为年,每四年一闰,导致大概每400年就和回归年计时快3天
  • 恒星年和恒星日都是以恒星参考,那么参考的是哪颗或哪些呢?笔者目前也解答不了,不过相信是离太阳系足够远的恒星又容易被观察到的,相信在近代,这类有许多。
  • 恒星是一种统称或描述,物理学上更常见的是它的终态,有白矮星、中子星、黑洞,脉冲星就是中子星一种,他的特点就是磁化后可以非常精确的毫秒级间隔发出宇宙射线,如果在地球上能捕捉到这些脉冲,那么就可以计时。LIGO引力波探测项目的计时就是选择脉冲星

回归年(tropical year)

  • 也称为太阳年(solar year),是从地球上观察太阳再回到黄道上相同的点所经历的时间,即太阳平黄经变化360°
  • 这可以由由每天正午时日晷影子长短测量得到,有分点和至点区别,对应的时间精确度有些差异,但对在黄道上所有的点取平均值的年称为平回归年,为365.24219日,而以春分为起点,则是365.2424日
  • 回归年是制定阳历和阴阳历的基础,中国用的回归年,是从冬至再回到冬至所经历的时间。回归年就是和季节起点能完美同步的
  • 早在祖冲之时代已经精确到365.2428日,而在欧洲自希腊时期到1582年,都在使用365.25这个数值,也即欧洲 儒略历(Julian calendar) 一年的定义,之后就是大家知道的 格里历(Gregorian calendar) 诞生了,它更新一年为365.2425日,接近现代测量值365.2422,【注意这里两个历法,下文还会讲到】,再之后1627年,经开普勒更新这个数值为365.24219日,之后经过牛顿、拉普拉斯、拉格朗日这些天才数学家物理学家天文学家们给出平均回归年的计算公式,公式也指出大概每百年这个数值会少半秒。
  • 这里可能会看到闰秒的有关,没关系,下文还会讲到。回归年比恒星年少20分24秒左右,所以1回归年 = 365.242199日 = 365天5小时48分46秒,那么大约25786年便会差一周,不过日常计时不受此影响
  • 中国元代郭守敬通过建造27个天文台地点,编造授时历将一年精确到365.2425,早于格里历300年,授时历主要是二十四节气,尤其冬至、夏至的确切预算、置闰方法的改进,虽然是阴历,但其精度简直是格里历的先驱了
  • 罗马历、儒略历都是由当时开疆扩土的帝国领袖推动发明创造的,授时历则是由忽必烈推动,大概古代四处征战离不开对时间日期的计量的需求

儒略历(Julian calendar)

  • 古罗马的历法取材古希腊,本质就是下文要讲的阴历一种,所以古希腊对月份的称呼是强烈的和其祭祀、生产相关的,一年有354天,也有设置闰月方法,直到公元前432年才发现19年7闰,并专门命名:默冬(Metonic cycle),即19个回归年235个月相。
  • 旧罗马历设置闰月方法是在二月和三月插入闰月,即插入27或28天,理论上可以消除和回归年的差距,但因为置闰由教皇决定,所以会被滥用于是否延长其执政官的任期。也导致月份和季节偏差变得明显。
  • 因为这种混乱,凯撒大帝在公元前45年1月1日起颁布执行儒略历,取代该旧罗马历法。改为12个月一年,四年一闰,平年365日,闰年于二月底增加一闰日,故年平均长度为365.25日
  • 在这前一年,即公元前46年是历法最为混乱一年,为了和回归年一致,儒略历将这一年增加到445天(355天+旧历闰23天+新历两个闰月67天)
  • 儒略历开始几十年执行得较为混乱,前36年在罗马一度错误的出现3年一闰,导致少了3天,在后来几年内通过不设置闰月方式修复
  • 这段时间内出现过2月30号,历史争议,但瑞典、苏联确实出现过2月30日
  • 儒略历不仅被广泛使用,且时间跨度也较久,如英国美国1752年才停用,俄罗斯直到1918年才停止使用

公历/阳历

  • 阳历就是太阳历,比较有名的是西方从希腊到16世纪的儒略历,以及后来的格里历
  • 太阳历就是以地球公转为参考,地球上所呈现出太阳直射点的周期性变化,所制定的历法,约为365.2421897日
  • 在中文里阳历一般指的就是格里历,公历本义是指国际通用历法,源于百年前民国时的叫法,也称西历、国历、新历,实际也是格里历,如果看英文翻译,公历就是 Gregorian calendar

格里历(格里高利历/基督纪年)

  • 上文回归年部分介绍过,参考回归年的就是回归阳历,儒略历/格里历均是,他们特点就是: 春分太阳直射赤道,当天昼夜时间平均,夏至则直射在北回归线,日照时间达到最长,秋分再次直射赤道,而冬至阳光直射在南回归线时,日照时间最短。其中春分在东方对于生产在西方对于教会和生产,都有重要意义。
  • 上文儒略历将一年分为365.25天,与真实的回归年有偏差,格里历则通过将四年一闰调整为四年一闰但排除被100整除的年份,将这个偏差修复了缩短了0.0075日,即365.2425日。这也是格里历和儒略历的区别。
  • 为什么要改儒略历?以及为什么英美200年后才执行?
    • 复活节是基督教重要的日子,每年春分月圆之后第一个星期日举行,一般是在3月21日后满月后第一个星期日
    • 之所以是3月21,因为这是早期形成的春分日共识,但儒略历和回归年误差导致每年较春分提前10.8分钟即0.0075日(365.25-365.2425),两千年后可能就到4月份了
    • 所以1582年10月4日之后紧接就是1582年10月15日(但这里公元前46年到1582年,实际偏移应该是(1628*0.0075)) 12天,为什么却只跳过10天呢?网上有解释是追溯到旧罗马历前713年至前46年多了两日误差,这是一种解释,但其实没有讲清楚这个误差怎么来的,而且这段时间纪年本身也是混乱的,我觉得可能这个解释等价于 儒略历开始之日就是和回归年有误差的(当然,也可能极小概率这1600多年地球自转原因), 但我觉得可能只是当时制定者从下一年春分日推算出需要跳过10天会更准确。但不管何种原因,目标是一致的。
  • 所以这里只需记住;1582年——1699年:格里历日期减10日等于儒略历日期。
    1700年(格里历无闰日,但儒略历有):所以18世纪格里历减11日等于儒略历
    1800年(格里历无闰日,但儒略历有):所以19世纪格里历减12日等于儒略历
    1900年(格里历无闰日,但儒略历有):所以20世纪格里历减13日等于儒略历
    2000年(格里历有闰日,儒略历有), 二者抵消\
  • 格里历是教宗颁布,新教国家英国并不采纳,美国为英殖民地亦然,直到1752年,大概是贸易往来时差可以接受但是日期差不可接受,所以1752年9月2日直接跳到14日,日本则是明治维新后切换
  • 上面提到,俄国1917年依旧使用儒略历,十月革命发生在俄历(也即儒略历)的10月25日,对应格里历需+13日,即公历11月
  • 格里历较平均回归年约3300年误差一日,较春分回归年约7700年误差一日
  • 格里历在400年的周期恰好是20871个星期,这4800个月中688个月第一天是星期日(最多),684个月是从星期六开始(最少)
  • 这里附图,很好的描绘了格里历6月的日期和天文学上的夏至时间差,横轴是年份,竖轴是6月的日期,每个点对应当年天文学上夏至时间。可以想到对于儒略历如果不修复设闰规则,这些点是一直向下的

阴阳历(Lunisolar calendar)

  • 以太阳定一年和四季(阳历),以月相定月份周期(阴历)
  • 比如 古代的“阳历”,只保证一年满足地球太阳运转周期,不考虑月份。而阴历只满足月亮运行周期,不满足年,阴阳历调和了二者。这种做法在东亚、东南亚、以色列犹太地区流行

阴历/农历

  • 阴历月相计时,指的就是朔望月,是指月球连续两次合朔的时间间隔,受地球影响,在29.27至29.83天之间变动,长期的平均长度是29.530588天(29天12小时44分2.8秒),即29.5天
  • 世界各地都有阴历计时,甚至包括玛雅人以及我国的少数民族地区,但农历是我国特有的,民国“废历运动”时期,对旧的历法统一称之农历,因为旧历主要用于农业生产,包括年月日、24节气、闰月、星宿黄道置闰法等知识,所以农历就是阴历,最早叫法为夏历,后期近百种历法的改进,有各类称呼,也叫华夏历法
  • 上面可见中国的阴历实际是阴阳历(月相+节气),延续两千多年至民国,但农历纪元不符合世界主流的公历纪元,中国是直接切换到公历纪元,但农历依旧并存至今
  • 大部分阴历大月30日,小月29日,这样12个月为1年的话,那么就是354日或355日之间,只是大体上符合一个四季循环,符合上面29.5天均值,但也存在部分地区阴历不是29.5均值,或者保持354/355天一年,这就会导致新年可能缓慢推移,从冬季逐年移动到夏季。例如伊斯兰历、古希腊历便是如此,伊斯兰的斋月是伊斯兰历的九月,所以你可能会发现有的兰州拉面馆老板休息参加斋月,怎么有时在夏季有时在冬季
  • 春秋时期的阴历,就已经设置30或29日的大小月,与月亮运行周期一致29.53一致。这样每年354天,同时为了与太阳周期一致,还会增加一个大小月设置闰年约384日,这样每十九年设置7个闰月((365.24-354)*19/7=30),就能满足每十九年平均为365.247日,与地球绕日运行周期一致,在其他地区有8年/84年等周期选择,是的阴历年月周期接近太阳年
  • 古代已经知晓回归年等于365.25天,月相变化的周期=29.53天,而能够找到19年即235个月这个数,使得误差远少于一天,但十九年七闰其实还是会产生误差的,每19年和太阳年就会有0.0892天的误差:19*365.2422-235*29.5306=0.0892,这样每213年就会积累约1天的误差,隋唐时已经意识到这个问题,采用更准确的“定气”法,通过两个冬至及合理的置闰规则,是的农历年和太阳回归年对应,但大概计算复杂,直到清代才被官方采纳。
  • 感兴趣可以看看置闰细则以及置闰的2033年问题
  • 19年7闰至少公元前500年就被实际使用,跟默冬类似,汉字也有专门命名:章,章岁十九、章月二百三十五、章闰七

好了,本文后续的内容理解起来就容易些了

十二时辰、刻

  • 古时一天按天干地支分12个时辰,也分为24时,标准的更小一级是“刻”
  • 古人就把一天分为100刻,即1刻等于14分24秒,自西周至明,“百刻制”与“十二时辰计时”并用,到了清代96刻才成为标准。
  • 所以“刻”就是古代最小时间测量单位,像白驹过隙、弹指、刹那,这些都是文学术语,不是真实的测量单位

回归年 2

下面表有助理解平均回归年,这里Year 0 是天文学上的公元0年,公历纪年是没有0年的,days指的是平太阳日,这里也可以看到在公元0年平回归年的值是365.2423天(数千年稳定在365.2424和365.2423之间),而现在是365.2422天

Year 0 Year 2000
春分 365.242137 days 365.242374 days
夏至 365.241726 365.241626
秋分 365.242496 365.242018
冬至 365.242883 365.242740
平均回归年 365.242310 365.242189

现代测量下来的回归年是365.242189670天,而回归年长度可以认为是每年递减约5毫秒。

  • 那么,这里的“天”指的是什么呢?这里 ”365.242189670天”的天,在如今是和下文的地球时相关,即86400秒,但相对论是近代才有的,此前怎么定义日呢?显然过去天文学家使用的就是太阳日(不区分视或平),不过这里精度差对于不需要深入理解这些内容可以忽略
  • 春分/冬至等测量的回归年长度不一致,是因地球椭圆轨道+远近时绕日速度不同导致
  • 注意回归年长度和地球自转速度(太阳日长度)无影响的,但太阳日长度和其椭圆轨道+远近时绕日速度相关

恒星日 2 / 恒星时(Sidereal time)

  • 恒星日可以分为12恒星时,但地球上每个地方的恒星时都与它的经度有关,其定义是一个地方的子午圈与天球的春分点之间的时角
  • 均恒星时可以通过平太阳时简单转换得到
  • 恒星日不适合作为计时工具的依赖,因本身存在偏差
  • 地球自转长期看在变缓,世界上走时最精确的原子钟显示,现代的一日比一个世纪前长了约1.7毫秒,短期看,有时也会变快,如最近几年发现自转每天减少1.59 毫秒(注意这里有助于理解下文闰秒)

太阳日 2 / 太阳时

  • 视觉太阳日(apparent solar day)和平均太阳日,上文提到视太阳日就是真太阳日,但因为公转轨道是椭圆(近快远慢)以及倾斜角存在,每太阳日可能在24小时偏差13-20秒
  • 比如1998年9月16日视太阳日为24小时−21.3秒,而均匀的平太阳日才是人类希望看到的钟表上的时间
  • 太阳时分为真太阳时(Apparent solar time)和平太阳时,日晷所表示的时间就是真太阳时,现代钟表所表示的时间就是平太阳时,所以平太阳时也叫民用时,不过真太阳时是不均匀的,而平太阳时同平太阳日,一年中每天都是均匀的
  • 现代真太阳时可以有均太阳时(就是钟表时间)简单运算得到,但均太阳时实际是19世纪 天文学家创造的概念,所以这之前就是通过真太阳时计时的,比如古代日晷和滴漏计算一天时刻,其实日晷计算的是视太阳日,存在偏差,只不过这个偏差不超过30秒,且在一年内可以均衡掉(但对于日期及节气计算是准确的),日晷推算出正午,而对于其他时刻,可以滴漏等计时,我国古代浑天仪+浑象仪是可以算到较为精确的时辰+刻的
  • 浑天仪+浑象仪 计时原理,比欧洲的天文钟要早,我国唐朝便有有机械钟,元初郭守敬大明灯漏,欧洲则到14世纪出现,摆钟是17世纪出现,但都存在误差需要校正
  • 1967年后有了原子钟能够计时真正的86400秒,但我们依旧把人类的一天(钟表/日历上的一日)或“平太阳日”约定为86400秒(记住这两种计量不同,有助下文理解 闰秒的产生),写到这里可以开始介绍 闰秒 了,但不妨再看看下面几个概念,对时间理解会更深些。

儒略日(Julian day)

  • 这名称实际是法国天文学家16世纪创建的,该Julian和罗马的凯撒大帝无关,也和儒略历无关,纯粹以人名命名的一套计数法,它开始自公元前
  • 是一种日数计算时间的计时法,主要是天文学家在使用
  • 儒略日数(Julian Day Number,JDN), 这是儒略日计数系统重要的概念,指的是从格林威治标准时间的中午开始,距儒略历的公元前4713年1月1日中午12点所经过的自然天数
  • 儒略日期(Julian date,JD)是以格林威治标准时中午12:00的儒略日加上那一天的瞬时时间的分数。儒略日期是儒略日添加小数部分所表示的儒略日数
  • 上述概念了解即可,这里提到是因为儒略日期通常用于各种日期转换和计算,比如中国农历纪元里名人诞辰对应格里历时间

格林尼治标准时间(Greenwich Mean Time,GMT)

  • 位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
  • 1885年1月1日投入使用,1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时讯号,但上世纪50年代开始不再被通用。
  • 格林尼治平时 为什么不再被使用?当然基于天文观测本身的缺陷,尤其是后来的原子钟精度极高,被协调世界时取代。 但为什么不修复而是淘汰呢,我认为可能是格林尼治最初被提出时就是有争议的,需要有一个独立三方来制定,也即后来国际天文联合会的世界时。所以如果仅从时间表示上看,可以认为下文的UTC时间(世界协调时)就是格林尼治时间修复版,即格林尼治时间始终和UTC时间相差不超过1(0.9)秒
  • GMT很好的将时区即成在自己的体系中,后来世界时沿用 时区列表
  • 关于时区,epoch [-5364658800]秒,对应UTC+0时间是 1800-01-01 1点整,那么对应伦敦、上海时间是多少?
1
2
3
4
5
6
7
8
// java语言为例
System.out.println(ZonedDateTime.ofInstant(Instant.ofEpochMilli(-5364658800000L), ZoneId.of("Europe/London"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXXX")));
System.out.println(ZonedDateTime.ofInstant(Instant.ofEpochMilli(-5364658800000L), ZoneId.of("Asia/Shanghai"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXXX")));
// 输出如下:
1800-01-01 00:58:45-00:01
1800-01-01 09:05:43+08:05

看到了么,上海时间实际早了 8时5分43秒,伦敦慢了0时1分15秒!
因为在1912年前上海没有本地时区的,所以为默认的国际时区偏移),即用上海的精度除以15即得得到+08:05,或“08:05:43Z”或“080543Z”,伦敦亦然,像Java语言可以从源码看到JDK 1.8 从位于 jre/lib/tzdb.dat的文件加载时区数据库,可以参考:时区数据库wiki/下载数据库,他是由IANA组织维护的时区信息数据库

1
2
3
4
5
6
7
8
9
10
11
12
# Zone  NAME    STDOFF  RULES FORMAT  [UNTIL]
# Beijing time, used throughout China; represented by Shanghai.
#STDOFF 8:05:43.2
Zone Asia/Shanghai 8:05:43 - LMT 1901
8:00 Shang C%sT 1949 May 28
8:00 PRC C%sT
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/London -0:01:15 - LMT 1847 Dec 1
0:00 GB-Eire %s 1968 Oct 27
1:00 - BST 1971 Oct 31 2:00u
0:00 GB-Eire %s 1996
0:00 EU GMT/BST

有兴趣可以具体参考,里面记录了自1970年以来时区和城市的变化,包括历史已知的,并且还包含一些时间的转换,非常详细,比如仅香港/台湾的规则,就多达20条,甚至一年三次偏移, Unix/Linux类系统90年代已经集成,python早期datetime.timezone支持有限,PEP 615 3.9支持,从系统时区文件”/usr/share/zoneinfo”或自己的时区数据库加载.

  • 从上面我们也可以理解,JAVA在1.8中新的Time类中(JSR-310),设计的其实是更为科学,把ZoneID分为ZoneOffset和ZoneRegion,ZoneOffset是没有用region名字(如Asia/Shanghai)作为参数的构造方法(参见ZoneRules),因为同一个地方时区偏移可能多个。而早期版本TimeZone当使用region名字时,默认实际是当前时区,不过也支持自定义时差和规则或者自定义实现 ,当然新版仅datetime就分为LocalDateTime、ZonedDateTime、OffsetDateTime、LocalDate、LocalTime、ChronoLocalDate,或许值得再写一篇,但其实通过类名和本文,不难理解
    从下面代码,1处可见新接口通过ZoneRules将一个地方的时区偏移和其epoch时间关联起来。 2处列出JAVA加载IANA数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ZoneOffset offset=ZoneId.of("Asia/Shanghai").getRules().getOffset(Instant.ofEpochMilli(-3153600000000L)) //#1
System.out.println(offset2.getTotalSeconds()); // 输出是29143,而非28800
// java.time.ZoneRegion.ofId
static ZoneRegion ofId(String zoneId, boolean checkAvailable) {
...
rules = ZoneRulesProvider.getRules(zoneId, true);
return new ZoneRegion(zoneId, rules);
}
// ZoneRulesProvider的实现是:java.time.zone.TzdbZoneRulesProvider
public TzdbZoneRulesProvider() {
...
String libDir = System.getProperty("java.home") + File.separator + "lib";
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(
new File(libDir, "tzdb.dat"))))) {
load(dis);
}
}

load会从二进制的javahome/lib/tzdb.dat读出版本信息、regionIds、zoneRules, zoneRules存放在TzdbZoneRulesProvider.regionToRules这个map中,zoneRule解析代码在 ZoneRules.readExternal/ZoneOffsetTransitionRule.readExternal。
TzdbZoneRulesProvider 实际被用到时候才会加载文件,另一个优化是加载的其实是byte数组(Objcet),只有这个ZoneRegion/ZoneRules真正被使用时候才会解析并被替换成ZoneRules(Object)。但要注意ZoneRegion对应GMT时区缩写时候是存在CST时间问题的

  • 格林尼治天文台就是根据恒星时反推太阳时的,所以他们最初对时也存在时间不连续问题,所以有真平太阳时差说法
  • 一个例外是 地球南极点和北极点时区应该怎么计算?我们知道比如在北极,太阳在三月的春分时升起,在九月的春分前后到达日落,即太阳一直在地平线以上,那么时间是如何确定的?目前可以选择使用格林威治标准时间,也可以任意观察者自己的时区

世界时(Universal Time,UT)

  • 世界时其实和GMT同样基于天文观测时间,同样有缺陷,但其考虑到受到地球自转不均匀变化和极移的影响精度,于是约定UT0、UT1和UT2三个系统解决这种影响
    • UT0系统是由一个天文台的天文观测直接测定的世界时,没有考虑极移造成的天文台地理坐标变化
    • UT1系统是在UT0的基础上加入了极移改正 Δλ,修正地轴摆动的影响。UT1是目前使用的世界时标准。被作为目前世界民用时间标准UTC在增减闰秒时的参照标准
    • UT2系统是UT1的平滑处理版本,在UT1基础上加入了地球自转速率的季节性改正 ΔT
  • UT1-2 介绍看起来简单,但是却涉及了恒星时、地球自转角、儒略日期、贝塞尔年、地极移动等,不过好在理解了下文的基于UT1的世界协调时,我们可以不用再关注UT2,所以这里不要纠结Δλ、ΔT分别是啥以及计算公式

国际原子时(International Atomic Time,TAI)

  • 自提出至1960年,秒是由天文学家定义的,即平均太阳日的1/86400(但至今仍然适用),在1960至1967年之间,平太阳日问题,更改定义为1960年地球自转一周时间的1/86400,但1967至今秒实际有TAI重新进行了革命性的定义
  • 在此之前,即1928年石英钟问世,其原理是利用石英晶体震荡来计时的计时工具,日误差小于千分之一秒,年误差3~5秒。但石英精度提升有限,至今不过日误差十万分之一秒
  • 原子钟使用电子转变能级时释放的精确微波讯号计时,这种电磁波非常稳定且不具放射性,利用仪器可以探测并计数从而实现计时。第一个原子钟是氨微波激射器,1949年已是当时最精确的计时工具,但这个原子钟还没有现在的石英钟准确。1952年铯原子作为振动源的原子钟问世,大大提高了精度,之后作为高精度计时工具被用于验证狭义相对论,如今已经达到3亿年相差不到1秒
  • 原子钟的出现,将真太阳时波动性和误差展示无余,同时也验证了其他误差原因,所以真太阳时已不适合作为计时依据,包括时分秒在科研时需要精准定义和计量,像美国俄罗斯等GPS定位系统,以及中国的北斗都配备了原子钟精确计时
  • 1967年第13届国际计量大会上通过一项决议,定义一秒为铯-133原子基态两个超精细能级间跃迁辐射振荡9,192,631,770周所持续的时间,时间作为国际单位制的七个基本物理量之一,至此有了精准的定义
  • 石英晶体的晶振本质是一种机械振动,震荡频率很难提升,晶振也受温度、电磁等影响,很难保持长期稳定,所以人们想到了原子能级跃迁的稳定特性,铯、铷电子跃迁可控且之后表现惰性,加之提纯相对容易,铯是当时可使用精度最高金属
  • 计算机计时工具不断改进,但总的来说都含有晶振计时部分,如主板就含有石英计时器(RTC、TSC),出厂前会给予当前时间的初始值,如果没有NTP等时间同步,计算机系统时间()源于此,但有实验验证过150天内(存在温度增减),这个时间慢了30多秒
  • CPU运行时数字脉冲信号震荡也是频率稳定的(GHz),可以通过计数确认时间变化,像Java中System.currentTimemillis或System.nanotime,其实都是通过比较两次CPU脉冲次数来计算时间差的(即基于cpu核心的时钟周期来计时)。不过nanotime这个方法有个初始计数t1(所以甚至可能是负数),之后返回值减去t1即是这段时间的纳秒数,而currentTimemillis实际返回unix epoch,即需要根据系统时间再转换一次,系统时间本身会考虑闰秒等因素。所以如果NTP同步时间如果发生漂移,nanotime得到的是真实的时间跨度,而currentTimemillis是调校后的钟表时间,即世界协调时
  • TAI是有世界各地近百个原子钟定期向BIH报告其周期数得到均值(自1958年1月1日午夜以来被9192631770除后的滴答数)而来,也就是秒级精度。 那么你或许已经想到 这些原子钟怎么上报自己的时间呢?这显然是一个分布式的问题(只不过区别是分布式节点都是可信的,而中心节点只需要取均值),其中上报延迟有怎么计算呢?(暂未找到相关信息,但猜想,他们可能只需要根据对比新的周期数反馈后,再比较上报周期数差值,根据差值调校即可,也就无所谓延迟了)
  • 原子钟的出现,可以看到,天文秒被原子秒所取代,物理学家从天文学家手里接手了秒的定义以及后续更多精细化工作。毕竟转了几十亿年的“钟表”,实际受到影响太多,风速、洋流运动、流星撞击、地震、潮汐等,每年相差0.5-0.6秒(相比平当时已知的UT1),即平均2年就可以产生一个闰秒(但其实无规则的,下文再介绍)
  • 有趣的是 定义天文时间的格林尼治和发明原子钟的英国国家物理实验室都在伦敦周围,原子钟早已出现,但其实秒的定义直到半个世纪后才替换
  • 在中国古已有之,但不是时间单位,而是表示万分之一,如祖冲之定义的圆周盈数:三丈一尺四寸一分五厘九毫二七忽
  • 光学原子钟是最新最高科技的计时工具,据说铯原子钟精确1000倍以上,可以非常直观的显示出重力对时间的影响,有望再次重新定义秒
  • 最后再来看看秒的定义,学生时代看到这里总会好奇:
      1. 要定义这么多次震荡为1秒依据是什么呢?
      1. 为什么是9192631770个震荡周期?9192631775个可以么?
  • 先说说1,之前一直是死记硬背,所以这里其实一直没有理解,所以也就没有真正理解什么是科学,不知道科学测量其实是一个逐渐精进的过程,或者也可以说是无心插柳的过程,人类是先有天文时意义上的一秒的定义(一天的86400分之一),包括后来通过重力加速度设计摆钟测出更加精确的一秒,但意识到误差的存在,而相对论、量子力学的出现改变了人类对时间的理解,这个时候寻求一种微观意义上的能精确计时的工具成为了必然,聪明的人发现如同恒星周期一样,晶体的晶振频率极高且恒定,天然的计时工具,再后来人们发现有些原子跃迁产生更高频更稳定的电磁波(微波),并且不容易受到重力、气压、电磁场等环境因素制约,于是就诞生了一类更精确的计时工具
  • 再来回答2,首先是不建议这么做,但如果问知乎或deepseek得到答案可能是如果用后者,可能会导致许多科学数值改写,但这个答案并不合理,因为这样秒作为基本单位就失去了意义,真实原因可能需要参考当时起草人草稿才得知,但笔者猜测可能的原因是9192631770是一个实验测量值,数值分析得来

协调世界时(Coordinated Universal Time 即 UTC)

  • 上面提到UTC,这里简单梳理加深印象:
  • UT到UT1的修复:上面提到旧的世界时加入了Δλ,即地轴摆动的影响,修复了世界时的误差,这是第一处“协调”的地方
  • 闰秒,UT1到UTC基于原子时的修复:上面已经提及秒的定义已经从天文时间变为更科学的原子时,所以一日是86400原子秒么,还是UT1?计时工具需要方便人类日常生活,而后者更有意义(对于科学测量则不然),所以发明UTC来协调UT1和基于原子振荡周期的国际原子时(TAI),这是第二处“协调”的地方
  • 上文提到格林尼治平时(GMT)已经被UTC取代,现在一般认为是与UTC即世界协调时相同(所以被叫做 格林尼治标准时间),其他行业如航海领域依旧参考UT1,这里不展开。
  • 那么”协调世界时测算标准就是UT1“ 怎么理解呢?简单形象化一点: DUT1 = UT1 - UTC,这就是UTC和UT1的差距,通过 DUT1,就可以把UT2考虑到的需要复杂计算的偏差以及未考虑到的偏差,都可以概括起来。 这个偏差由国际地球自转服务组织(IERS)跟踪并修复。这里可以看到我们把UTC和UT1、GMT串联起来了
  • DUT1如何协调?这就不得不提到目前在用的方案:闰秒

闰秒

  • WIKI:UTC基于国际原子时,并通过不规则的加入闰秒来抵消地球自转变慢的影响。闰秒在必要的时候会被插入到UTC中,以保证协调世界时(UTC)与世界时(UT1)相差不超过0.9秒
  • 注意这里:
      1. 协调时UTC是基于原子时间
      1. 闰秒调整的是UTC和UT1
  • 原子时显然是我们需要且期望的,但天文时(无论基于1900.0平回归年或恒星年)UT1也是需要的,二者产生偏差如何协调?一个互相兼容的解决方案就是采取新的时间标准,”以原子时为基准“同时每一秒和原子时一样(都是国际单位制秒),而与后者相差过大时候可以加一秒或减一秒调整,目前方案就是(一般在相差0.6秒时插入,来保证)保持UTC和UT1始终相差不超过0.9。不知道这里你是否疑问,为什么不直接用UT1作为协调,而新引入UTC?这是因为UT1存在偏差,所以有UT2解决几千年一小时偏差问题,所以不如直接引入UTC,UTC最初的确就是保持与UT2的同步,直到1972年
  • 协调时UTC在1961年提出,原子时作为标准则是在1967年,实际此前原子时早已定义,但是在1956年历书秒依旧被选择作作为国际单位制中的时间基本单位,也就是基于地球绕太阳公转周期的历书时,所以在原子时作为事实标准的1972年之前,通过原子时进行的协调时校正是不规律的,频率的改变发生在每年的年末, 每次跳跃都使时间增加100毫秒,这种频繁跳跃干扰太多,所以当时“UTC的秒长应和TAI的秒长一致”以及“间阶跃的长度应仅为1秒”,两个观点被采纳,这样1971年底,UTC时间进行了最后一次不规则时间跳跃,此后调整为闰秒方式,注意这里闰秒是为了协调 UTC与UT1之间的误差,而此时TAI和UTC已经相差10秒了(不要问为啥,总之基准问题,就是慢了,可参考闰秒wiki),自1972年协调世界时正式使用至今,全球已经实施了27次正闰秒调整,上一次的调整是2016年12月31日,所以至今55年过去,UTC时间读数实际已经落后TAI时间37秒,但是这55年内计数上慢了27秒seconds-since-the-epoch
  • 上面提到UT2实际因地球的旋转速度会随着气候和地质事件的变化而变化,所以UTC的闰秒间隔不规则的,需要认为的修正,一般是IERS提前约六个月决定,所以上文大概闰秒实际不是满两年就有的,1972-1979年的八年中有九个闰秒,像90年代调整7次而2017年至今没有再调整过
  • 一般在UTC日期的23:59:59秒和下一日期的00:00:00秒之间插入一个正闰秒, 日期优先选择12月和6月的最后一天,其次可选择3月或9月的最后一天,额外的秒在UTC时钟上显示为23:59:60。负闰秒将删掉所选月份最后一天的最后一秒23:59:59,即的23:59:58之后将紧跟着下一天的00:00:00。但目前没有触发过负闰秒
  • 闰秒是地球自转的原子时间和平太阳时之间累积的差异指标,但自转变快变慢并不是唯一原因,比如1972年的平均日长约为86400.003秒,2016年的平均日长约为86400.001秒,这段时间内地球自转率整体上其实是有所增加,尽管在这段时间内已经多次插入正闰秒
  • 闰秒在开始就跟闰月、闰年一样是令人拍案叫绝的发明,但闰秒实际对应epoch的回退,随着计算机走向大众,出现不少时间精度要求场景,比如依赖精确时间的程序控制的服务中,比如基于时间戳生成id服务,产生许多问题,不少组织和工程师、Linus等建议废弃,目前是到2035年,闰秒将废弃
  • 看到这里,相信你应该了解到,从unix epoch转换成日期时间或日期时间转为unix epoch不需要考虑闰秒,对于UTC而言闰秒只是一个虚构的读数问题,只不过可能映射到同一个epoch,假设下面是tm_xxx都是utc时间,则对应转换公式为:
1
2
3
tm_sec + tm_min * 60 + tm_hour * 3600 + tm_yday * 86400 + (tm_year-70) * 31536000 + ((tm_year - 69) / 4) * 86400 -
((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
// 北京时间'2025-03-01 08:00:00',对应utc tm_hour=0,tm_yday=59, tm_year=125,可算出UTC '2025-03-01 00:00:00'对应1740787200
  • 我们看上面公式 转换‘2025-02-29 00:00:00’ 和’2025-03-01 00:00:00’都对应同一个epoch - 这就是闰秒作为符号的意义
  • 这里举个闰秒产生时TAI-UTC-unixepoch的例子,如下所示,TAI时间为1999-01-01T00:00:30.75之后,如果没有闰秒 UTC应该是1999-01-01T00:00:00.00,加入闰秒,则变成1998-12-31T23:59:60.00,即还是1998年,这里注意到在这TAI2秒内unixtime始终是 915148800 秒,这里是时间及时同步场景,如果我们调用系统时间(gettimeofday)915148800实际经历两次,也就是时间回退了。当然没,如果时间同步不及时,只要计算机始终频率均匀不变,将来也必然会回退
1
2
3
4
5
6
7
8
9
10
11
TAI=1999-01-01T00:00:30.75; UTC=1998-12-31T23:59:59.75; unixtime=915148799.75
TAI=1999-01-01T00:00:31.00; UTC=1998-12-31T23:59:60.00; unixtime=915148800.00
TAI=1999-01-01T00:00:31.25; UTC=1998-12-31T23:59:60.25; unixtime=915148800.25
TAI=1999-01-01T00:00:31.50; UTC=1998-12-31T23:59:60.50; unixtime=915148800.50
TAI=1999-01-01T00:00:31.75; UTC=1998-12-31T23:59:60.75; unixtime=915148800.75
TAI=1999-01-01T00:00:32.00; UTC=1999-01-01T00:00:00.00; unixtime=915148800.00
TAI=1999-01-01T00:00:32.25; UTC=1999-01-01T00:00:00.25; unixtime=915148800.25
TAI=1999-01-01T00:00:32.50; UTC=1999-01-01T00:00:00.50; unixtime=915148800.50
TAI=1999-01-01T00:00:32.75; UTC=1999-01-01T00:00:00.75; unixtime=915148800.75
TAI=1999-01-01T00:00:33.00; UTC=1999-01-01T00:00:01.00; unixtime=915148801.00
TAI=1999-01-01T00:00:33.25; UTC=1999-01-01T00:00:01.25; unixtime=915148801.25
  • 好了,你可能听过 UNIX时间、Unix Epoch、POSIX时间,其实都是UTC1970年1月1日0时0分0秒起至现在的总秒数,其中POSIX是时间就是Unix时间,因为POSIX本身就是各种UNIX约定的一系列API标准的总称,因为IEEE制定,所以其正式称呼为IEEE 1003,那么Unix Epoch是如何转成人类可读的日期时间(或UTC格式时间),上文也列出,这里不重述
  • 这里另外举个例子,理解下 闰秒时TAI-UTC-UT1的关系,它展示了一个闰秒时,UTC时间和UT1时间的差距(原子时作为参考),可以看到,UTC-UT1误差调小了,TAI-UTC差距则变大1秒。这个例子来自:The Unix leap second mess
1
2
3
4
5
6
TAI=2009-01-01T00:00:32.0; UTC=2008-12-31T23:59:59.0; UT1≅2008-12-31T23:59:58.407
TAI=2009-01-01T00:00:32.5; UTC=2008-12-31T23:59:59.5; UT1≅2008-12-31T23:59:58.907
TAI=2009-01-01T00:00:33.0; UTC=2008-12-31T23:59:60.0; UT1≅2008-12-31T23:59:59.407
TAI=2009-01-01T00:00:33.5; UTC=2008-12-31T23:59:60.5; UT1≅2008-12-31T23:59:59.907
TAI=2009-01-01T00:00:34.0; UTC=2009-01-01T00:00:00.0; UT1≅2009-01-01T00:00:00.407
TAI=2009-01-01T00:00:34.5; UTC=2009-01-01T00:00:00.5; UT1≅2009-01-01T00:00:00.907
  • 上文提到UTC与GMT基本上等同,误差不超过0.9秒,这里就看看他们的区别,比如:UTC在格林尼治时间2016年12月31日23时59分59秒之后,还需要经过一个闰秒才会跨入新的一年,即UTC里你能看到2016-12-31 23:59:60的时间,但GMT里就不会。

国际标准ISO 8601

  • 在语言的时间工具库,我们一般会看到 ISO8601、UTC、RFC822、W3C等字样,其实对应不同的日期和时间表示方法的标准
  • ISO 8601 是国际标准化组织的日期和时间的表示方法,比如 2004-05-03T17:30:08+08:00或20040503T173008+08,即是一种日期/时间表示方法的规范,规定了一种明确的、国际上都能理解的日历和时钟格式。

上面介绍完毕,相信大部分理解日常时间和计算机时间的关系,这里额外再提几个时间,加深印象

历书时

  • 文艺复兴时代,欧洲也有一小时分成3/4/12份,16世纪末则每12秒/4秒为一个单位(水表嘀嗒一次),1656年惠更斯开始钟摆成为当时最精确计时工具(大约200日差一秒),这里的秒是钟摆重力加速度钟摆效应计时,但其实源头是是由天文时上的秒定义的。“秒”正式的定义出现在1675年意大利《世界测量》这部著作中,一秒被首次定义为一太阳日的1/86400,但星历表的精确是天文尺度上的,不能作为连续的秒级的计时工具。这就是最初的历书时。

地球时(Terrestrial Time, TT)

  • 原时(proper time),proper这里就是own的意思,原时即是固有时间,相对论之后人们意识到不同参考系观察某事件其对应时间可能不一致,而原时则特指 观察者的时钟和观察的事件,同处于一个系统中。比如引力和速度都会导致时间膨胀,如对于原子钟,如果离地2万千米,每天就快45微秒,TAI需要计算世界各地原子钟取得均值的一个原因就是这
  • 地球时本质是天文时间的标准,特指是从地球表面进行的天文观测的时间测量。是地球的地心座标时
  • 在许多天文计算中,时间常常是一种相对值,后者说相对的参考系,在现代,考虑到重力、速度、观察者坐标系不同,需要考虑相对论效应产生坐标时,对于天体运动,就有地心座标时这个概念,地球时与地心座标时的关系是线性的变化, 理解地心座标时超出大部分人认知范围,我们只需要知道,这是一个理想的观察坐标系中时间的定义,他是天文时,但是可用原子秒作为单位,并且是线性关系

好了,最后看看计算机的时间同步

网络时间协议(Network Time Protocol,NTP)

我们上面提到计算机自带诸多计时工具,同时有会通过网络同步时间,目前大部分同步时间就是这个NTP协议,自1985年以来被应用,NTP是目前仍在使用的最古老的互联网协议之一。NTP同步时间后,会修正系统时间。

  • NTP时钟同步的是什么?同步的是协调世界时(UTC),几毫秒的误差内。测算出网络延迟后,NTP通常能保持几十毫秒甚至一毫秒的误差。
  • 当前协议为版本4(NTPv4), NTP同步的实际是一个时间戳,但是协议包含内容丰富,可以参考这里NTP,不详述,需要指出的是这个协议是有2-bit标识闰秒的
  • 系统时间: 比如Linux 提供了两个系统时间:clock realtime 和 clock monotonic。
    • 前者就是可以被NTP或用户改变/设置的,顾名思义,wall time,真实时间,gettimeofday()使用的就是这个时间
    • JAVA的System.currentTimemillis也是通过gettimeofday获取这个时间(java8: hotspot/src/os/linux/vm/os_linux.cpp中的javaTimeMillis)
    • Redis 的过期计算用的也是这个时间
    • clock monotonic ,系统启动至今时间(ns),JAVA nanoTime默认使用就是这个时间(如果不支持monotonic则退化前者),如名字,是单调的,这里不再提及
    • 像Redis过期时间使用的显然是前者,因为仅有clock monotonic重启之后无意义了,除非再保存一个真实的clock realtime(当然如果都假设过期时间短,比如锁,是可以的),所以可以看到Redis讨论这里是不少的工作量
    • 这里附上一个 因为时间同步的Redis Slowlog问题
  • NTP校准时间支持ntpdate和ntpd 两种方式,前者是直接同步,所以闰秒时候就会出现跃迁,后者则是平滑的处理时间差,比如ntpd 当接收到需要闰秒回拨的时间时,会逐渐与服务端的时钟对齐(如每秒慢0.5ms)直到追平。

最后, 看了本文,人类在精确计时方面的的进步令人惊叹,但也该同时意识到,精确计时是不可能的,就像物理学规律在普朗克时间更小粒度下失效一样,就像原子钟精确计时恰恰证明了精确计时是不可能的。

** 遵循CC协议,转载请标注来源 **