闰秒简介

2014-08-30 language c/cpp

闰秒,是为保持协调世界时接近于世界时时刻,由国际计量局统一规定在年底或年中对协调世界时增加或减少 1 秒的调整。

由于地球自转的不均匀性和长期变慢性,当世界时和原子时之间相差超过到 ±0.9 秒时,就把协调世界时向前拨 1 秒或向后拨 1 秒。

闰秒的调整也就是意味着 UTC 与 TAI(国际原子时) 之间的差异,截止到 2016 年,两者已经相差 37 秒。

闰秒

首先,看下我们当前对秒的定义。

  • 1956 年,秒定义为平均太阳日的 1/86400;
  • 1960 至 1967 年之间,定义为 1960 年地球自转一周时间的 1/86400;
  • 1967 年,国际计量大会决定,以铯原子基态的两个超精细能级间在零磁场下跃迁辐射 9,192,631,770 周期所持续的时间;
  • 1977 年,人类认识到了引力时间膨胀会导致在不同高度的原子钟时间不同,重新定义为 “水平面的铯原子” 基态的两个超精细能级间在零磁场下跃迁辐射 9,192,631,770 周期所持续的时间;
  • 2019 年,国际时间频率咨询委员会将讨论“秒”的重新定义问题。

随着人类认知的进步,对秒的定义逐渐发生了变化,从 1967 年开始,时间的计量标准便正式由天文学的宏观领域过渡到了物理学的微观领域。

常见概念

接着看下与闰秒相关的概念。

UT (Universal Time,世界时)

一种以格林尼治子夜起算的平太阳时,由于以地球自转为基准,观测精度受限于地球的自转速度的稳定,地球体积不均匀、潮汐引力以及其他星球的扰动的原因,导致地球转速不稳定,每日误差达数毫秒。

TAI (International Atomic Time) 国际原子时

1971 年建立,利用某些元素(如铯、氢、铷)的原子能级跃迁频率有极高稳定性的特性定义时间标准,现为国际计量局 (BIPM) 的时间部门维持,综合全球约 60 个实验室中的大约 240 台各种自由运转的原子钟提供的数据进行处理,得出 “国际时间标准” 称为国际原子时 (TAI),每日误差为数纳秒。

TAI 时间原点为 UT 1958.01.01 00:00:00,在此之后 TAI 就沿着原子秒的节拍一直走下去,和 UT 误差也越来越大。

UTC (协调世界时)

如上所述,现在有两种时间计量系统:基于天文测量而得出的 “世界时” 和以物理学发展发现的原子振荡周期固定这一特性而来的 “原子时” 。

UTC 就是用于将世界时 (UT,天文时间) 和国际原子时 (TAI,原子时间) 协调起来另外一套计量系统。1971 年国际计量大会通过决议,使用 UTC 来计量时间,协调的原则就是 UTC 以原子秒长节拍,在时刻上尽量接近于世界时 (UT)。

目前 UTC 是事实上的时间标准,比如所有计算机中的时间就是 UTC 时间,通过时区换算为本地时间,而闰秒实际上就是 UTC 特有的。

闰秒

闰秒简单来说,就是为了让 UTC 尽量靠近 UT 时间做出的人为调整。

详细来说,世界时依靠天文观测因此误差较大 (每日数毫秒),而原子时依靠原子的物理特性,相对精度较高 (每日误差几个纳秒),UTC 是以原子秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。

为了确保 UTC 与 UT 尽量接近,保证误差小于 0.9 秒,当 UTC 与 UT 之间的误差超过 0.6 秒时,国际地球自转服务组织 (IERS) 就会决定在最快到来的闰秒调整日期 (目前基本都是 6.30 或 12.31) 的最后一分钟,对 UTC 增加或减少一秒,也就是这一分钟将变为 61 秒或 59 秒, 这个增加或减少的一秒即为闰秒。

闰秒操作

如果增加一秒,称为正闰秒,会在调整日的 23:59:59 后额外插入一个 23:59:60,然后再到次日的 00:00:00,这样当日最后一分钟就有 61 秒。

如果减少一秒,则为负闰秒,会在调整日的 23:59:58 秒后跳过 23:59:59 这一秒,直接到达次日 00:00:00, 这样当日最后一分钟就只有 59 秒。

当前为止所有的闰秒调整都是正闰秒,也就是说地球转的越来越慢了。

系统状态

注意,闰秒的插入是在 UTC 时间,所以不同的时区闰秒写入时间会有所区别。

