Files
go-ha/internal/scheduling/daily.go

111 lines
3.0 KiB
Go

package scheduling
import (
"fmt"
"hash/fnv"
"time"
"github.com/Xevion/go-ha/internal"
"github.com/dromara/carbon/v2"
"github.com/nathan-osman/go-sunrise"
)
type Trigger interface {
// NextTime calculates the next occurrence of this trigger after the given time
NextTime(now time.Time) *time.Time
Hash() uint64
}
// FixedTimeTrigger represents a trigger at a specific hour and minute each day
type FixedTimeTrigger struct {
Hour int // 0-23
Minute int // 0-59
}
// SunTrigger represents a trigger based on sunrise or sunset with optional offset
type SunTrigger struct {
latitude float64 // latitude of the location
longitude float64 // longitude of the location
sunset bool // true for sunset, false for sunrise
offset *time.Duration // offset from sun event (can be negative)
}
func (t *FixedTimeTrigger) NextTime(now time.Time) *time.Time {
next := carbon.NewCarbon(now).SetHour(t.Hour).SetMinute(t.Minute)
// If the calculated time is before or equal to now, advance to the next day
if !next.StdTime().After(now) {
next = next.AddDay()
}
return internal.Ptr(next.StdTime().Local())
}
// Hash returns a stable hash value for the FixedTimeTrigger
func (t *FixedTimeTrigger) Hash() uint64 {
h := fnv.New64()
fmt.Fprintf(h, "%d:%d", t.Hour, t.Minute)
return h.Sum64()
}
// NextTime returns the next time the sun will rise or set. If an offset is provided, it will be added to the calculated time.
func (t *SunTrigger) NextTime(now time.Time) *time.Time {
var sun time.Time
if t.sunset {
_, sun = sunrise.SunriseSunset(t.latitude, t.longitude, now.Year(), now.Month(), now.Day())
} else {
sun, _ = sunrise.SunriseSunset(t.latitude, t.longitude, now.Year(), now.Month(), now.Day())
}
// In the case that the sun does not rise or set on the given day, return nil
if sun.IsZero() {
return nil
}
sun = sun.Local() // Convert to local time
if t.offset != nil && *t.offset != 0 {
sun = sun.Add(*t.offset) // Add the offset if provided and not zero
}
return &sun
}
// Hash returns a stable hash value for the SunTrigger
func (t *SunTrigger) Hash() uint64 {
h := fnv.New64()
fmt.Fprintf(h, "%f:%f:%t", t.latitude, t.longitude, t.sunset)
if t.offset != nil {
fmt.Fprintf(h, ":%d", t.offset.Nanoseconds())
}
return h.Sum64()
}
// CompositeDailySchedule combines multiple triggers into a single daily schedule.
type CompositeDailySchedule struct {
triggers []Trigger
}
// NextTime returns the next time the first viable trigger will run.
func (c *CompositeDailySchedule) NextTime(now time.Time) *time.Time {
best := c.triggers[0].NextTime(now)
for _, trigger := range c.triggers[1:] {
potential := trigger.NextTime(now)
if potential != nil && (best == nil || potential.Before(*best)) {
best = potential
}
}
return best
}
// Hash returns a stable hash value for the CompositeDailySchedule
func (c *CompositeDailySchedule) Hash() uint64 {
h := fnv.New64()
for _, trigger := range c.triggers {
fmt.Fprintf(h, "%d", trigger.Hash())
}
return h.Sum64()
}