![UNIX 秒、TAI (SI) 秒、闰秒和实际代码](https://linux22.com/image/88367/UNIX%20%E7%A7%92%E3%80%81TAI%20(SI)%20%E7%A7%92%E3%80%81%E9%97%B0%E7%A7%92%E5%92%8C%E5%AE%9E%E9%99%85%E4%BB%A3%E7%A0%81.png)
我最近读了很多有关 UNIX Time 的内容,其中大部分内容语无伦次,大部分内容相互矛盾。我正在尝试协调 UNIX 时间(以下简称为 UXT)、TAI 和 UTC 之间的转换,为此,我需要正确理解 UXT。问题是,我似乎找不到其他人这样做。
以下是我的最佳解释,是通过繁琐的研究从无数来源中重建的。也有哪里不对的地方。 我正在寻找对以下内容的整体分析和逐点验证/反驳。本质上,修复以下内容以使其正常工作。
TAI 是单调递增的时间标准。它以 SI 秒为单位,并忽略 DST 和闰秒。
UTC 与 TAI 相同,但通过整数个闰 SI 秒进行修正(转换为时间字符串反映为第 60 秒),以便与天文时间标准 UT1 的误差在 0.9 SI 秒以内。
UXT 是一个计数UNIX 秒自 1970 年 1 月 1 日 00:00:00 UTC 起。每天总是正好有 86400 秒。然而,UXT 与 UTC 相关。
这怎么可能?出色地,UNIX 秒需要与 SI 秒不同,并且由于闰秒并不完全规则,因此 UNIX 秒不能是明确定义的时间长度。
从 UTC 到 UXT 的转换UNIX 规范第 4.15 条将不同的 UTC 时间别名为相同的 UXT 时间戳,有效地使 UNIX 秒与 SI 秒相同(UNIX 闰秒除外,它是两个 SI 秒)。
在实践中,实际发生的情况各不相同。大多数计算机基于远程服务器进行同步,因此它们在同步期间隐式处理闰秒更新。所有这些意味着,虽然每个单独的 UXT 时间戳都可以轻松地与 UTC 相互转换(使用
gmtime
或§4.15,分别),你不能真正使用它们进行算术来找出任何东西。尤其,difftime
返回 UNIX 秒,所以你不能用它做任何事情,包括将它添加到不同的时间戳,除非你知道所有相关的闰秒在哪里。
我想到目前为止我已经明白了。
- 但现在我们看看实际的代码,它根本没有做任何这些事情。我可以理解人们使用测量持续时间
difftime
,只是希望它足够好(或者不知道存在问题),但计时库也是错误的。
作为一个例子,libtai
tai_now.c:7
提供从 UXT 到 TAI 的转换 ( ),如下所示:TAI := 4,611,686,018,427,387,914 + UXT
。由于 TAI 计时 SI 秒,而 UXT 计时 UNIX 秒,因此您无法执行此操作。然而,由于libtai
明确处理闰秒,因此这是一个粗心的错误似乎并不合理。
它不是特定于libtai
.这种事你随处可见。
所以:第 1-6 点与第 7 点不一致。也就是说,大量现有代码与它所代表的时间标准相矛盾。 什么地方出了错?
答案1
问题是大多数文档不使用可以区分时间尺度而没有歧义句子的词汇。我建议http://www.ucolick.org/~sla/leapsecs/picktwo.html作为对这个问题的介绍,在历史使用中,有两种不相关的秒——一种是地球居民日历日的细分,另一种是在特定参考系中测量的恒定持续时间。任何跨越 1970 年之前和之后日期的时间库都试图利用这两种秒,最终提供的答案类似于声称提供 arcsin(-2) 的函数——也就是说,有所涉及的复杂性需要仔细解释和定义到底什么是重要的。
答案2
出色地 。 。 。未来的你在这里,写作是为了一种结束的感觉。
在你写这个问题几年后,但还是在今天之前几年,你写了一个巨大的教程这解决了这个问题。它构成了现代时间标准的完整引导,从 TAI 到 UTC 到偏移量和 UXT。
至于你的问题,看来你的第1-6点确实基本正确。你的第7点可以概括为“软件不可能就这么不擅长,对吧?”
剧透警告:它可以。关于 libtai 的具体抱怨并不是一个很好的例子 - 该库包含闰秒,但只能通过组合适当的函数来实现,而不仅仅是您查看的单个函数。对我们来说,这感觉像是一个巨大的(并且明显令人困惑的)缺陷,即使它最终得到了正确的答案(谁知道它是否正确),但喋喋不休是没有用的,因为这种事情非常常见。不仅大多数软件实际上会出错,而且软件会以不同的、不兼容的方式出错!
哎呀,文档对于基本的 UXT 原语,time(...)
,甚至还不清楚。标准 C 文档(参考),(参考),遵循 POSIX 标准(部分)(参考)说这是自纪元以来的秒数。但 POSIX 标准中的其他地方(参考),它不是秒数,而是从纪元开始的秒数,不包括闰秒,这是正确的表征。这个想法是为了让计算变得更简单,但它显然让计算变得更困难,因为减法不起作用当您在数轴上引入不连续性时。difftime(...)
例如,任何使用 的程序都必然是不正确的。
这里的关键思维模型是 SI 秒和 UNIX 秒是不同的。这一点在 POSIX 规范的另一个地方最为清楚。(参考)。本节通过日期时间转换将 UXT 与 UTC 挂钩。 UTC 包括闰秒,但本节还指定天的长度为 86 400 秒。我们怎样才能调和这一点呢?关键的认识是,有些 UNIX 秒的长度是 2 SI 秒,有些是 0(显然,绝大多数是 1)。还困惑吗?去阅读教程吧!现实世界的代码如此混乱也就不足为奇了。
您很遗憾无法引导读者编写代码。您编写了自己的计时库,但它既复杂又强大,而且还没有真正适合生产。
我想,首先,调用time(...)
以获取 UNIX 秒的 POSIX 纪元相对计数,减去 220 924 790,然后添加闰秒(从表中)以获得 SI 秒的 TAI 纪元相对计数。反向执行即可返回。 (注:TAI 纪元有些模糊;我选择 1977-01-01 00:00:00 TAI(即 1976-12-31 23:59:45 UTC),因为这是标准化 TAI 的纪元, JD TAI、TCB、TCG、TT 和 TDB(参考).)
在 C++20 中,我们将有std::chrono::tai_clock
,纪元为 1958-01-01 00:00:00 TAI(即 1957-12-31 23:59:50 UTC)。希望这是正确的,我们可以把这整个肮脏的事情抛在脑后。
答案3
你是对的,这种混乱是可怕的。我的结论:
UXT 和 UTC 相同,除了闰秒期间。在那里,UTC 数到 60,而 UTX 只是“挂起”了一秒钟。
答案4
对你的心理健康来说,最好的办法是假设每天有 86,400 秒,无一例外,这对大多数人来说已经足够了。如果您现在计算时间,减去 5,000 乘以 86,400 秒,您将得到与 5,000 天前完全相同的小时、分钟和秒。
现在 Posix 时间是 UTC,但删除了闰秒。这意味着它总是非常接近 UT1(太阳时),最多相差 0.9 秒。最好的心智模型是,每次在 UTC 中添加或删除闰秒时,在 Posix 时间中,我们都会有一个 2 秒长的秒,或者一个 0 秒长的秒。您可以观察到,如果您恰好在闰秒发生的那一刻使用秒表 - 根据 Posix 的说法,根据您的秒表,某一分钟将需要 61 或 59 秒。或者,如果您的计算机显示带有秒的时钟,则秒数字会静止一秒或在某个时刻跳动两秒。在闰秒被删除的那一刻之外,您无法观察到这一点。 Posix 系统中没有这些变化的痕迹。
这什么时候错了?如果有一场 100m 比赛,在正确的时间点,并且您记录了开始和结束时间,根据 Posix 时间,有人可以在开始后 9.317 秒完成比赛。实际上,这将是 10.317 秒。因此,如果您有物理数据,那么时间间隔可能会误差一秒。
你怎样才能解决它?解决这个问题的唯一方法是建立一个插入闰秒的表格,并用它来将“没有闰秒的 UTC”更改为 TAI。
请注意,Posix 时间不是真的没有闰秒的 UTC,但是您的系统关于 UTC 是什么的最佳信息。您也许可以“手动”更改计算机上的时钟,并且 Posix 时间将关闭。您计算机上的时钟可能有点不准确,并由时间服务器纠正,在这种情况下,它每天可能会偏离 UTC 两秒,然后再纠正回来。
我有一个应用程序存在这个问题,并且 iOS(可能通常在 Posix 中)有一个 sysctl 调用返回启动时间。当您的时钟发生变化时,该函数会返回更改后的日期,除了每秒变化一秒之外。当您重新启动计算机时,它还会返回更改的日期。因此,您可以存储最后一个已知值,并在启动时间发生变化时询问时间服务器。