时间发展史
早期计时方案
人类对时间的测量始于对自然周期的观察。早期文明通过月相和太阳的运动,将时间划分为日、月、年等可管理的单位。
随着社会发展,更精细的时间划分成为必要。古埃及和巴比伦人受到以12为基数的数字系统影响,将一天分为24小时。此后,1小时被划分为60分钟,1分钟又被划分为60秒,形成了沿用至今的时间框架。
原子钟时间
虽然早期方法沿用了数个世纪,但其核心问题在于精度不足。20世纪50年代原子钟的发明彻底改变了这一局面。原子钟依据铯原子的振动来测量时间,提供了无与伦比的精确度。

图1:显示时间、温度、湿度等信息的数字时钟
基于原子钟,秒被重新定义为:
铯-133原子基态的两个超精细能级之间跃迁对应的辐射的9,192,631,770个周期的持续时间。
这一定义后来成为了国际原子时(TAI)的基础。
JavaScript 中的几种时间标准
UT(世界时)
世界时(Universal Time, UT)是一种基于地球自转的时间系统,但其准确性存在固有缺陷,主要原因如下:
- 地球自转的不均匀性
- 长期减慢:由于潮汐摩擦等因素,地球自转速度在长期上逐渐减慢,导致一天的实际长度缓慢增加。
- 短期波动:大气运动、地震、冰川融化等也会引起地球自转速度的短期波动。
- 天文观测的不确定性
- 观测误差:通过观测恒星位置来确定世界时,会受到天气、仪器精度等因素影响。
- 数据处理:复杂的观测数据处理和校正过程也可能引入误差。
TAI(国际原子时)
原子时直接源自原子钟,基于稳定的物理现象,既精确又一致,是现代计时系统的核心支柱。
UTC 时间
1972年,协调世界时(Coordinated Universal Time, UTC)被引入,用以协调世界时与原子时之间的差异。UTC在概念上与世界时保持一致,但会通过增加或删除闰秒来补偿地球不规则的自转。
GMT(格林威治标准时间)是基于地球自转的一种时间标准,常被认为是UTC的前身。
闰秒是为了保持UTC与地球自转同步而偶尔添加(或理论上删除)的1秒调整,确保两者误差在0.9秒以内。然而,闰秒给依赖精确计时的系统带来了诸多复杂性。

图2:展示全球不同时区分布的世界地图
UTC是一个全球统一的时间标准,本身不考虑时区差异,但可以通过加减时区偏移量来转换为本地时间。
在JavaScript中,Date.UTC()静态方法接受与Date构造函数相似的参数,但将其视为UTC时间,并返回自1970年1月1日00:00:00 UTC以来的毫秒数。
const utcDate1 = new Date(Date.UTC(96, 1, 2, 3, 4, 5));
const utcDate2 = new Date(Date.UTC(0, 0, 0, 0, 0, 0));
console.log(utcDate1.toUTCString());
// 输出: "Fri, 02 Feb 1996 03:04:05 GMT"
console.log(utcDate2.toUTCString());
// 输出: "Sun, 31 Dec 1899 00:00:00 GMT"
console.log('The time in the UTC+0 timezone is' + utcDate1.toISOString()); // UTC 0时间,即ISO格式
console.log('Your local time is' + new Date().toLocaleString()); // 本地时间
JavaScript 中的秒时长
ECMAScript 采用 POSIX 时间,忽略闰秒
ECMAScript标准定义的JavaScript时间系统基于POSIX(可移植操作系统接口)时间。这意味着日期和时间以自Unix纪元(1970年1月1日00:00:00 UTC)以来经过的毫秒数来衡量,并且不包括闰秒。
const start = Date.now();
console.log("starting timer...");
// Date.now() 静态方法返回自纪元以来经过的毫秒数
setTimeout(() => {
const millis = Date.now() - start;
console.log(`seconds elapsed = ${Math.floor(millis / 1000)}`);
// Expected output: "seconds elapsed = 2"
}, 2000);
该系统固定认为每天有86,400秒(24小时),无视闰秒和地球自转的天文变化。虽然这简化了计算并能满足大多数应用需求,但在要求极端精确计时的场景下存在局限。
POSIX 时间 ≠ UTC 时间
尽管POSIX和UTC都基于相同的纪元起点,但两者存在核心区别:

