thread-master/apps/backend/internal/module/radar/domain/schedule.go

93 lines
2.1 KiB
Go
Raw Normal View History

2026-08-27 05:35:07 +00:00
package domain
import (
"fmt"
"sort"
"time"
)
const (
ScheduleTimezone = "Asia/Taipei"
DefaultSweepHour = 6
MaxSweepHours = 6
minSweepHour = 0
maxSweepHour = 23
)
// RadarSchedule is the owner's automatic patrol timetable (Taipei local hours).
type RadarSchedule struct {
OwnerUID int64 `bson:"_id" json:"owner_uid"`
Hours []int `bson:"hours" json:"hours"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
func TaipeiLocation() *time.Location {
loc, err := time.LoadLocation(ScheduleTimezone)
if err != nil {
return time.FixedZone(ScheduleTimezone, 8*3600)
}
return loc
}
func DefaultSweepHours() []int {
return []int{DefaultSweepHour}
}
func NormalizeSweepHours(hours []int) ([]int, error) {
if len(hours) == 0 {
return DefaultSweepHours(), nil
}
seen := map[int]bool{}
out := make([]int, 0, len(hours))
for _, h := range hours {
if h < minSweepHour || h > maxSweepHour {
return nil, fmt.Errorf("%w: hours must be 023 (got %d)", ErrValidation, h)
}
if seen[h] {
continue
}
seen[h] = true
out = append(out, h)
}
if len(out) == 0 {
return DefaultSweepHours(), nil
}
if len(out) > MaxSweepHours {
return nil, fmt.Errorf("%w: at most %d patrol hours", ErrValidation, MaxSweepHours)
}
sort.Ints(out)
return out, nil
}
// SweepSlot is one due automatic patrol (Taipei calendar day + hour).
type SweepSlot struct {
Date string
Hour int
RunAt int64
}
// DueSweepSlots returns selected hours that have already started today (Taipei)
// plus any earlier selected hours the same day, so a late worker still catches up.
func DueSweepSlots(now time.Time, hours []int) []SweepSlot {
hours, err := NormalizeSweepHours(hours)
if err != nil {
hours = DefaultSweepHours()
}
loc := TaipeiLocation()
local := now.In(loc)
day := time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, loc)
out := make([]SweepSlot, 0, len(hours))
for _, h := range hours {
slot := day.Add(time.Duration(h) * time.Hour)
if local.Before(slot) {
continue
}
out = append(out, SweepSlot{
Date: day.Format("2006-01-02"),
Hour: h,
RunAt: slot.UnixNano(),
})
}
return out
}