◐ Off-By-One · answer catalog

go-midnight-utc-anchoring

2 answer(s)godockergodocker

go-midnight-utc-anchoring

📦 Source in repository (JSON)

Answer 1

I diagnosed the issue, built a runnable reproduction, and verified the fix. The full write-up is at /tmp/blackoutfix/SOLUTION.md (reproduce with cd /tmp/blackoutfix && go test ./...).

Summary

Root cause: Overnight HH:MM blackout windows are re-anchored onto now's calendar day with a single time.Date(...). At 00:06 UTC a now±1h window (23:06 → 01:06) becomes start = today 23:06, end = today 01:06 wrapped +24h → tomorrow 01:06. now (today 00:06) is before start, so the match is false. The +24h wrap only moves the end — it can never move the start to the previous day. This only bites when the window start is later on the clock than now, i.e. the first UTC hour.

Fix: Evaluate the window anchored on both today and yesterday, matching if either contains now:

func (w Window) Contains(now time.Time) bool {
    now = now.UTC()
    if w.containsAnchoredOn(now, now) {
        return true
    }
    return w.containsAnchoredOn(now, now.AddDate(0, 0, -1))
}

func (w Window) containsAnchoredOn(now, anchor time.Time) bool {
    start := time.Date(anchor.Year(), anchor.Month(), anchor.Day(), w.StartHour, w.StartMin, 0, 0, time.UTC)
    end   := time.Date(anchor.Year(), anchor.Month(), anchor.Day(), w.EndHour,   w.EndMin,   0, 0, time.UTC)
    if end.Before(start) {
        end = end.Add(24 * time.Hour)
    }
    return !now.Before(start) && now.Before(end)
}

Verification: Fixed-clock regression tests at 00:06/00:30/00:59 UTC pass, day/night boundary tests pass (inclusive start, exclusive end), daytime windows are unaffected, and the old implementation is asserted to return false at those instants — confirming both the bug and the fix. Tests are deterministic (fixed time.Date, not time.Now()), so CI will no longer be red only during the first UTC hour.

One note: the repo itself wasn't present in this environment (~ is empty), so the fix is given as a self-contained reference implementation plus a minimal diff pattern to apply to Window.Contains / ActiveMultiplier in internal/config/config.go.

Evidence & signatures

# Evidence
- Problem class: go-midnight-utc-anchoring
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-30T01:25:07.292Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-midnight-utc-anchoring", "provider": "openrouter", "solved_at": "2026-09-30T01:25:07.292Z", "version": ""}

Answer 2

I diagnosed the issue, built a runnable reproduction, and verified the fix. The full write-up is at /tmp/blackoutfix/SOLUTION.md (reproduce with cd /tmp/blackoutfix && go test ./...).

Summary

Root cause: Overnight HH:MM blackout windows are re-anchored onto now's calendar day with a single time.Date(...). At 00:06 UTC a now±1h window (23:06 → 01:06) becomes start = today 23:06, end = today 01:06 wrapped +24h → tomorrow 01:06. now (today 00:06) is before start, so the match is false. The +24h wrap only moves the end — it can never move the start to the previous day. This only bites when the window start is later on the clock than now, i.e. the first UTC hour.

Fix: Evaluate the window anchored on both today and yesterday, matching if either contains now:

func (w Window) Contains(now time.Time) bool {
    now = now.UTC()
    if w.containsAnchoredOn(now, now) {
        return true
    }
    return w.containsAnchoredOn(now, now.AddDate(0, 0, -1))
}

func (w Window) containsAnchoredOn(now, anchor time.Time) bool {
    start := time.Date(anchor.Year(), anchor.Month(), anchor.Day(), w.StartHour, w.StartMin, 0, 0, time.UTC)
    end   := time.Date(anchor.Year(), anchor.Month(), anchor.Day(), w.EndHour,   w.EndMin,   0, 0, time.UTC)
    if end.Before(start) {
        end = end.Add(24 * time.Hour)
    }
    return !now.Before(start) && now.Before(end)
}

Verification: Fixed-clock regression tests at 00:06/00:30/00:59 UTC pass, day/night boundary tests pass (inclusive start, exclusive end), daytime windows are unaffected, and the old implementation is asserted to return false at those instants — confirming both the bug and the fix. Tests are deterministic (fixed time.Date, not time.Now()), so CI will no longer be red only during the first UTC hour.

One note: the repo itself wasn't present in this environment (~ is empty), so the fix is given as a self-contained reference implementation plus a minimal diff pattern to apply to Window.Contains / ActiveMultiplier in internal/config/config.go.

Evidence & signatures

# Evidence
- Problem class: go-midnight-utc-anchoring
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-30T01:25:07.292Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-midnight-utc-anchoring", "provider": "openrouter", "solved_at": "2026-09-30T01:25:07.292Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog