高精度时间

W3C 工作草案,

关于本文件的更多信息
当前版本:
https://www.w3.org/TR/2026/WD-hr-time-3-20260324/
最新发布版本:
https://www.w3.org/TR/hr-time-3/
编辑草案:
https://w3c.github.io/hr-time/
之前版本:
历史记录:
https://www.w3.org/standards/history/hr-time-3/
反馈:
public-web-perf@w3.org 邮件主题行为 “[hr-time-3] … message topic …” (邮件归档)
GitHub
规范内问题
测试套件:
https://wpt.live/hr-time/
编辑者:
Yoav Weiss (Shopify)
前任编辑:
(Google LLC)
(Google LLC)
(Microsoft Corp.)

摘要

本规范定义了一个 API,可提供时间原点与亚毫秒分辨率的当前时间,并且不会受到系统时钟偏移或调整影响。

本文档状态

本节描述了本文档在发布时的状态。 当前所有 W3C 发布物和本技术报告的最新修订版 可见于W3C 标准与草案索引

本文档由Web 性能工作组推荐路径 发布为工作草案。 作为工作草案发布 并不代表 W3C及其成员的认可。

此为草案文件, 随时可能被其他文件 更新、替代或废弃。 除作为进行中工作外,不宜引用本文件。

GitHub Issues为本规范讨论的首选方式。

本文档受 2025年8月18日 W3C 流程文件约束。

本文档由依据 W3C 专利政策运作的团体产生。 W3C 维护着 所有相关专利披露的公开列表, 该页面还包括披露专利的方法说明。 任何知晓某专利且认为其包含 必要声明 的个人, 须根据 W3C 专利政策第 6 条 披露相关信息。

1. 引言

本节为非规范性内容。

ECMAScript 语言规范 [ECMA-262] 定义了 Date 对象, 它表示自 1970 年 1 月 1 日 UTC 起的毫秒数型时间值。 对于大部分用途,这样的时间定义已足够,因为这些值可表示自 1970 年 1 月 1 日 UTC 起约 285,616 年内任意时间点的毫秒精度。

实际上,这些时间定义会受到时钟漂移和系统时钟调整的影响。时间值不一定总是单调递增,后续数值可能降低,或停滞不变。

例如,以下脚本计算的 duration 可能为正、为负或为零:

var mark_start = Date.now();
doTask(); // Some task
var duration = Date.now() - mark_start;

对于某些任务,这种时间定义并不充分,因为:

本规范并不建议更改 Date.now() [ECMA-262] 的行为, 因它对于获取日历当前值十分有用,且有着长期使用历史。 DOMHighResTimeStamp 类型, Performance.now() 方法, 以及 Performance.timeOrigin 属性通过提供具备亚毫秒分辨率的单调递增时间值,解决了上述问题。

提供亚毫秒分辨率并非本规范的强制要求。实现可出于隐私与安全原因选择限制其暴露的定时器分辨率,而不暴露亚毫秒定时。依赖亚毫秒分辨率的用例在这种情况下可能无法满足。

1.1. 应用场景

本节为非规范性内容。

本规范定义了几种不同能力:它通过稳定、单调的时钟提供时间戳、支持上下文间比较,并支持亚毫秒分辨率(如实现允许的话)。

性能测量需要稳定的单调时钟,因为无关的时钟漂移会扭曲测量结果,甚至使其失效。例如,要精确测量导航到 Document、获取资源或脚本执行的耗时,需要具备亚毫秒分辨率的单调递增时钟。

在不同上下文间比较时间戳也非常重要,例如 Worker 与主线程之间协作,或对这些工作进行分析以获取统一事件时间线视图时。

对亚毫秒定时器的需求主要包括以下场景:

1.2. 示例

本节为非规范性内容。

开发者可能希望构建整个应用的时间线,包括 WorkerSharedWorker 上下文产生的事件,这些上下文拥有不同的 时间原点。 要在同一个时间线展示这些事件,应用可借助 Performance.timeOrigin 属性对 DOMHighResTimeStamp 进行换算。

