Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions internal/align/align_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,30 @@ func TestUpcomingUsesRunningWindow(t *testing.T) {
}
}

func TestUpcomingNormalizesPersistedUTCLastPing(t *testing.T) {
originalLocal := time.Local
time.Local = time.FixedZone("UTC+8", 8*60*60)
t.Cleanup(func() { time.Local = originalLocal })

planner := mustPlanner(t, []string{"0 0 * * *"}, 300)
now := time.Date(2026, 9, 8, 11, 40, 0, 0, time.Local)
lastPingLocal := time.Date(2026, 9, 8, 10, 41, 0, 0, time.Local)
lastPingUTC := lastPingLocal.UTC()

plans := planner.Upcoming(now, &lastPingUTC, 3)
if len(plans) != 3 {
t.Fatalf("want 3 plans, got %d", len(plans))
}
wantPing := time.Date(2026, 9, 8, 19, 0, 0, 0, time.Local)
wantRefresh := time.Date(2026, 9, 9, 0, 0, 0, 0, time.Local)
if !plans[0].PingAt.Equal(wantPing) || !plans[0].RefreshAt.Equal(wantRefresh) {
t.Fatalf("plan[0] = %+v, want ping %s refresh %s", plans[0], wantPing, wantRefresh)
}
if plans[0].PingAt.Location() != time.Local || plans[0].RefreshAt.Location() != time.Local {
t.Fatalf("plan should use local timezone, got ping=%s refresh=%s", plans[0].PingAt.Location(), plans[0].RefreshAt.Location())
}
}

func TestParseCronRejectsInvalid(t *testing.T) {
if _, err := ParseCron("not a cron"); err == nil {
t.Fatal("expected error for invalid cron")
Expand Down
3 changes: 2 additions & 1 deletion internal/align/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func (p *Planner) HasTargets() bool {
// 仅依赖当前时间与 cron 目标:延后中的每一分钟都会重新评估,随着时间逼近锚点,
// 待等时长单调减小,绝不会超过预算。每个目标使用它自己的延时预算。
func (p *Planner) Decide(now time.Time) Decision {
now = now.In(time.Local)
if len(p.targets) == 0 {
return Decision{Action: ActionPing, Now: now, Reason: "未配置对齐目标,满额即 ping"}
}
Expand Down Expand Up @@ -128,7 +129,7 @@ func (p *Planner) Upcoming(now time.Time, lastPing *time.Time, n int) []PingPlan
// 模拟假设每次 ping 后窗口在 window 后再次满额(工具自身链式语义),不建模真实使用。
func (p *Planner) NextPingTimes(start time.Time, n int) []PingPlan {
plans := make([]PingPlan, 0, n)
full := start
full := start.In(time.Local)
for i := 0; i < n; i++ {
decision := p.Decide(full)
pingAt := full
Expand Down
Loading