Skip to main content

Appendix D. Leap Seconds (闰秒)

本附录详细说明闰秒的概念、历史和在RFC 3339中的处理方式。

什么是闰秒?

闰秒 (Leap Second) 是偶尔添加到协调世界时 (UTC) 中的一秒,用于使UTC时间与地球自转保持同步。

为什么需要闰秒?

原子时 (TAI):
- 基于原子钟,极其稳定
- 1秒 = 9,192,631,770个铯原子振荡周期
- 永不变化

地球自转:
- 不是完全均匀的
- 受潮汐摩擦等因素影响
- 逐渐变慢(每世纪约1.4毫秒/天)
- 速率不可预测

问题:
如果UTC仅基于原子时,几百年后UTC时间将与太阳时(地球自转)
显著偏离,最终可能在"中午"天还是黑的!

闰秒的运作方式

闰秒可以是正闰秒(添加一秒)或负闰秒(减少一秒),但到目前为止只添加过正闰秒。

正闰秒示例

正常的月末最后一分钟:
23:59:58
23:59:59
00:00:00 (下一天)

有正闰秒的月末:
23:59:58
23:59:59
23:59:60 ← 闰秒!
00:00:00 (下一天)

RFC 3339表示

1990-12-31T23:59:60Z  ✅ 有效(1990年12月31日有闰秒)
2012-06-30T23:59:60Z ✅ 有效(2012年6月30日有闰秒)
2015-06-30T23:59:60Z ✅ 有效(2015年6月30日有闰秒)

2023-12-31T23:59:60Z ❌ 无效(2023年12月31日没有闰秒)

历史闰秒列表

自1972年引入UTC以来的所有闰秒:

年-月-日          UTC时间               TAI-UTC
1972-06-30 23:59:60Z +11秒
1972-12-31 23:59:60Z +12秒
1973-12-31 23:59:60Z +13秒
1974-12-31 23:59:60Z +14秒
1975-12-31 23:59:60Z +15秒
1976-12-31 23:59:60Z +16秒
1977-12-31 23:59:60Z +17秒
1978-12-31 23:59:60Z +18秒
1979-12-31 23:59:60Z +19秒
1981-06-30 23:59:60Z +20秒
1982-06-30 23:59:60Z +21秒
1983-06-30 23:59:60Z +22秒
1985-06-30 23:59:60Z +23秒
1987-12-31 23:59:60Z +24秒
1989-12-31 23:59:60Z +25秒
1990-12-31 23:59:60Z +26秒
1992-06-30 23:59:60Z +27秒
1993-06-30 23:59:60Z +28秒
1994-06-30 23:59:60Z +29秒
1995-12-31 23:59:60Z +30秒
1997-06-30 23:59:60Z +31秒
1998-12-31 23:59:60Z +32秒
2005-12-31 23:59:60Z +33秒
2008-12-31 23:59:60Z +34秒
2012-06-30 23:59:60Z +35秒
2015-06-30 23:59:60Z +36秒
2016-12-31 23:59:60Z +37秒

注意:

  • 闰秒只在6月30日或12月31日添加
  • 自1972年以来共添加了27个闰秒
  • 最近的闰秒是2016年12月31日
  • 闰秒的添加频率在减缓

编程中的闰秒处理

问题1: 大多数系统不支持闰秒

# Unix时间戳不表示闰秒
# 1990-12-31T23:59:59Z 和 1991-01-01T00:00:00Z
# 之间的时间戳相差1秒,而不是2秒

import time
from datetime import datetime

# 许多编程语言和操作系统会"抹平"闰秒
# 例如,在闰秒时刻,系统时钟可能会:
# 23:59:59 → 23:59:59 (停顿1秒) → 00:00:00
# 或者: 23:59:59 → 23:59:60 → 00:00:00
# 或者: 23:59:59 → 00:00:00 (跳过闰秒)

问题2: 解析23:59:60

# 标准库通常不支持60秒
from datetime import datetime

try:
dt = datetime(1990, 12, 31, 23, 59, 60)
# 大多数实现会抛出ValueError
except ValueError as e:
print(f"Error: {e}")
# 可能的解决方案:
dt = datetime(1990, 12, 31, 23, 59, 59)
# 或者:
dt = datetime(1991, 1, 1, 0, 0, 0)

宽容的解析实现

def parse_rfc3339_with_leap_seconds(timestamp_str):
"""
宽容地解析RFC 3339时间戳,包括闰秒
"""
# 检查是否包含闰秒
if ':60' in timestamp_str:
# 将60秒替换为59秒
timestamp_str = timestamp_str.replace(':60', ':59')
# 解析并添加1秒
dt = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
return dt + timedelta(seconds=1)
else:
return datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))

# 示例
ts1 = parse_rfc3339_with_leap_seconds("1990-12-31T23:59:60Z")
ts2 = parse_rfc3339_with_leap_seconds("1991-01-01T00:00:00Z")
print(ts1 == ts2) # True (它们表示相同的时间点)

RFC 3339的闰秒规范

语法要求

time-second = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second rules
  • 秒值 可以 (MAY) 是60
  • 但仅在月末
  • 且必须是已公布的闰秒

验证示例

# 已知的闰秒日期(需定期更新)
LEAP_SECONDS = {
(1990, 12, 31),
(1992, 6, 30),
(1993, 6, 30),
(1994, 6, 30),
(1995, 12, 31),
(1997, 6, 30),
(1998, 12, 31),
(2005, 12, 31),
(2008, 12, 31),
(2012, 6, 30),
(2015, 6, 30),
(2016, 12, 31),
# 未来的闰秒需要添加
}

def is_valid_leap_second(year, month, day, hour, minute, second):
"""验证闰秒是否有效"""
if second != 60:
return True # 不是闰秒,不需要特殊验证

# 闰秒只能出现在23:59:60
if hour != 23 or minute != 59:
return False

# 必须是月末
if (month, day) not in [(6, 30), (12, 31)]:
return False

# 必须是已公布的闰秒日期
return (year, month, day) in LEAP_SECONDS

获取最新闰秒信息

IERS公告

国际地球自转和参考系统服务 (IERS) 负责宣布闰秒:

官方网站: https://www.iers.org/
公告: Bulletin C

闰秒通常提前6个月宣布

NTP闰秒指示器

NTP (Network Time Protocol) 服务器在闰秒前24小时设置闰秒指示器:

# NTP客户端可以查询闰秒指示器
import ntplib

c = ntplib.NTPClient()
response = c.request('pool.ntp.org')
leap_indicator = response.leap
# 0: 无警告
# 1: 最后一分钟有61秒(正闰秒)
# 2: 最后一分钟有59秒(负闰秒)
# 3: 闹钟状态(时钟未同步)

闰秒的争议和未来

争议

支持者:
- 保持UTC与太阳时同步
- 避免天文观测混乱

反对者:
- 造成技术系统问题
- 无法预测(需提前6个月公布)
- 许多系统实现不正确

可能的未来变化

2022年决议:
国际电信联盟(ITU)投票决定在2035年或之后
停止闰秒的插入

可能的替代方案:
1. 每100年插入一次"闰小时"
2. 允许UTC与太阳时逐渐偏离
3. 重新定义UTC

实施建议

1. 宽容地接受

# 接受60秒,但转换为标准表示
def normalize_leap_second(dt_str):
if ':60' in dt_str:
# 将23:59:60转换为下一天的00:00:00
# 或保留为23:59:59 + note
pass

2. 使用UTC

# 始终使用UTC进行内部存储
# 避免本地时区的复杂性
from datetime import timezone
dt = datetime.now(timezone.utc)

3. 测试边界情况

# 测试闰秒的前后时刻
test_cases = [
"1990-12-31T23:59:59Z",
"1990-12-31T23:59:60Z", # 闰秒
"1991-01-01T00:00:00Z",
]

4. 监控IERS公告

定期检查https://www.iers.org/ 获取闰秒公告。


关键要点: 闰秒是UTC时间系统的一个复杂特性。虽然RFC 3339语法上支持闰秒,但大多数系统实现会遇到困难。实现应宽容地处理闰秒,并保持与权威时间源(如NTP)的同步。