mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-05 23:15:07 -06:00
92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
package scheduling
|
|
|
|
import (
|
|
"fmt"
|
|
"hash/fnv"
|
|
"time"
|
|
)
|
|
|
|
// IntervalTrigger represents a trigger that fires at a sequence of intervals.
|
|
type IntervalTrigger struct {
|
|
intervals []time.Duration // required for hash
|
|
epoch time.Time // required for hash
|
|
totalDuration time.Duration
|
|
}
|
|
|
|
// NewIntervalTrigger creates a new IntervalTrigger from one or more durations.
|
|
// An error is returned if no intervals are provided or if any interval is not positive.
|
|
// The epoch is the reference point for all interval calculations.
|
|
// The duration between each time alternates between each interval (or, if there is only one interval, it is the interval).
|
|
// For example, if the intervals are [1h, 2h, 3h], the first time will be at epoch + 1h, the second time will be at
|
|
// epoch + 1h + 2h, the third time will be at epoch + 1h + 2h + 3h, and so on.
|
|
func NewIntervalTrigger(interval time.Duration, additional ...time.Duration) (*IntervalTrigger, error) {
|
|
if interval <= 0 {
|
|
return nil, fmt.Errorf("intervals must be positive")
|
|
}
|
|
totalDuration := interval
|
|
for _, d := range additional {
|
|
if d <= 0 {
|
|
return nil, fmt.Errorf("intervals must be positive")
|
|
}
|
|
totalDuration += d
|
|
}
|
|
|
|
return &IntervalTrigger{
|
|
intervals: append([]time.Duration{interval}, additional...),
|
|
epoch: time.Time{}, // default epoch is zero time
|
|
totalDuration: totalDuration,
|
|
}, nil
|
|
}
|
|
|
|
// WithEpoch sets the epoch time for the IntervalTrigger. The epoch is the reference point for all interval calculations.
|
|
func (t *IntervalTrigger) WithEpoch(epoch time.Time) *IntervalTrigger {
|
|
t.epoch = epoch
|
|
return t
|
|
}
|
|
|
|
// NextTime calculates the next occurrence of this interval trigger after the given time.
|
|
func (t *IntervalTrigger) NextTime(now time.Time) *time.Time {
|
|
if t.totalDuration == 0 {
|
|
return nil
|
|
}
|
|
|
|
epoch := t.epoch
|
|
if epoch.IsZero() {
|
|
epoch = time.Unix(0, 0).UTC()
|
|
}
|
|
|
|
// If the current time is before the epoch, the next time is the first one in the cycle.
|
|
if now.Before(epoch) {
|
|
next := epoch.Add(t.intervals[0])
|
|
return &next
|
|
}
|
|
|
|
cyclesSinceEpoch := now.Sub(epoch) / t.totalDuration
|
|
currentCycleStart := epoch.Add(time.Duration(cyclesSinceEpoch) * t.totalDuration)
|
|
|
|
// Cycle through the offsets until the next time is found
|
|
cycle := currentCycleStart
|
|
for i := 0; i < len(t.intervals); i++ {
|
|
cycle = cycle.Add(t.intervals[i])
|
|
if cycle.After(now) {
|
|
return &cycle
|
|
}
|
|
}
|
|
|
|
// If we've reached here, it means we're at the end of a cycle.
|
|
// The next time will be the first interval of the next cycle.
|
|
nextCycleStart := currentCycleStart.Add(t.totalDuration)
|
|
next := nextCycleStart.Add(t.intervals[0])
|
|
return &next
|
|
}
|
|
|
|
// Hash returns a stable hash value for the IntervalTrigger.
|
|
func (t *IntervalTrigger) Hash() uint64 {
|
|
h := fnv.New64a()
|
|
fmt.Fprintf(h, "interval:%d", t.epoch.UnixNano())
|
|
for _, d := range t.intervals {
|
|
fmt.Fprintf(h, ":%d", d)
|
|
}
|
|
return h.Sum64()
|
|
}
|