撰文 | Snowino
我们知道,一年不总是365天。闰年的2月有29号。那你是否知道,一个昼夜循环也不严格是86400秒?以及,UTC / TAI / GPS时间为什么不相同?
1874年,在厘米-克-秒(CGS)单位之中,1秒被定义为一个平均太阳日的86400分之一。
1952年,国际天文学联合会(IAU)根据恒星年(sidereal year)定义1秒,现在被称为历书时(ephemeris time,ET)。1个恒星年就是地球绕太阳转一圈所需要的时间,也是太阳在恒星背景上回到原来位置所需要的时间(白天-黑夜转换时太阳相对于恒星背景位置几乎不变,一起转动,季节更替时间尺度范围内才能观察到太阳相对于恒星位置发生变化)。
1955年,IAU将1秒定义为1900年平均回归年(tropical year, 太阳年)的1/31556925.9747。1个回归年指太阳在天空中的轨迹还原所需要的时间,即两个春分点之间的时间。因为农业生产生活和太阳在天空中的位置息息相关,所以回归年和人类的生产活动更为密切。
1956年,国际计量协会(International Committee for Weights and Measures) 选用了一个更精确的数字,1秒=1回归年 / 31556925.9747
1960年,国际计量大会(General Conference on Weights and Measures)上,这个定义被引入国际单位制(SI)
1967年,为了达到更好的精度,国际单位制秒(SI second)被重新定义为铯-133原子超精细结构跃迁放出的光子的周期的9192631770倍。这个定义和天文(历书时)秒定义差别小于1e-10,并且和1750年至1892年之间的太阳日的平均长度的1/86400接近。
这里光子的周期可以通过其能量换算得到。
此时,我们有两套计时系统:
国际原子时(Temps Atomique International,TAI),每86400国际单位制秒经过一天;
世界时(Universal Time, UT),每个平太阳日(mean solar day)经过一天,即太阳每跨过本初子午线算一天。
在过去的几个世纪里,平太阳日每个世纪增加1.4-1.7毫秒。到1861年,平太阳日已经比86400国际单位制秒要长1~2毫秒。因此,国际原子时的日期时间会不断地比世界时的日期时间要超前。
1960年,世界协调时(Coordinated Universal Time, UTC)被提出,用于时间广播服务。它基于国际原子时,但是最开始人们认为UTC应该和UT保持一致,所以国际时间局(Bureau International de I’Heure,BIH)将UTC中的1秒定义为国际原子时1秒减去一个小的修正项。
1972年,UTC修正1秒长度的修正方法被闰秒策略替代。从1972年至今,一共有37个闰秒被引入,这样UTC时间就可以和UT时间保持一致。
到这里,我们就可以回答最开始那个问题了。
在计算机中,Unix时间被定义为从1970年1月1日本初子午线0点所对应的时刻,即epoch/纪元,到现在经过的秒数。该数目以国际单位秒计数。这个时间定义是明确的。
在TAI时间中,每经过86400秒为1天,多数年为365天,闰年为366天。
在笔者写这个文章的这个时刻记为t。
从可以查询到,t时刻对应的TAI时间和UTC时间分别为:
TAI时间:2023年10月29日14时17分37秒
UTC时间:2023年10月29日14时17分00秒
根据TAI,我们可以计算出已经过去了19659 TAI天,51457秒,或者1698589057秒。
而不考虑闰秒,根据UTC时间,我们可以计算出现在经过了1698589020秒,和TAI时间相比,少了37秒。
而国际时间局BIH从1972年至今刚好插入了37次闰秒,这样两个结果就吻合了。
那么怎么理解C++中time_t记录了“从纪元到所代表时刻经过的秒数,不包括闰秒“呢?
这里我们考察t=UTC时间1973年1月2日0点的unix时间。
BIH在1972年6月30日和12月31日插入了闰秒,而1972年为闰年,1970和1971年不是,所以从纪元到t时刻经过了恰好365×3+2 天 + 2 秒,即94780802秒
利用std::tm结构体,我们可以构造t时刻,并计算它的unix时间:
输出结果为94780800秒,而不是94780802秒。
UTC时间所对应的那个时刻是有明确定义的,这个时刻和纪元时刻之间的时间间隔因此也是确定的,是94780802秒,而不是94780800秒,因此C++程序中的计算结果是错误的。
那怎么理解C++程序中的时间和时刻呢?
C++中有两种记录时间的数据结构,time_t和tm结构体。
time_t记录了Unix时间,即纪元至某一时刻经过的秒数。
而tm结构体则记录了年份、月数、日期、小时、分钟、秒、是否考虑daylight saving。
当用gmtime函数将tm结构体转为time_t时,tm结构体时间被认为是UTC时间。
当使用localtime函数时,tm结构体时间被认为是UTC时间提前或推迟一段时间,即当地时间,时区信息则从TZ环境变量读取。
为了一致的理解C++中所记录的时刻,我们有两种方式:
一种方式,是尊重time_t,那么std::gmtime(&t)返回的tm结构体对应的时间就是TAI时间。使用std::mktime(&tm)时,需要先将UTC时间转换成TAI时间,然后传给mktime,以得到正确的time_t。
另一种方式,是尊重tm结构体,那么就要记住,std::mktime(&tm)返回的unix时间的秒数没有考虑闰秒,因此在某些时候所计算得到的时间间隔是错误的。这对于纳秒级的高精度实验可能是严重的问题。
考虑到这个问题,在C++20中,C++的高精度时间库引入了tai_clock和to_utc / from_utc函数,可以方便的在UTC时间和TAI时间之间转换。
而GPS时间和TAI时间则几乎一样。GPS时间被定义为UTC时间1980年1月6日0点到某一时刻经过的时间间隔,不受闰秒影响。现在GPS时间比UTC时间超前18秒,它总是比TAI时间慢19秒。
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777