93 lines
2.1 KiB
Go
93 lines
2.1 KiB
Go
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 0–23 (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
|
||
}
|