// ---- worker.js -----------------------------
// Shared worker script
onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(e) {
    // Time execution in worker
    var task_start = performance.now();
    result = runSomeWorkerTask();
    var task_end = performance.now();
  }

  // Send results and epoch-relative timestamps to another context
  port.postMessage({
    'task': 'Some worker task',
    'start_time': task_start + performance.timeOrigin,
    'end_time': task_end + performance.timeOrigin,
    'result': result
  });
}

// ---- application.js ------------------------
// Timing tasks in the document
var task_start = performance.now();
runSomeApplicationTask();
var task_end = performance.now();

// developer provided method to upload runtime performance data
reportEventToAnalytics({
  'task': 'Some document task',
  'start_time': task_start,
  'duration': task_end - task_start
});

// Translating worker timestamps into document’s time origin
var worker = new SharedWorker('worker.js');
worker.port.onmessage = function (event) {
  var msg = event.data;

  // translate epoch-relative timestamps into document’s time origin
  msg.start_time = msg.start_time - performance.timeOrigin;
  msg.end_time = msg.end_time - performance.timeOrigin;

  reportEventToAnalytics(msg);
}

2. 时间概念

2.1. 时钟

时钟追踪时间的流逝,可报告 算法执行时的不安全当前时间。 时钟种类很多。Web 平台上的所有时钟都试图保证 1 毫秒时钟与真实世界 1 毫秒等值,但它们遇到无法完全精确时的处理方式不同。

挂钟时间不安全当前时间

总是尽可能接近用户的时间概念。由于计算机有时会变慢或变快,或失去对时间的跟踪,其 挂钟时间 有时需要被调整,这意味着 不安全当前时间 可能会减少,因此不适合用于性能测量或记录事件顺序。Web 平台与 挂钟时间 共享 [ECMA-262]time

单调时钟不安全当前时间

绝不会减少,因此不会因为系统时钟的调整而变化。单调时钟 只存在于 用户代理 的单次执行周期内,所以无法用于比较可能发生在不同执行周期中的事件。

由于 单调时钟 无法调整为用户的时间概念,应当用于测量用途而非用户可见时间。任何与用户交流的时间都应当使用挂钟时间。

用户代理在浏览器重启、启动一个隔离的浏览会话(如隐身或类似模式)或创建无法与任何现有设置对象通信的 环境设置对象时,可以选择新的 Unix 纪元的单调时间估算值。因此,开发者不应将共享时间戳作为跨所有过去、现在和未来上下文均保持单调特性的绝对时间;实际上,单调特性只适用于能够通过现有消息机制互相通信的上下文,例如 postMessage(message, options)BroadcastChannel等。

在某些场景下(如标签页被置于后台),用户代理可能会选择减少该上下文中的定时器和周期回调的运行频率,甚至全部冻结。任何这类节流都不应影响单调时钟所返回时间的分辨率或精度。

2.2. 时刻与时长

每个 时钟不安全当前时间 返回一个 不安全时刻时间粗化会将这些 不安全时刻 转换成 粗化时刻 或简称 时刻。由不同时钟产生的 不安全时刻时刻 是不可比较的。

时刻不安全时刻 表示时间点,也就是说它们不能直接存储为数字。实现通常会把 时刻 表示为距离某个其他固定时间点的 持续时间,但规范应直接处理 时刻本身。

持续时间 表示从一个 时刻到同一 时钟上的另一个时刻的距离。两个端点都不能是 不安全时刻,以确保 持续时间及其差值能够缓解[§ 9.1 时钟分辨率]中的问题。 持续时间以毫秒、秒等单位度量。由于所有 时钟都试图以相同的速度计数,持续时间没有关联的 时钟,并且如果某个 持续时间由一个时钟上的两个 时刻计算得到,则可以加到另一个时钟上的某个 时刻,以得到该第二个 时钟上的另一个 时刻

