main.go 原本在启动时执行 time.Local = timeutil.Location(),靠隐式改写进程全局 状态把时间钉在 +08:00。副作用是任何跑在这行之前的代码都处于另一个时区,且依赖 关系完全不可见。 - 移除该全局赋值,业务时间全部走 timeutil 的显式时区 - rock_snapshot.parseRockTime 是唯一真实依赖,且是会出事的那一处:上游返回不带 偏移的北京时间墙面时间,用 time.Local 解析会在 TZ≠+08:00 时整体偏移 8 小时, 把「本次新绑定」误判为「历史残留」而放回库存,直接造成重复发货。 改为 timeutil.Location() 并补回归测试 - 有一点无法绕开:pgx 会把 timestamptz 解码到 time.Local,所以接口 JSON 的时间 偏移仍由进程时区决定。新增 timeutil.LocalZoneNotice(),启动时把不一致显式告警, 交由运维用 TZ=Asia/Shanghai 声明(Docker 部署已内置),而不是靠代码偷偷改全局 - 测试时间基线改用业务时区,避免随宿主机 TZ 漂移 验证:全量测试在默认时区、TZ=UTC、TZ=America/New_York 三种环境下均通过; 实测 TZ=UTC 启动打印时区提示,业务时区下静默。
44 lines
1.3 KiB
Go
44 lines
1.3 KiB
Go
package timeutil
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFormatAPITimeUsesBusinessTimezone(t *testing.T) {
|
|
value := time.Date(2026, 7, 30, 9, 53, 58, 147355000, time.UTC)
|
|
if got := FormatAPITime(value); got != "2026-07-30T17:53:58+08:00" {
|
|
t.Fatalf("unexpected api time: %s", got)
|
|
}
|
|
}
|
|
|
|
func TestStartOfDayUsesBusinessTimezone(t *testing.T) {
|
|
value := time.Date(2026, 7, 30, 18, 30, 0, 0, time.UTC)
|
|
got := StartOfDay(value)
|
|
if got.Format(APITimeLayout) != "2026-07-31T00:00:00+08:00" {
|
|
t.Fatalf("unexpected day start: %s", got.Format(APITimeLayout))
|
|
}
|
|
}
|
|
|
|
// TestLocalZoneNotice 锁住启动体检:进程时区偏移不等于 +08:00 时必须给出提示,
|
|
// 否则 pgx 把 timestamptz 解码到 time.Local 会让接口返回的时间偏移悄悄变化。
|
|
func TestLocalZoneNotice(t *testing.T) {
|
|
original := time.Local
|
|
t.Cleanup(func() { time.Local = original })
|
|
|
|
time.Local = time.FixedZone("CST", BusinessZoneOffsetSeconds)
|
|
if got := LocalZoneNotice(); got != "" {
|
|
t.Fatalf("业务时区不应告警,实际 %q", got)
|
|
}
|
|
|
|
time.Local = time.UTC
|
|
if got := LocalZoneNotice(); got == "" {
|
|
t.Fatal("UTC 进程时区必须告警")
|
|
}
|
|
|
|
time.Local = time.FixedZone("EST", -5*60*60)
|
|
if got := LocalZoneNotice(); got == "" {
|
|
t.Fatal("负偏移进程时区必须告警")
|
|
}
|
|
}
|