处理闰秒

admin

引言

闰秒是对协调世界时(UTC)偶尔添加的一秒调整,以保持其与地球自转同步。这是软件开发中时间处理最复杂的方面之一。

快速总结:UTC 添加闰秒以保持在 UT1(太阳时)的 0.9 秒以内。截至 2026 年 1 月,自 1972 年以来已添加 37 个闰秒,使 UTC 比国际原子时(TAI)慢 37 秒。

什么是闰秒?

定义

一个闰秒是对 UTC 应用的一秒调整,用于考虑:

地球自转减速:地球自转逐渐变慢

不规律的自转速率:地球自转速度不可预测地变化

UT1-UTC 偏移:保持 UTC 在太阳时(UT1)的 0.9 秒内

CopyTEXT1闰秒公式:

2 如果 UT1 - UTC > 0.9 秒 → 添加正闰秒

3 如果 UT1 - UTC < -0.9 秒 → 添加负闰秒

4

5结果:UTC 保持与地球自转同步

闰秒如何工作

当添加闰秒时,UTC 日的最后一分钟有 61 秒而不是 60 秒:

CopyTEXT1普通日(无闰秒):

2 23:59:58 UTC

3 23:59:59 UTC

4 00:00:00 UTC(第二天)

5

6闰秒日:

7 23:59:58 UTC

8 23:59:59 UTC

9 23:59:60 UTC ← 闰秒!

10 00:00:00 UTC(第二天)

注意:负闰秒在实践中从未出现过,尽管如果地球自转突然加速,理论上是可能的。

闰秒历史

时间线

年份事件UTC-TAI 偏移1972首次添加闰秒+10 秒1972-1984添加 12 个闰秒+22 秒1985-1995添加 8 个闰秒+29 秒1996-2005添加 3 个闰秒+32 秒2008-2016添加 3 个闰秒+35 秒2017最近一次闰秒+36 秒2025未来闰秒+37 秒

最近的闰秒

CopyTEXT1所有闰秒(1972 - 2025):

2 - 1972-06-30: +1 秒(UTC-TAI = +11s)

3 - 1972-12-31: +1 秒(UTC-TAI = +12s)

4 - 1973-12-31: +1 秒(UTC-TAI = +13s)

5 - 1974-12-31: +1 秒(UTC-TAI = +14s)

6 - 1975-12-31: +1 秒(UTC-TAI = +15s)

7 - 1976-12-31: +1 秒(UTC-TAI = +16s)

8 - 1977-12-31: +1 秒(UTC-TAI = +17s)

9 - 1978-12-31: +1 秒(UTC-TAI = +18s)

10 - 1979-12-31: +1 秒(UTC-TAI = +19s)

11 - 1981-06-30: +1 秒(UTC-TAI = +20s)

12 - 1982-06-30: +1 秒(UTC-TAI = +21s)

13 - 1983-06-30: +1 秒(UTC-TAI = +22s)

14 - 1985-06-30: +1 秒(UTC-TAI = +23s)

15 - 1987-12-31: +1 秒(UTC-TAI = +24s)

16 - 1989-12-31: +1 秒(UTC-TAI = +25s)

17 - 1990-12-31: +1 秒(UTC-TAI = +26s)

18 - 1992-06-30: +1 秒(UTC-TAI = +27s)

19 - 1993-06-30: +1 秒(UTC-TAI = +28s)

20 - 1994-06-30: +1 秒(UTC-TAI = +29s)

21 - 1995-12-31: +1 秒(UTC-TAI = +30s)

22 - 1997-06-30: +1 秒(UTC-TAI = +31s)

23 - 1998-12-31: +1 秒(UTC-TAI = +32s)

24 - 2005-12-31: +1 秒(UTC-TAI = +33s)

25 - 2008-12-31: +1 秒(UTC-TAI = +34s)

26 - 2012-06-30: +1 秒(UTC-TAI = +35s)

27 - 2015-06-30: +1 秒(UTC-TAI = +36s)

28 - 2025-12-31: +1 秒(UTC-TAI = +37s)

闰秒的未来

国际电信联盟(ITU)正在考虑在 2035 年前废除闰秒,这将大大简化全球的时间处理。

重要:如果闰秒被废除,UTC 将逐渐偏离太阳时。这是天文学家、软件开发者和计时组织之间存在争议的话题。

TAI vs UTC

国际原子时(TAI)

TAI 是基于全球原子钟加权平均的时间标度。它从不包含闰秒,使其成为完全均匀的时间标度。

CopyTEXT1TAI 特征:

2 - 基于:全球 400+ 个原子钟

3 - 精度:±0.000000001 秒(1 纳秒)

4 - 闰秒:从不

5 - 用途:科学研究、精确同步

6