ab 的持续时间 是通过以下算法得到的:

  1. 断言: a 由与 b 相同的 时钟 创建。
  2. 断言: ab 均为 粗化的时刻
  3. 返回从 ab 的时间量,作为一个 持续时间。如果 ba 之前,则这将是一个负的 持续时间

持续时间可被隐式当作DOMHighResTimeStamp 使用。 隐式转换持续时间为时间戳时, 给定一个 持续时间 d,返回 d 的毫秒数。

3. 规范作者工具

若要在单个页面内(单个 环境设置对象的上下文中)进行时间测量,使用 settingsObject当前相对时间戳, 定义为 settingsObject时间原点settingsObject当前单调时间持续时间。此值可通过 将持续时间隐式转换DOMHighResTimeStamp 直接暴露给 JavaScript。

若要在单个 UA 执行过程中测量时间,但是某个 环境设置对象时间原点不适合作为比较的基础时, 可通过 时刻创建, 使用 环境设置对象当前单调时间。 一个 环境设置对象 settingsObject当前单调时间 由以下步骤得到:

  1. unsafeMonotonicTime单调时钟不安全当前时间
  2. 返回调用 粗化时间,参数为 unsafeMonotonicTime 以及 settingsObject跨源隔离能力

时刻 来自 单调时钟 无法在 JavaScript 或 HTTP 中被直接表示。 相反,应公开两个此类 持续时间 之间的 时刻

为了在多个 UA 执行之间测量时间,创建 时刻 ,使用 当前粗化墙钟时间 或(如果在 跨源隔离上下文 中需要更高精度) 使用 环境设置对象当前墙钟时间当前粗化墙钟时间 是调用 粗化时间 并以 墙钟不安全的当前时间 作为参数所得的结果。

环境设置对象 settingsObject当前挂钟时间 由以下步骤得到:

  1. unsafeWallTime挂钟时间不安全当前时间
  2. 返回调用 粗化时间,参数为 unsafeWallTime 以及 settingsObject跨源隔离能力

当使用来自墙钟时刻时,请确保你的 设计考虑到用户将其时钟向前或向后调整的情况。

来自墙钟时刻, 可以通过将该时刻距离 Unix 纪元 的毫秒数,传入 Date 构造函数的方式在 JavaScript 中表示,或者将 该时刻距离 Unix 纪元的纳秒数, 传入 Temporal.Instant 构造函数也可。[Temporal]

避免在计算机之间传递类似的表示,因为这样会暴露用户的时钟偏差, 这是一种跟踪向量。 应采用类似单调时钟 时刻 的方式,传递两个时刻之间的持续时间。

3.1. 示例

DOM 事件发生的时间可以这样报告:

  1. eventtimeStamp 属性初始化为 this相关设置对象当前相对时间戳

错误报告的年龄可以通过以下方式计算:

  1. report生成时间初始化为 settings当前单调时间

之后:

  1. data 为包含如下键值对的映射:
    age
    report生成时间context相关设置对象当前单调时间之间相差的毫秒数,四舍五入为整数。
    ...

多天归因报告的过期,可这样处理:

  1. source 为一个新的归因源结构体,其条目为:
    ...
    source time
    context当前挂钟时间
    expiry
    value["expiry"] 解析持续时间字符串

若干天后:

  1. 如果 context当前挂钟时间 小于 source 的 source time + source 的 expiry, 则发送一个报告。

4. 时间原点

Unix 纪元(epoch)时刻,对应于 墙钟上的 1970 年 1 月 1 日 00:00:00 UTC。

每一组可能以任何方式通信的 环境设置对象,都具有一个 Unix 纪元的估算单调时间(estimated monotonic time of the Unix epoch),即对应于 时刻单调时钟上的一个瞬间, 其值通过以下步骤初始化:

  1. wall time墙钟不安全当前时间
  2. monotonic time单调时钟不安全当前时间
  3. epoch timemonotonic time - (wall time - Unix 纪元)
  4. Unix 纪元的估算单调时间 初始化为调用 coarsen time 并以 epoch time 作为参数的结果。
上述“可能通信的 settings-objects 的集合”需要有更好的规范。它与 familiar with 类似,但包括 Worker

