时间与时区
应用时区、统一时钟和可测试时间
Runa 在内核里维护应用时区和应用时钟。业务记录、审计时间、队列时间、缓存过期、集群心跳、调度默认时区等框架时间戳都会尽量走这套时间源。
这样做的目标很简单:一个应用只有一个时区来源,测试时也可以固定当前时间。
设置应用时区
推荐在应用入口设置:
app := runa.New(
runa.Timezone("Asia/Shanghai"),
)
也可以写在 config/app.toml:
timezone = "Asia/Shanghai"
runa.Timezone(...) 的优先级高于配置文件。时区会在应用 Freeze 阶段、各 Provider Boot 之前生效。
读取当前时间
需要记录业务时间戳时,使用 core.Now():
now := core.Now()
core.Now() 返回应用时区下的当前时间。默认时区是 time.Local,设置 runa.Timezone(...) 或 config/app.toml 后会切换到指定时区。
常用时间函数
loc := core.Location()
now := core.Now()
createdAt := core.In(time.Now())
startAt, err := core.Parse("2006-01-02 15:04:05", "2026-06-30 12:00:00")
core.Location()返回应用时区core.Now()返回应用时区下的当前时间core.In(t)把任意时间转换到应用时区core.Parse(layout, value)按应用时区解析时间字符串
测试中固定时间
测试时间戳逻辑时,可以覆盖应用时钟:
fixed := time.Date(2026, 6, 30, 12, 0, 0, 0, time.UTC)
core.SetClock(core.FixedClock(fixed))
defer core.SetClock(nil)
got := core.Now()
_ = got
如果测试也改了时区,记得恢复:
old := core.Location()
core.SetLocation(time.UTC)
defer core.SetLocation(old)
不要用 core.Now 测耗时
core.Now() 会把时间转换到应用时区,转换后的时间不保留 Go 的单调时钟信息。
因此要区分两类场景:
| 场景 | 推荐写法 |
|---|---|
| 记录、展示、审计、过期时间 | core.Now() |
| 请求耗时、超时、延迟、滑动窗口计时 | time.Now() + time.Since(...) |
例如请求耗时应该这样写:
started := time.Now()
err := handle()
latency := time.Since(started)
这类代码不要改成 core.Now()。
哪些能力会跟随应用时区
常见的框架时间戳会跟随应用时区:
audit审计记录时间queue任务创建、运行和更新时间message消息创建时间cache、lock、storage的过期时间cluster实例启动和心跳时间console、observe中用于展示的检查时间schedule在未显式设置时区时,默认使用应用时区database/oro默认把应用时区传给 Oro 配置
如果某个能力本身有独立时区选项,显式选项会覆盖应用默认值。