我有一个服务器在TZ=UTC
运行,我有这样的代码:
time_t t = time(NULL);
struct tm tm;
gmtime_r(&t, &tm);
当服务器处于闰秒时,问题是tm.tm_sec == 60
吗?
例如,如果我在以下时间段内:
1998-12-31T23:59:60.00 - 915 148 800.00
1998-12-31T23:59:60.25 - 915 148 800.25
1998-12-31T23:59:60.50 - 915 148 800.50
1998-12-31T23:59:60.75 - 915 148 800.75
1999-01-01T00:00:00.00 - 915 148 800.00
gmtime()
会tm == 1998-12-31T23:59:60
为time_t = 915148800
返回tm == 1999-01-01T00:00:00
,并且一旦退出闰秒,就会返回同一time_t
的{{1}}吗?
答案 0 :(得分:11)
简短的回答是,不,实际上gmtime_r
永远不会用tm_sec
填写60.这是不幸的,但不可避免。
根本问题是,根据Posix标准,time_t
是自1970-01-01 UTC 以来没有闰秒以来的秒数。
在最近的闰秒期间,进展如下:
1483228799 2016-12-31 23:59:59
1483228800 2017-01-01 00:00:00
是的,那里应该有一个闰秒23:59:60
。但time_t
和1483228799
之间不存在1483228800
值。
我知道gmtime
变体有两种方式可以返回以:60
结尾的时间:
您可以在UTC以外的其他设备上运行您的操作系统时钟,通常是TAI或TAI-10,并使用所谓的"右"时区转换为UTC(或本地时间)进行显示。有关此问题的讨论,请参阅this web page。
您可以使用clock_gettime()
并定义新的clkid值,可能是CLOCK_UTC
,在必要时使用故意非标准化的time_t
值来解决struct timespec
问题。例如,在1483228799
和1483228800
之间获取时间值的方法是将tv_sec
设置为1483228799
,将tv_nsec
设置为1000000000
。有关详细信息,请参阅this web page。
方式#1工作得很好,但没有人使用它,因为没有人想在除了它应该是的UTC以外的任何东西上运行内核时钟。 (您最终会遇到诸如文件系统时间戳之类的问题,以及嵌入这些时间戳的tar
等程序。)
gmtime
变体,也许gmtime_ts_r
,接受struct timespec
而不是time_t
。
附录:我刚读过你的问题标题。你问,"当服务器处于闰秒时,gmtime()
会报告60秒吗?"我们可以通过说"是,但是"来回答这个问题,因为大多数服务器都无法在闰秒期间正确表示时间,因此他们不会永远不会 "上"闰秒。
附录2:我忘了提到方案#1似乎在当地时间更好 - 也就是说,当您调用其中一个localtime
变种时 - 而不是UTC时间和{ {1}}。显然,gmtime
执行的转化会受到localtime
环境变量设置的影响,但TZ
对TZ
的影响并不明显。我观察到一些gmtime
实施受gmtime
影响,因此可以根据"右"进行闰秒。区域,有些不能。特别是,GNU glibc中的TZ
似乎注意到"权利"中的闰秒信息。如果gmtime
指定了一个区域,而IANA tzcode distribution中的TZ
则没有。
答案 1 :(得分:5)
当服务器处于闰秒时,问题是
tm.tm_sec == 60
吗?
没有。在典型的UNIX系统上,time_t
计算自纪元(1970-01-01 00:00:00 GMT)以来非跳跃秒的数量。因此,将time_t
转换为struct tm
将始终产生时间结构,其tm_sec
值介于0到59之间。
忽略time_t
计算中的闰秒,可以将time_t
转换为人类可读的日期/时间,而无需充分了解该时间之前的所有闰秒。它还可以在将来明确转换time_t
值;包括闰秒会使这种情况变得不可能,因为在未来的6个月内,闰秒的存在是不可知的。
UNIX和类UNIX系统有几种方法可以处理闰秒。最典型的是:
闰秒重复一个time_t
值。 (这是严格解释标准的结果,但会导致许多应用程序出现故障,因为时间似乎已经倒退。)
系统时间在闰秒周围的某段时间内运行稍微慢一些,以及#34;涂抹"跨越更广泛时期的闰秒。 (此解决方案已被许多大型云平台采用,包括Google和Amazon。它避免了任何本地时钟不一致,但代价是让受影响的系统与UTC同步不到半秒持续时间。)
系统时间设置为TAI。由于这不包括闰秒,因此不需要闰秒处理。 (这种情况很少见,因为它会使系统与UTC系统几秒钟不同步,这些系统构成了世界上大部分地区。但对于与外界几乎没有联系的系统而言,它可能是一个可行的选择,因此没有办法学习即将到来的闰秒。)
系统完全不知道闰秒,但是在闰秒使系统的时钟离开正确的时间一秒后,其NTP客户端将纠正时钟。 (This is what Windows does.)
答案 2 :(得分:2)
绝对没有简单的答案。当闰秒有60秒时,你需要1)OS中的某些东西知道有一个闰秒到期,2)对于你使用的C库也知道闰秒,和用它做点什么。
很多操作系统和库都没有。
我发现最好的是现代版本的Linux内核与gpsd和ntpd合作,使用GPS接收器作为时间参考。 GPS在其系统数据流中公布闰秒,gpsd,ntpd和Linux内核可以在闰秒发生时保持CLOCK_TAI,系统时钟也是正确的。我不知道glibc是否在闰秒时做了明智的事情。
在其他UNIX上,您的里程会有所不同。相当。
Windows是一个灾难性的灾区。例如,C#中的DateTime类不知道历史闰秒。下次收到网络时间更新时,系统时钟将跳1秒。
答案 3 :(得分:2)
POSIX指定time_t
“Seconds,因为Epoch”值和细分(struct tm
)时间之间的关系完全以不允许闰秒或TAI的方式,所以基本上(直到关于在闰秒附近应该发生什么的一些模糊性,POSIX time_t
值是UT1,而不是UTC,gmtime
的结果反映了这一点。实际上没有办法适应或改变这与现有规范和基于它们的现有软件兼容。
正确的前进方式几乎可以肯定是谷歌与leap second smearing所做的混合,以及在“模糊的UTC”和“实际的UTC”时间之间来回转换的标准化公式(以及TAI)闰秒的24小时窗口以及执行这些转换的API。
答案 4 :(得分:1)
我在www.cplusplus.com上读到了关于gmtime:“使用timer指向的值来填充tm结构,其中的值代表相应的时间,表示为UTC时间(即GMT时区的时间) )”。
所以有一个矛盾。 UTC具有绝对恒定长度的秒数,因此需要闰秒,而GMT具有完全86,400秒的非常微小变化的天数。 gmtime()不能同时在UTC和GMT中工作。
当我们被告知gmtime()返回“UTC假设没有闰秒”时,我认为这意味着GMT。这意味着没有记录闰秒,这意味着时间慢慢地从UTC偏离,直到差异大约为0.9秒并且在UTC中添加闰秒,但在GMT中没有。这对开发人员来说很容易处理,但不太准确。
另一种选择是持续秒,直到你接近闰秒,然后在闰秒周围调整1000秒。它也很容易处理,大部分时间都是100%准确,有时1000秒内有0.1%的误差。
第二种选择是持续秒,有闰秒,然后忘记它们。因此gmtime()将连续两次返回相同的秒,从x秒0纳秒到x秒999999999纳秒,然后再从x秒0纳秒到x秒999999999纳秒,再到x + 1秒。哪会造成麻烦。
当然,有另一个时钟将返回精确的UTC,包括闰秒,精确的秒数,将是有用的。要将“自纪元以来的秒数”翻译为年,月,日,小时,分钟,秒,需要知道自纪元以来的所有闰秒(如果您在此之前处理时间,则需要在纪元之前)。并且一个将返回的时钟保证精确的GMT,没有闰秒和几秒但几乎不是恒定的时间。
答案 5 :(得分:0)
他们问题的另一个角度是拥有一个关于闰秒'知道'的库。大多数图书馆都没有,所以从gmtime这样的函数中得到的答案严格地说是在闰秒期间不准确。此外,时差计算通常会产生跨越闰秒的不准确结果。例如,昨天在同一UTC时间给你的time_t的值正好比今天的值小86400秒,即使实际上有闰秒。
天文学界解决了这个问题。这是SOFA Library,其中包含适当的时间例程。请参阅their manual (PDF),有关时间表的部分。如果成为您软件的一部分并保持最新(每个新的闰秒都需要新版本),您可以获得准确的时间计算,转换和显示。