性能度量会报告一个 持续时间,该持续时间从相关 时刻 的初始化早期 开始,属于某个 环境设置对象。 该 时刻 被存储在该设置对象的 time origin 中。

若要 获取 time origin 时间戳,给定一个 全局对象 (global object) global,按照下列步骤返回一个 持续时间

  1. timeOriginglobal相关设置对象(relevant settings object)time origin

    Window 上下文中,此值表示 导航已开始的时间。在 WorkerServiceWorker 内容中,此值表示 worker 被启动的时间。[SERVICE-WORKERS]

  2. 返回 持续时间,从 Unix 纪元的估算单调时间timeOrigin

get time origin timestamp 返回的值大致等于 globalUnix 纪元以来time origin发生的时间。它可能与在 time origin 时调用 Date.now() 返回的值不同,因为前者是基于不受系统及用户时钟调整、时钟偏移等影响的 单调时钟记录的。

coarsen time(粗化时间) 算法,给定某个 不安全时刻 timestamp(来自某个时钟)以及一个可选布尔值 crossOriginIsolatedCapability(默认为 false),按以下步骤运行:
  1. time resolution 为 100 微秒,或更高的 实现自定义值。
  2. 如果 crossOriginIsolatedCapability 是 true,则将 time resolution 设为 5 微秒,或更高的 实现自定义 值。
  3. 实现自定义的方式,对 timestamp 进行粗化并可能增加抖动,使其分辨率不会超过 time resolution
  4. 时刻形式返回 timestamp