7当前 TAI-UTC 偏移:+37 秒(截至 2026 年 1 月)

TAI 和 UTC 之间的转换

CopyJAVASCRIPT1// 将 TAI 时间戳转换为 UTC 时间戳

2const TAI_OFFSET_SECONDS = 37; // 截至 2026 年

3

4function taiToUtc(taiTimestamp) {

5 return taiTimestamp - TAI_OFFSET_SECONDS;

6}

7

8function utcToTai(utcTimestamp) {

9 return utcTimestamp + TAI_OFFSET_SECONDS;

10}

11

12// 示例

13const taiTs = 1735689637;

14const utcTs = taiToUtc(taiTs); // 1735689600

15

16console.log('TAI 时间戳:', taiTs);

17console.log('UTC 时间戳:', utcTs);

CopyPYTHON1from datetime import datetime, timezone, timedelta

2

3TAI_OFFSET_SECONDS = 37 # 截至 2026 年

4

5def tai_to_utc(tai_timestamp):

6 return tai_timestamp - TAI_OFFSET_SECONDS

7

8def utc_to_tai(utc_timestamp):

9 return utc_timestamp + TAI_OFFSET_SECONDS

10

11# 示例

12tai_ts = 1735689637

13utc_ts = tai_to_utc(tai_ts) # 1735689600

14

15print(f'TAI 时间戳: {tai_ts}')

16print(f'UTC 时间戳: {utc_ts}')

编程中处理闰秒

JavaScript

JavaScript 的 Date 对象不直接支持闰秒。它将 23:59:60 的时间戳重复为 23:59:59。

CopyJAVASCRIPT1// JavaScript 中的闰秒处理

2const leapSecondDate = new Date('2016-12-31T23:59:60Z');

3

4// JavaScript 将此视为 23:59:59Z

5console.log(leapSecondDate.toISOString()); // "2016-12-31T23:59:59.000Z"

6

7// 解决方案:使用支持闰秒的库

8import { unix } from 'dayjs';

9import utc from 'dayjs/plugin/utc';

10import customParseFormat from 'dayjs/plugin/customParseFormat';

11

12// 注意:Day.js 也不原生支持闰秒

13// 考虑使用专门的计时库进行闰秒支持

Python

Python 的 datetime 模块对闰秒的支持有限。标准库无法表示 23:59:60。

CopyPYTHON1# Python 中的闰秒处理

2from datetime import datetime, timezone, timedelta

3

4# 标准 datetime 不支持闰秒

5try:

6 leap_second = datetime(2016, 12, 31, 23, 59, 60, tzinfo=timezone.utc)

7except ValueError as e:

8 print(f'错误: {e}') # ValueError: second must be in 0..59

9

10# 解决方案:使用专门的库

11# 对于真正的闰秒支持,考虑:

12# - astropy.time 用于科学应用

13# - 专门的时间处理库

Java

Java 8+ 的 java.time 包在 Instant 类中支持闰秒。

CopyJAVA1import java.time.Instant;

2import java.time.temporal.ChronoUnit;

3

4// Java 中的闰秒处理

5Instant leapSecondInstant = Instant.parse("2016-12-31T23:59:60Z");

6

7// Java 在 Instant 中正确处理闰秒

8System.out.println("闰秒: " + leapSecondInstant);

9

10// 检查时间戳是否包含闰秒

11Instant timestamp = Instant.parse("2016-12-31T23:59:60Z");

12boolean isLeapSecond = timestamp.getNano() == 0 &&

13 timestamp.getEpochSecond() % 60 == 59;

14

15System.out.println("是闰秒: " + isLeapSecond);

Go

Go 的 time 包没有原生的闰秒支持。

CopyGO1package main

2

3import (

4 "fmt"

5 "time"

6)

7

8func main() {

9 // Go 不原生支持闰秒

10 leapSecondStr := "2016-12-31T23:59:60Z"

11 _, err := time.Parse(time.RFC3339, leapSecondStr)

12

13 if err != nil {

14 fmt.Println("错误:", err)

15 // Go 将拒绝闰秒时间戳

16 }

17}

时间平滑(Time Smearing)

什么是时间平滑?

时间平滑是一种技术,用于将闰秒调整在一段时间内(通常 12-24 小时)逐渐分布,而不是瞬间应用它们。

CopyTEXT1传统闰秒:

2 23:59:58 UTC

3 23:59:59 UTC

4 23:59:60 UTC ← 瞬间跳跃

5 00:00:00 UTC(第二天)

6

7平滑闰秒(24 小时平滑):

8 每秒在 24 小时内延长约 1.16 毫秒

9 无瞬间跳跃,平滑过渡

平滑实现

系统平滑方法持续时间Google TrueTime线性平滑24 小时Amazon Time Sync Service线性平滑24 小时NTP 池可选平滑1-24 小时Linux内核步进(无平滑)瞬间

注意:时间平滑被大型分布式系统使用以避免同步问题。然而,它也会产生自己的问题:平滑后的时间不是标准 UTC,无法可靠地转换为其他时间系统。

IETF RFC 8536 推荐

RFC 8536 为软件系统中的闰秒处理提供了指导原则:

关键建议

内部时间使用 TAI:存储 TAI 时间戳以保持精度

仅在显示时转换为 UTC:仅在向用户显示时才应用闰秒偏移

使用 NTP 进行同步:从 NTP 服务器获取准确时间

文档化闰秒处理:清楚地记录你的系统如何处理闰秒

测试闰秒事件:在测试中模拟闰秒转换

最佳实践

CopyTEXT1对于大多数应用:

2 ✓ 使用 UTC 时间戳(存储时忽略闰秒)

3 ✓ 仅在需要时应用闰秒偏移(罕见情况)

4 ✓ 使用历史闰秒日期进行测试

5 ✓ 文档化你的闰秒策略

6

7对于高精度应用:

8 ✓ 存储 TAI 时间戳进行内部计算

9 ✓ 维护闰秒表

10 ✓ 显示时转换为 UTC

11 ✓ 使用 NTP 进行同步

常见问题和解决方案

问题 1:闰秒期间的时间跳跃

问题:系统在闰秒转换期间经历 1 秒的跳跃。

解决方案:使用时间平滑或实现闰秒感知。

CopyJAVASCRIPT1// 时间平滑示例(简化版)

2function smearedTime(timestamp, leapSecondDate) {

3 const diffHours = (timestamp - leapSecondDate) / (1000 * 60 * 60);

4 const smearDuration = 24; // 24 小时

5 const smearFactor = Math.min(Math.max(diffHours / smearDuration, 0), 1);

6

7 return timestamp + smearFactor * 1000; // 在 24 小时内添加最多 1 秒

8}

问题 2:数据库查询失败

问题:由于像 23:59:60 这样的时间戳在大多数数据库中无效,查询在闰秒期间失败。

解决方案:存储不带闰秒的时间戳,文档化闰秒行为。

CopySQL1-- 存储标准 UTC 时间戳(不含闰秒)

2CREATE TABLE events (

3 id INT PRIMARY KEY,

4 event_timestamp TIMESTAMP WITHOUT TIME ZONE, -- 标准 UTC

5 description TEXT

6);

7

8-- 处理闰秒:使用范围

9SELECT * FROM events

10WHERE event_timestamp BETWEEN '2016-12-31T23:59:59Z' AND '2017-01-01T00:00:01Z';

问题 3:闰秒期间的日志错误

问题:日志文件在闰秒期间显示重复或无序的时间戳。

解决方案:使用高分辨率时间戳和唯一序列标识符。

CopyPYTHON1# 带闰秒感知的日志记录

2import time

3from datetime import datetime

4

5def log_event(message):

6 # 使用毫秒精度处理闰秒

7 timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

8 sequence_id = time.time_ns() # 纳秒精度

9

10 print(f'[{timestamp}] [{sequence_id}] {message}')

按场景分类的代码示例

场景 1:使用闰秒偏移转换时间戳

CopyJAVASCRIPT1const LEAP_SECONDS = 37; // 截至 2026 年

2

3// 将 TAI 时间戳转换为可读的 UTC

4function taiToUtcString(taiTimestamp) {

5 const utcTimestamp = taiTimestamp - LEAP_SECONDS;

6 const date = new Date(utcTimestamp * 1000);

7 return date.toISOString();

8}

9

10// 示例

11const taiTs = 1735689637;

12console.log(taiToUtcString(taiTs)); // "2026-01-01T00:00:00.000Z"

CopyPYTHON1from datetime import datetime, timezone, timedelta

2

3LEAP_SECONDS = 37 # 截至 2026 年

4

5def tai_to_utc_string(tai_timestamp):

6 utc_timestamp = tai_timestamp - LEAP_SECONDS

7 utc_time = datetime.fromtimestamp(utc_timestamp, timezone.utc)

8 return utc_time.isoformat()

9

10# 示例

11tai_ts = 1735689637

12print(tai_to_utc_string(tai_ts)) # "2026-01-01T00:00:00Z"

场景 2:检查日期是否为闰秒

CopyJAVASCRIPT1const LEAP_SECOND_DATES = [

2 '1972-06-30', '1972-12-31', '1973-12-31', '1974-12-31',

3 '1975-12-31', '1976-12-31', '1977-12-31', '1978-12-31',

4 '1979-12-31', '1981-06-30', '1982-06-30', '1983-06-30',

5 '1985-06-30', '1987-12-31', '1989-12-31', '1990-12-31',

6 '1992-06-30', '1993-06-30', '1994-06-30', '1995-12-31',

7 '1997-06-30', '1998-12-31', '2005-12-31', '2008-12-31',

8 '2012-06-30', '2015-06-30', '2025-12-31'

9];

