【问题标题】:Will `gmtime()` report seconds as 60 when in a leap second?`gmtime()` 会在闰秒时将秒报告为 60 吗?
【发布时间】:2018-07-28 12:41:05
【问题描述】:

我有一个在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() 是否会为time_t = 915148800 返回tm == 1998-12-31T23:59:60,并且一旦超出闰秒,是否会为相同的time_t 返回tm == 1999-01-01T00:00:00

【问题讨论】:

  • Leap Second Smearing 也值得考虑。
  • @user2864740 太好了,谢谢你提出这个问题,知道gmtime 将如何处理这个问题也很重要。

标签: c time posix unix-timestamp utc


【解决方案1】:

简短的回答是,不,实际上gmtime_r 永远不会用 60 填充tm_sec。这是不幸的,但不可避免。

根本问题在于,根据 Posix 标准,time_t 是自 1970-01-01 UTC 以来的秒数假设没有闰秒

在最近的闰秒期间,进展是这样的:

1483228799    2016-12-31 23:59:59
1483228800    2017-01-01 00:00:00

是的,那里应该有闰秒,23:59:60。但是14832287991483228800 之间没有可能的time_t 值。

我知道gmtime 变体返回以:60 结尾的时间的两种方法:

  1. 您可以在 UTC 以外的其他时间运行操作系统时钟,通常是 TAI 或 TAI-10,并使用所谓的“正确”时区转换为 UTC(或本地时间)以进行显示。有关这方面的一些讨论,请参阅 this web page

  2. 1234563例如,获取介于14832287991483228800 之间的时间值的方法是将tv_sec 设置为1483228799,将tv_nsec 设置为1000000000。更多详情请见this web page

方式#1 运行良好,但没有人使用它,因为没有人想在它应该是的 UTC 以外的任何地方运行他们的内核时钟。 (您最终会遇到文件系统时间戳等问题,以及嵌入这些时间戳的 tar 等程序。)

方式 #2 是一个绝妙的想法,IMO,但据我所知,它从未在已发布的操作系统中实现过。 (碰巧,我有一个适用于 Linux 的实现,但我还没有发布我的工作。)要让方法 #2 工作,你需要一个新的gmtime 变体,也许是gmtime_ts_r,它接受@987654349 @ 而不是 time_t


附录:我刚刚重读了您的问题标题。您问:“当服务器处于闰秒时,gmtime() 会报告 60 秒吗?”我们可以通过说“是的,但是”来回答这个问题,并声明由于大多数服务器无法正确表示闰秒期间的时间,因此它们从不“开启”闰秒。 p>


附录 2:我忘了提到方案 #1 似乎更适合当地时间 - 也就是说,当您调用 localtime 变体之一时 - 比 UTC 时间和 gmtime 更好。显然localtime 执行的转换受TZ 环境变量设置的影响,但尚不清楚TZgmtime 有什么影响。我观察到一些gmtime 实现受到TZ 的影响,因此可以根据“正确”区域执行闰秒,而有些则不能。特别是,GNU glibc 中的gmtime 似乎会注意“正确”区域中的闰秒信息,如果TZ 指定了一个,而IANA tzcode distribution 中的gmtime 则没有。

【讨论】:

  • 关于“gmtime_r 永远不会用 60 填充 tm_sec”,这是不正确的。见coliru.stacked-crooked.com/a/622da23fd57dabca
  • @Yuki 对。看起来你在我完成我的回答之前得到了你的评论。这就是#1。
  • @SteveSummit 它说明了这种方法有什么问题!通常gmtime() 不应该关心$TZ 的设置; “正确”的时区是一个奇怪的例外。
  • “正确”的时区大错特错。这不仅仅是“正常”; gmtime 甚至没有允许依赖于$TZ
【解决方案2】:

我在 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,包括闰秒,精确的秒数,会很有用。要将“自纪元以来的秒数”转换为年、月、日、小时、分钟、秒,需要了解自纪元以来的所有闰秒(或者如果您处理在此之前的时间,则在纪元之前)。一个时钟将返回保证准确的格林威治标准时间,没有闰秒和几乎但不是完全恒定的时间。