relative high resolution time(相对高精度时间),给定一个 不安全时刻(来自 单调时钟time 以及一个 全局对象 global,返回以下步骤所得的 持续时间
  1. coarse time 为调用 coarsen time,参数为 timeglobal相关设置对象cross-origin isolated capability 的结果。
  2. 返回 relative high resolution coarse time,参数为 coarse timeglobal
relative high resolution coarse time(相对高精度粗化时间) 接收来自 单调时钟coarseTime, 和一个 全局对象 global,即为 duration from global相关设置对象time origincoarseTime 的持续时间。

current high resolution time(当前高精度时间),给定一个 全局对象 current global,必须返回 使用 relative high resolution time 并传入 unsafe shared current timecurrent global 的结果。

coarsened shared current time(粗化的共享当前时间),接收一个可选的布尔值 crossOriginIsolatedCapability(默认为 false),必须返回调用coarsen time,参数为 unsafe shared current timecrossOriginIsolatedCapability 的结果。

unsafe shared current time(不安全的共享当前时间) 必须返回 单调时钟不安全当前时间

5. DOMHighResTimeStamp 类型别名

DOMHighResTimeStamp 类型用于存储以毫秒为单位的持续时间。 根据其上下文,它还可以表示某个时刻, 即该持续时间 加在某个基准时刻(如time originUnix 纪元)后的时间点。

typedef double DOMHighResTimeStamp;

DOMHighResTimeStamp 应当以毫秒为单位表示时间,精度应足够保证可测量性,同时防范计时攻击 — 参见§ 9.1 时钟分辨率获取更多注意事项。

DOMHighResTimeStamp 是一个 double, 所以它只能以有限分辨率表示相对于纪元的时间——即从Unix 纪元 到某个时刻的毫秒数。 对于 2023 年的时刻,其分辨率约为 0.2 微秒。

6. EpochTimeStamp 类型别名

typedef unsigned long long EpochTimeStamp;

EpochTimeStamp 表示从 Unix 纪元挂钟时间上某个 时刻的整数毫秒数, 不包含闰秒。使用该类型的规范会定义毫秒数的具体解释方式。

7. Performance 接口

[Exposed=(Window,Worker)]
interface Performance : EventTarget {
    DOMHighResTimeStamp now();
    readonly attribute DOMHighResTimeStamp timeOrigin;
    [Default] object toJSON();
};

7.1. now() 方法

now() 方法必须返回以毫秒为单位的, 当前高分辨率时间 (以 this相关全局对象 为参数), 得到的 持续时间

当在同一个 时间原点Performance 对象上调用 now() 方法时,返回的时间值必须使用同一个 单调时钟。 若任意两个按时间顺序记录的、 来自 now() 方法的时间值具有同一 时间原点, 那么这两个时间值之差绝不能为负值。

7.2. timeOrigin 属性

timeOrigin 属性必须返回 获取时间原点时间戳相关全局对象(即 this)返回的 持续时间的毫秒数。

当获取 Performance.timeOrigin 时,必须使用同一 单调时钟 ——即所有 时间原点共享的时钟,其参考点为 [ECMA-262]time 定义。详见 [§ 9 安全注意事项]。

7.3. toJSON() 方法

调用 toJSON() 时,应执行 [WEBIDL]默认 toJSON 步骤

8. WindowOrWorkerGlobalScope mixin 的扩展

8.1. performance 属性

performance 属性定义在接口 mixin WindowOrWorkerGlobalScope 上,可从全局对象 访问与性能有关的属性与方法。

partial interface mixin WindowOrWorkerGlobalScope {
  [Replaceable] readonly attribute Performance performance;
};

9. 安全性考虑

本节为非规范性内容。

9.1. 时钟分辨率

许多应用在测量和调度时都需要精确计时。例如,协调页面上的动画、声音等活动需要高分辨率时间来提升用户体验。测量同样能帮助开发者追踪关键代码组件的性能,发现性能回退等。

但同样的精确计时能力也有被攻击者用于恶意目的的风险,攻击者可能借此推测、推断本不应可见或可访问的数据。例如,缓存攻击、指纹统计分析与微架构攻击都涉及利用浏览器或应用各类高分辨率计时信息来分辨用户子集、标识具体用户、揭示同进程的无关数据 —— 见 [CACHE-ATTACKS][SPECTRE]

本规范定义的 API 可提供亚毫秒分辨率时间,比分辨率仅有毫秒的 EpochTimeStamp 更精确。但即便无该 API,攻击者也可通过反复执行与统计分析获得高分辨率估算值。

为了防止该新 API 显著提升此类攻击的精度与速度, DOMHighResTimeStamp 类型的最小分辨率应当足够粗糙以防御攻击。

在必要时,用户代理应将更高精度值设为 时间分辨率,用于 粗化时间 的处理模型, 以应对架构或软件限制等带来的隐私和安全问题,或其他需要关注的事项。

为减轻上述攻击,用户代理可使用任何其认为必要的技术。这些技术的部署可能依赖于浏览器体系结构、用户设备、内容类型及其跨源读取数据的能力等各种因素。

可能的缓解手段包括:

要彻底防止此类定时侧信道攻击实际上几乎不可能:要么所有操作都在和任何敏感信息取值无关的固定时间内完成,要么应用必须完全隔离计时原语(时钟、定时器、计数器等)。这两者都因实现复杂度及对应用/浏览器性能与响应性的损害而不现实。

时钟分辨率仍是研究中的难题,尚无业界公认或适用于所有浏览器的权威建议。可追踪 Issue 79

9.2. 时钟漂移

本规范还定义了一个 API,能提供时间原点零时点的亚毫秒级时间分辨率,并要求且向应用公开 单调时钟,且必须在所有浏览器上下文间共享。 单调时钟无需与物理时间绑定, 但推荐依据 [ECMA-262]time 定义进行设置, 以避免暴露关于用户的新的指纹熵——例如该时间已可被应用轻易获得, 若公开新的逻辑时钟则会泄露新的信息。

不过,即使采用上述机制,单调时钟仍可能提供额外的 时钟漂移分辨率。 目前,应用可在同一上下文中多次记录当天时间和单调时间值(通过 Date.now()now()), 并观测它们之间的漂移——例如因自动或用户时钟调整。 通过 timeOrigin 属性,攻击者还能将 时间原点(由 单调时钟报告) 与 时间原点的当前当天时间(即 `performance.timeOrigin` 与 `Date.now() - performance.now()` 的差值)进行比较, 还可以在更长时间周期内观测这些时钟间的漂移。

实际上,应用可以跨多次导航观测到同样的时钟漂移: 应用可在每个上下文记录逻辑时间,并借助客户端或服务器时间同步机制推断用户时钟的变化。 类似地,底层机制如 TCP 时间戳,也可向服务器揭示同样的高分辨率信息,无需多次访问。 因此,该 API 提供的信息不应暴露有关用户的重大或此前不可获得的熵信息。

10. 隐私性考虑

本节为非规范性内容。

当前针对 Document时间原点定义会暴露 跨源重定向带来的总耗时(直到请求到达文档源为止)。 这暴露了跨源信息, 但如何在不对性能指标造成重大影响的前提下缓解,尚无定论。

相关讨论见 Navigation Timing Issue 160

11. 致谢

感谢 Arvind Jain、Angelos D. Keromytis、Boris Zbarsky、Jason Weber、Karen Anderson、Nat Duca、Philippe Le Hegaret、Ryosuke Niwa、 Simha Sethumadhavan、Todd Reifsteck、Tony Gentilcore、Vasileios P. Kemerlis、Yoav Weiss 和 Yossef Oren 对本规范的贡献。

一致性

文档约定

一致性要求通过描述性断言和 RFC 2119 术语结合表达。 在本规范的规范性部分中, “MUST”“MUST NOT”“REQUIRED”“SHALL”“SHALL NOT”“SHOULD”“SHOULD NOT”“RECOMMENDED”“MAY”“OPTIONAL” 等关键字应按 RFC 2119 的说明解释。 但为可读性考虑, 本规范中这些词不会全部大写出现。

除明确标记为非规范性、示例和注释的部分, 其余本规范文本均为规范性内容。 [RFC2119]

本规范中的示例以“例如(for example)”开头,或用 class="example" 将其与规范性文本区分,例如:

这是一个信息性示例。

信息性注释以“Note”开头, 并用 class="note" 与规范性文本区分,例如:

注,这是一个信息性注释。

一致性算法

作为算法一部分、祈使句表述的要求 (例如:“去除所有前导空格字符” 或 “返回 false 并中止这些步骤”) 应结合算法引言所用关键词 (例如“must”、“should”、“may”等) 一同解释。

以算法或具体步骤表述的一致性要求, 可用任意方式实现,只要最终结果等价即可。 本规范中定义的算法旨在易于理解,不要求高性能。 鼓励实现者优化。

索引

本规范定义的术语

引自其他规范的术语

参考文献

规范性引用

[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[ECMA-262]
ECMAScript 语言规范. URL: https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren 等. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 用于 RFC 的关键字以标示要求级别. 1997年3月. 最佳现行实践. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. 现行标准. URL: https://webidl.spec.whatwg.org/

信息性引用

[CACHE-ATTACKS]
Yossef Oren 等。沙盒中的间谍——Javascript中的实际缓存攻击。2015年3月1日。URL: https://arxiv.org/abs/1502.07373
[SERVICE-WORKERS]
Monica CHINTALA; Yoshisato Yanagisawa。Service Workers。2026年3月12日。CR。URL: https://www.w3.org/TR/service-workers/
[SPECTRE]
Paul Kocher 等。幽灵攻击:利用推测执行。2018年1月。URL: https://spectreattack.com/spectre.pdf
[Temporal]
Temporal。第三阶段提案。URL: https://tc39.es/proposal-temporal/

IDL 索引

typedef double DOMHighResTimeStamp;

typedef unsigned long long EpochTimeStamp;

[Exposed=(Window,Worker)]
interface Performance : EventTarget {
    DOMHighResTimeStamp now();
    readonly attribute DOMHighResTimeStamp timeOrigin;
    [Default] object toJSON();
};

partial interface mixin WindowOrWorkerGlobalScope {
  [Replaceable] readonly attribute Performance performance;
};

问题索引

上述“可互通信的 settings-object 组”还需更详细规定。与 familiar with 类似,但包含 Worker