bigywater.tech
Java · 347

Clock 与可测试时间

「现在几点」是测试里最难控制的隐式输入。这篇用真实运行把 Clock 的几种实现、注入后能省多少测试时间、以及固定时钟时容易漏掉的时区与分辨率问题逐个验证。

3.38 vs 1254 ms
30 分钟 TTL 的过期用例:注入 MutableClock 与用真实时钟 sleep 的耗时
06-30 / 07-01
同一个固定瞬间,Clock.fixed(at, systemDefault) 在不同默认时区下的 LocalDate
微秒
macOS 上 Instant.now() 的实际分辨率(纳秒 mod 1000 恒为 0)

Clock 的几种实现

实测 · 本机执行输出
  [kinds]
    fixed:               两次 instant() 相同? true → 2026-03-08T01:59:59.987654321Z
    offset(fixed, +90s): 2026-03-08T02:01:29.987654321Z
    tickSeconds(fixed):  TickClock / 对 fixed 取整: 2026-03-08T01:59:59Z
    tickMillis(fixed):   2026-03-08T01:59:59.987Z
    tickMinutes(fixed):  2026-03-08T01:59:00Z
    tick(PT1.5S):正常,instant() = 2026-03-08T01:59:58.500Z
    tick(PT0.3S):正常,instant() = 2026-03-08T01:59:59.700Z
    tick(PT0.000000003S):IllegalArgumentException: Invalid tick duration
    tick(PT0.0000015S):IllegalArgumentException: Invalid tick duration
    tick(PT0S):正常,instant() = 2026-03-08T01:59:59.987654321Z
    tick(PT-1S):IllegalArgumentException: Tick duration must not be negative
    systemUTC / systemDefaultZone 的时区: Z / Asia/Shanghai
    toString: FixedClock[2026-03-08T01:59:59.987654321Z,Z] | OffsetClock[FixedClock[2026-03-08T01:59:59.987654321Z,Z],PT1S] | TickClock[SystemClock[Z],PT1S]

注入 Clock 的过期测试

实测 · 本机执行输出
  [expiry]
    签发后:valid=true
    29:59 之后:valid=true
    30:00 整:valid=false(isBefore 是严格小于,恰好到期就失效)
    一年之后:valid=false
    可变时钟版本的整个测试耗时 3.38 ms
    真实时钟版本:TTL 1200 ms,sleep 1250 ms 之后 valid=false,测试耗时 1254 ms

默认时区的陷阱

实测 · 本机执行输出
  [-Duser.timezone=UTC]
    默认时区 UTC;Legacy.today()(当前系统时间) = 2026-10-03
    同一个固定瞬间 2026-06-30T20:30:00Z,不同 zone 的 LocalDate:
      UTC                    2026-06-30
      Asia/Shanghai          2026-07-01
      America/Los_Angeles    2026-06-30
      Pacific/Kiritimati     2026-07-01
    Clock.fixed(at, systemDefault) 的结果随默认时区而变:2026-06-30
  [-Duser.timezone=Asia/Shanghai]
    默认时区 Asia/Shanghai;Legacy.today()(当前系统时间) = 2026-10-04
    同一个固定瞬间 2026-06-30T20:30:00Z,不同 zone 的 LocalDate:
      UTC                    2026-06-30
      Asia/Shanghai          2026-07-01
      America/Los_Angeles    2026-06-30
      Pacific/Kiritimati     2026-07-01
    Clock.fixed(at, systemDefault) 的结果随默认时区而变:2026-07-01
  [-Duser.timezone=America/Los_Angeles]
    默认时区 America/Los_Angeles;Legacy.today()(当前系统时间) = 2026-10-03
    同一个固定瞬间 2026-06-30T20:30:00Z,不同 zone 的 LocalDate:
      UTC                    2026-06-30
      Asia/Shanghai          2026-07-01
      America/Los_Angeles    2026-06-30
      Pacific/Kiritimati     2026-07-01
    Clock.fixed(at, systemDefault) 的结果随默认时区而变:2026-06-30

时钟分辨率

实测 · 本机执行输出
  [precision]
    Instant.now() 的纳秒数 mod 1000 在 20000 次采样里有 1 种取值(只有 0 说明分辨率是微秒或更粗):[0]
    同样统计微秒位(纳秒 % 1e6 / 1000)有 999 种取值
    System.nanoTime() 相邻两次不同读数的最小差:41 ns

tick:截断读数

实测 · 本机执行输出
  [tick]
    tickSeconds 在 2.5 秒里的 125 次读数共有 4 种不同的值,最小间隔为 PT1S,每个值的纳秒部分都是 0:true
    tick(100 ms) 在 1 秒里有 11 种不同的值;全部是 100 ms 的整数倍:true

时钟回拨

实测 · 本机执行输出
  [wall]
    模拟系统时间在 200 ms 内被回拨 2 秒:Duration.between(开始, 结束)(基于 Clock) = PT-2S;基于 System.nanoTime 的耗时 = 205 ms
    (这是用可变时钟模拟的回拨,没有真的改系统时间)

分步演示

Clock
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,系统默认时区 Asia/Shanghai,第 3 节用 -Duser.timezone 覆盖
    • 没有验证:Linux 上 Instant.now() 的分辨率(这里是 macOS)、真实 NTP 回拨对 Clock.systemUTC() 的影响(只用 MutableClock 模拟)、Spring 或 Mockito 等框架里 Clock Bean 的注入方式、Clock.tick 在多线程下的一致性、闰秒处理
    京ICP备2026067502号-1京公网安备11010502063487号