【讨论】:

  • 如果我没记错的话,UTC 是 GMT 时区的时间标准。所以“gmtime() 不能同时在 UTC 和 GMT 中工作。”,似乎gmtime() 在 GMT 时区的 UTC 中同时工作。
  • @Yuki 计算机上的 GMT 时区(伦敦、都柏林、里斯本)几乎是 UTC,除了大多数实现在闰秒时间和计算 UTC 秒数时出错闰秒两边的时间。在计算机上使用字母“GMT”来描述这个时区,严格来说是不准确的; GMT 更像 UT1,而不是 UTC。他们多年前做出的调用函数gmtime 的选择是不幸的,因为它的规范......英国冬季的民用时间是UTC,而不是GMT。
  • 在这样的讨论中,术语“UTC”和“GMT”之间没有有用的区别。 UTC 是一个非常精确定义的术语。 “GMT”可以表示以下两种情况之一:(2) 与 UTC 完全相同。 (2)他们在冬季使用的英国时区,就像“EST”是他们在冬季使用的纽约时区一样。 C gmtime 函数确实更适合命名为 utctime
  • @gnasher729 如果你想要“另一个将返回精确 UTC 的时钟,包括闰秒,精确秒”,你可以拥有它,但是你不能使用time_t来代表它的价值!
  • 另外,如果您使用的是自一个纪元以来的秒数(也就是说,如果您使用的是 time_t 之类的东西),并且如果您想正确地使用闰秒,请不要想象您正在使用 UTC!如果你从一个纪元开始严格计算秒数,你可能正在做 TAI。 (然后,是的,如果您想从 TAI 转换为 UTC 年、月、日、小时、分钟、秒,您将需要闰秒表。)
【解决方案3】:

他们问题的另一个角度是拥有一个“了解”闰秒的库。大多数库都没有,因此您从 gmtime 等函数获得的答案严格来说在闰秒期间是不准确的。此外,时差计算通常会产生跨越闰秒的不准确结果。例如,昨天在同一 UTC 时间提供给您的 time_t 值恰好比今天的值小 86400 秒,即使实际上存在闰秒。

天文学界已经解决了这个问题。这是SOFA Library,里面有适当的时间程序。请参阅their manual (PDF),有关时间表的部分。如果成为您软件的一部分并保持最新状态(每个新的闰秒都需要一个新版本),您将获得准确的时间计算、转换和显示。

【讨论】:

  • 拥有一个好的库(例如您引用的 SOFA)当然很重要,但如果您处理的时间戳来自您的操作系统和操作系统,这并不一定足够不正确支持闰秒。如果gmtime 在闰秒期间为您提供了不准确的时间,部分原因是操作系统无法在闰秒期间为您提供准确的时间戳,部分原因是输入到gmtimetime_t 值无法代表闰秒。 SOFA 和其他闰秒感知库必须使用time_t 以外的时间表示。
  • @SteveSummit,这都是真的,但情况比以前好。 Linux + GPSD + NTPD + clock_gettime(CLOCK_TAI) + SOFA 是回避标准 POSIX 库和 *nix 中的“传统”时钟/时间函数的所有问题的一种方法。这种组合为您提供正确的时间、正确的时间本地化表示以及正确的时间计算。如果一个人在现代 linux 内核上用 C/C++ 开发一个新程序,并且连接了正确的 GPS 硬件并避免 glibc 的例程,这是一个很好的解决方案。它无法修复现有软件、其他语言等。
  • 啊,SOFA用clock_gettime(CLOCK_TAI)吗?你是对的,这是一条好路。 (CLOCK_TAI 还不是很安全——例如,参见this question 中的谜题——但它肯定会出现。)
  • @SteveSummit,我不确定;我不认为它有任何获取时间的方法,它只是能够在所有主要时间尺度上正确处理、转换和计算。虽然我相信从 clock_gettime(CLOCK_TAI) 得到的值可以用来生成 SOFA TAI 时间对象,之后一切都很容易。我们已经成功地使用 GPS + gpsd + ntpd 从 clock_gettime(CLOCK_TAI) 中获得了一个可信的结果,尽管我们必须等待 GPS 历书进来(大约 12 分钟后开机!)。
  • 是的,所有这些更新都很痛苦。 (1) 就我个人而言,我认为clock_gettime(CLOCK_TAI) 如果不知道taioffset,应该返回-1。 (如果内核不知道 taioffset 直到 NTP 告诉它有一个闰秒,之后它认为 taioffset 为 1,那就更糟了。)(2)我有一个实验性内核,其中硬编译了闰秒表,但是通过/proc 中的一个特殊文件暴露了——用于读取更新。但是还有很多细节需要解决。
【解决方案4】:

问题是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 系统倾向于通过几种方式处理闰秒。最常见的是:

  1. 闰秒重复一个time_t 值。 (这是对标准进行严格解释的结果,但会导致许多应用程序出现故障,因为时间似乎倒退了。)

  2. 系统时间在闰秒周围的一段时间内运行稍慢,以在更广泛的时期内“涂抹”闰秒。 (此解决方案已被许多大型云平台采用,包括GoogleAmazon。它避免了任何本地时钟不一致,但代价是受影响的系统在持续时间内与 UTC 不同步最多半秒。 )

  3. 系统时间设置为 TAI。由于这不包括闰秒,因此不需要闰秒处理。 (这种情况很少见,因为它会使系统与构成世界大部分地区的 UTC 系统不同步几秒钟。但对于与外界几乎没有联系的系统来说,这可能是一个可行的选择,因此无法得知即将到来的闰秒。)

  4. 系统完全不知道闰秒,但它的 NTP 客户端将在闰秒使系统时钟与正确时间相差一秒后更正时钟。 (This is what Windows does.)