图3:以UTC为基准的世界时区地图
- POSIX忽略闰秒:UTC通过插入闰秒来对齐地球自转,而POSIX时间始终假设每天为86,400秒。即使在UTC日长为86,401或86,399秒的闰秒日,POSIX时间也保持不变。
- 时间精度:POSIX时间通常精确到秒(或毫秒),而UTC时间可精确到纳秒级。
- 时区处理:两者都基于零时区,时区转换均通过加减偏移量实现。
- 简化互操作性:POSIX假设所有秒的持续时间相等,使得计算时间差变得简单直接,但在发生闰秒时会产生偏差。
例如,在UTC时间23:59:60(闰秒)这一刻,POSIX时间会完全忽略这一秒,导致闰秒附近的时间戳可能出现歧义或不准确。
// ISO 8601格式的UTC时间,'Z'表示零时区(UTC+0)
const utcDate = new Date("2021-10-01T00:00:00Z");
// 手动加上时区偏移(例如,东八区,+8小时)
const offsetHours = 8;
const localTime = new Date(utcDate.getTime() + offsetHours * 60 * 60 * 1000);
console.log("Local time with manual offset:", localTime.toString());
闰秒的处理方案
步进调整:传统解决方案
历史上,需要处理闰秒的系统常采用“步进调整”技术。该方法直接忽略闰秒,导致系统时钟在闰秒发生时突然向前或向后跳跃一秒。
例如下表所示:

图4:UTC、TAI与POSIX(步进)时间对应关系表
从表中可见,TAI和UTC时间都是严格单调递增的,但POSIX(步进)时间在闰秒处会发生跳跃。这导致两个不同的UTC时刻可能映射到同一个POSIX时间戳,从而引发问题。
假设在23:59:59.600发起付款请求,并在下一秒23:59:60.200批准。在步进调整的系统中,日志可能显示:
- 315619199.600 - 请求付款
- 315619199.200 - 已批准付款(时间戳反而更早)
这种时间“倒流”现象正是大多数现代系统寻求其他解决方案的原因。
闰秒涂抹:谷歌的平滑方案
在许多系统中,采用称为“闰秒涂抹”的技术来处理闰秒。其核心思想不是突然增加或减少一整秒,而是将这一秒的偏差均匀分摊到更长的时间段(例如24小时)内。
- 在涂抹期间,每一秒的时长会略微变长或变短,从而确保系统时间平稳过渡,避免跳跃。
- 与步进调整相比,涂抹能保证时钟严格递增,满足多数应用对单调时间的要求。

图5:展示了步进调整与平滑涂抹处理闰秒的对比
上表中,POSIX(步进)完全忽略23:59:60这一秒。而POSIX(平滑)则通过微调每秒的时长,实现了时间的平稳过渡,代价是每秒的物理时长略有不同。这正是Google的NTP服务器处理闰秒的方式。
时间精确性对应用程序的影响
通常无需特别关注的情况
对于事件调度、计算年龄、或在用户界面中显示时间等常见任务,JavaScript基于POSIX的时间系统已足够精确。实践中,绝大多数时间相关错误实际上源于时区处理不当,而非秒长的微小差异。
需要特别关注的场景
然而,对于科学研究、高频交易、分布式系统同步等对时间精度要求极高的领域,由闰秒引起的微小差异可能导致严重后果。
精确的计时对于确保测量和交易的可靠性至关重要。在闰秒期间,单个POSIX时间戳可能对应UTC中的两个不同瞬间,这在需要严格时间同步的系统中会引入歧义和潜在错误。
希望本文能帮助你更深入地理解JavaScript中的时间系统。更多关于前端开发与网络协议的深度讨论,欢迎访问云栈社区进行交流。
参考资料