# ntpq -crv
associd=0 status=4519 **leap_add_sec**(leap_none), sync_ntp, 1 event, leap_armed(clock_sync),
version="ntpd 4.2.6p5@1.2349-o Mon Feb  6 07:34:43 UTC 2017 (1)",
processor="x86_64", system="Linux/3.10.0-514.10.2.el7.x86_64", leap=00,
stratum=3, precision=-18, rootdelay=70.647, rootdisp=142.974,
refid=59.46.44.253, reftime=dcb86ce1.cc4a9654  **Sat, May  6 2017 23:40:17.798**,
clock=dcb86dd0.e933c60d  Sat, May  6 2016 23:44:16.910, peer=41148, tc=6,
mintc=3, offset=-18.414, frequency=16.806, sys_jitter=83.733,
clk_jitter=49.584, clk_wander=6.979, tai=35, leapsec=201507010000,
expire=201512280000

由于闰秒是整数加入,因此 UTC 与 TAI 之间会出现若干整数秒差别,从 1958.01.01 00:00:00 这一 TAI 时间开始,到 1970.01.01 00:00:00 (epoch time,UNIX 0 second),UTC 已经比 TAI 慢了 10 秒,后经过多次闰秒调整,2015.07.01 00:00:00 开始 (闰秒调整后),UTC 将比 TAI 慢 36 秒。

在内核中,可以通过 "inserting leap second" 关键词查看。

另外,需要注意,内核日志显示的是 23:59:60 这一闰秒,实际验证时发现不一定会出现 23:59:60 这一时间点,而是和本机的 tzdata 版本有关,如果未升级到包含闰秒信息的最新版本的话,结果会是重复一次 23:59:59 。

可以通过 while true; do date; sleep 1; done 命令查看。

影响

对于普通人来说,这一秒对于正常的工作、学习来说,简直是无关痛痒,不会像闰年、闰月使得人们可以直接感受到。

相对于对时间要求比较精确的航空、航天、军工、互联网等,影响就会比较大了,可能会导致 CPU 突然彪高、机器宕机、服务瘫痪等。

调整过程

开源的 NTPD 在同步服务器时间时,会直接调用系统调用 adjtimex() 接口处理,内核在 do_adjtimex() 经过一系列处理逻辑,判定在发生闰秒时,并调用 printk() 给出打印提示。

printk(KERN_NOTICE "Clock: inserting leap second 23:59:60 UTC\n");
printk(KERN_NOTICE "Clock: deleting leap second 23:59:59 UTC\n");

时区相关的信息通过 tzdata 进行保存,这个是由 IANA 维护,定期发布相关版本。

程序影响

Linux 系统提供两种系统时间:A) Wall Time 记录的是真实事件,会受到时间调整的影响;B) Montonic Time 记录的是系统从启动以来的秒数,只能向前递增。

因此如果应用程序在获取时间时用的 Wall Time,那么就会受到系统时间调整 (如闰秒) 的影响,而采用后者则不会受到影响。

注意,闰秒只是为了协调 GAI 和 UTC 的差值,如果在事后查看闰秒跳变之间的秒数,实际上是没有变化的。

应对策略

NTP

建议升级到 4.2.2p1-9 以上的版本,低版本会直接通知内核直接增加一秒或者停止一秒。

使用 -x 参数运行,表明闰秒不会直接在内核中添加,而是把这一秒分散到大约 2000 秒中,缓慢的更改时间。这样做的优点是:

  • 避免时间倒退造成的应用逻辑问题;
  • 不会出现比较奇怪的时间点 23:59:60;
  • 由于实际上没有增加闰秒,所以内核可能的 bug 也不会出现。

这样做的缺点是:

  • 在调整的时间段内,一秒的长短实际上是不准确的,所以对时间精度要求高的场景就不适用了;
  • 由于 ntp 的更新机制,这个办法同样也不适用于那些要求所有节点时间一致的集群。

注意:如果主机上运行了 ntp 客户端,那么可以运行 file /etc/localtime,确保输出结果中有 no leap seconds 输出,表明不会添加闰秒。

闰秒模拟

闰秒信息会通过 hpiers.obspm.fr 这个网站公布。

闰秒前一天,NTP 服务器会通知其客户端第二天发生闰秒,例如在北京时间 2015.6.30 发生闰秒,则在 2015.6.29 会收到上一级的通知,闰秒标记位已经插入。

可以通过如下命令进行检查是否对发出闰秒标记位。

ntpq -c "lassoc" -c "mrv &1 &999 leap,srcadr,stratum"

当看到 leap=01 的时候,闰秒即将发生,其它的标志位有:

  • 00 No waring
  • 01 Last minute of the day has 61 seconds
  • 10 Last minute of the day has 59 seconds
  • 11 Unknown (clock unsynchronized)

也可以通过 ntptime | egrep 'INS|DEL' 标志位查看是否被设置。