【讨论】:

  • “将 time_t 转换为 struct tm 将总是产生...”这是不正确的,否则规范中的秒数不会为 60。跨度>
  • "time_t is TAI",这也不对,time_t 不是 TAI,time_t 是 Unix 时间戳,这些是不同的东西。 TAI 总是单调的,而time_t 不是。
  • @Yuki 1) 这是历史错误的结果。一些较旧的系统在时区数据中存储了闰秒表,理论上这将允许localtime() 执行闰秒校正。谢天谢地,这被认为是疯狂的,不再常用。
  • @Yuki 2) 我认为你误解了我对 TAI 的评论。我要说的是,一些系统使用 TAI 作为系统时钟(因此time_t),不需要处理闰秒。由于这使得转换为 UTC 变得困难,因此并不常见。
  • 现代 Linux 提供来自 ntpd 的闰秒更新的实时源(例如通过 gpsd)保持准确的CLOCK_TAI。这可以使用clock_gettime() 检索。您从中得到的 time_t 可以与正常的 time_t 进行比较,以计算出当前的闰秒偏移量。
【解决方案5】:

POSIX 以不允许闰秒或 TAI 的方式精确地指定 time_t "Seconds since the Epoch" 值和分解 (struct tm) 时间之间的关系,因此本质上(至于什么含糊不清)应该发生在闰秒附近),POSIX time_t 值是 UT1,而不是 UTC,gmtime 的结果反映了这一点。真的没有办法适应或改变它与现有规范和基于它们的现有软件兼容。

几乎可以肯定,正确的前进方向是 Google 对 leap second smearing 所做的混合,以及用于在 24 日“模糊 UTC”和“实际 UTC”时间(以及 TAI)之间来回转换的标准化公式- 闰秒周围的小时窗口和执行这些转换的 API。

【讨论】:

  • 这是错误的标准,它可以追溯到正确解决问题太困难的时候(没有互联网,没有 GPS 时间源等)。然而事情已经发生了变化,现在这是一个可解决的问题(Linux 现在有一个准确的 CLOCK_TAI,其他操作系统也可以)。标准可以更新,计算机应该将 CLOCK_TAI 作为系统时钟。这意味着要更改大量软件。我们所拥有的是由美国牵头的取消 UTC 闰秒的举措,这意味着 UTC(即民用时间)将在几十年和几个世纪内与 UT1 有所不同。英国不赞成这样做!
  • 关于 CLOCK_TAI 作为标准,TAI 本身已经标准化了近 50 年。所以这不像是最近的创新。本质上,软件行业在保持 TAI 太难时使用的必要条件成为公认的标准,并且在任何时候都没有任何拥有足够权威的人认为适合质疑该标准。叹息。
  • 如果您不关心闰秒(因为大多数人和大多数程序都不关心),那么是的,涂抹是要走的路。但是,如果您确实关心闰秒,那么尝试消除拖尾可能还不够好,因为不可能完美地扭转拖尾。 (这在 LEAPSECS 邮件列表中进行了讨论;抱歉,我没有引用结束此内容的确切消息。)
  • 将系统时钟与外部参考时间同步到 1 微秒以内需要对时钟硬件和电信设备进行大量投资,这比破解系统以使用变通方法来处理 UTC 闰秒要费力得多。让国际监管机构通过采用没有闰秒的 UTC 重新定义日历日,比破解系统使用没有闰秒的时间刻度需要付出更多的努力。在这些问题上达成普遍共识比创建新的时间尺度来满足不需要精确时间和频率的系统需要付出更多的努力。
  • @bazza,实际上没有。在很长一段时间内,POSIX 选择计算日历天数,而这些当前是 UT1 的平均太阳日,因此在很长一段时间内,POSIX 秒是平均太阳秒。
【解决方案6】:

对此绝对没有简单的答案。为了在有闰秒时有 60 秒,您需要 1)操作系统中的某些东西知道有闰秒到期,以及 2)您使用的 C 库也知道闰秒,以及用它做点什么。

很多操作系统和库都没有。

我发现最好的是现代版本的 Linux 内核与 gpsd 和 ntpd 结合使用,使用 GPS 接收器作为时间参考。 GPS 在其系统数据流中通告闰秒,并且 gpsd、ntpd 和 Linux 内核可以在闰秒发生时维护 CLOCK_TAI,并且系统时钟也是正确的。我不知道 glibc 是否对闰秒做了明智的事情。

在其他 UNIX 上,您的情况会有所不同。相当。

Windows 是 ******* 灾区。例如,C# 中的 DateTime 类不知道历史闰秒。下次收到网络时间更新时,系统时钟将跳变 1 秒。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-15
    • 2012-07-02
    • 1970-01-01
    • 1970-01-01
    • 2015-11-12
    • 1970-01-01
    相关资源
    最近更新 更多