10

11function isLeapSecondDate(date) {

12 const dateStr = date.toISOString().split('T')[0];

13 return LEAP_SECOND_DATES.includes(dateStr);

14}

15

16// 示例

17const date = new Date('2016-12-31T23:59:59Z');

18console.log(isLeapSecondDate(date)); // true

CopyPYTHON1from datetime import datetime

2

3LEAP_SECOND_DATES = [

4 datetime(1972, 6, 30), datetime(1972, 12, 31),

5 datetime(1973, 12, 31), datetime(1974, 12, 31),

6 datetime(1975, 12, 31), datetime(1976, 12, 31),

7 datetime(1977, 12, 31), datetime(1978, 12, 31),

8 datetime(1979, 12, 31), datetime(1981, 6, 30),

9 datetime(1982, 6, 30), datetime(1983, 6, 30),

10 datetime(1985, 6, 30), datetime(1987, 12, 31),

11 datetime(1989, 12, 31), datetime(1990, 12, 31),

12 datetime(1992, 6, 30), datetime(1993, 6, 30),

13 datetime(1994, 6, 30), datetime(1995, 12, 31),

14 datetime(1997, 6, 30), datetime(1998, 12, 31),

15 datetime(2005, 12, 31), datetime(2008, 12, 31),

16 datetime(2012, 6, 30), datetime(2015, 6, 30),

17 datetime(2025, 12, 31)

18]

19

20def is_leap_second_date(date):

21 return any(

22 date.year == leap_date.year and

23 date.month == leap_date.month and

24 date.day == leap_date.day

25 for leap_date in LEAP_SECOND_DATES

26 )

27

28# 示例

29date = datetime(2016, 12, 31, 23, 59, 59)

30print(is_leap_second_date(date)) # True

测试闰秒处理

测试用例

CopyTEXT1测试 1:验证闰秒偏移

2 输入:TAI = 1735689637

3 预期:UTC = 1735689600(差异 = 37 秒)

4 状态:如果差异等于当前 UTC-TAI 偏移则 PASS

5

6测试 2:处理闰秒时间戳

7 输入:"2016-12-31T23:59:60Z"

8 预期:系统优雅处理(无崩溃、无数据损坏)

9 状态:如果无错误则 PASS

10

11测试 3:转换闰秒日期范围

12 输入:范围 [2016-12-31T23:59:59Z, 2017-01-01T00:00:01Z]

13 预期:返回范围内的所有事件,包括闰秒事件

14 状态:如果返回所有事件则 PASS

15

16测试 4:验证时间平滑

17 输入:接近闰秒的时间戳

18 预期:平滑过渡,无瞬间跳跃

19 状态:如果过渡平滑则 PASS

最佳实践总结

对于大多数应用

忽略闰秒在存储中(使用标准 UTC 时间戳)

文档化你的闰秒处理策略

测试使用历史闰秒日期

使用 UTC作为你的主要时间标准

对于高精度应用

存储 TAI 时间戳用于内部计算

维护闰秒表用于转换

使用 NTP进行同步

在关键代码路径中实现闰秒感知

相关工具

TAI 时间转换器 - 在 TAI、UTC 和 GPS 时间之间转换

当前时间戳 - 获取当前 UTC 时间,多种精度

Unix 时间戳转换器 - 处理各种时间戳精度

GPS 时间转换器 - GPS 时间,带闰秒处理

常见问题

Q: 闰秒多久发生一次?

A: 自 1972 年以来已发生 27 次闰秒(大约每 1-2 年一次),但由于地球自转减速,最近的频率已降低。

Q: 闰秒会永远继续下去吗?

A: ITU 正在讨论在 2035 年前废除闰秒,这将停止闰秒添加,但会导致 UTC 逐渐偏离太阳时。

Q: 我的应用中需要处理闰秒吗?

A: 对于大多数应用,不需要——使用标准 UTC 时间戳。只有构建关键时间系统、科学应用或分布式数据库时才需要处理闰秒。

Q: 闰秒期间会发生什么?

A: UTC 添加额外的秒(23:59:60)以保持与地球自转同步。大多数系统重复 23:59:59 或使用时间平滑来避免跳跃。

Q: 如何测试闰秒处理?

A: 测试使用历史闰秒日期,如 2016-12-31T23:59:60Z,验证你的应用不会崩溃或产生错误结果。

Copyright © 2088 中国战将网 - 顶级游戏活动平台 All Rights Reserved.
友情链接