Files
silo-server/internal/catalog/collection_sync_schedule.go

55 lines
1.6 KiB
Go

package catalog
import (
"fmt"
"math/rand/v2"
"time"
"github.com/robfig/cron/v3"
)
// cronParser uses the standard 5-field cron format (minute hour dom month dow).
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
// ParseCronExpression validates a cron expression string.
// It accepts the standard 5-field format: minute hour dom month dow.
func ParseCronExpression(expr string) error {
_, err := cronParser.Parse(expr)
if err != nil {
return fmt.Errorf("invalid cron expression: %w", err)
}
return nil
}
// computeNextSyncAt parses a cron expression and returns the next scheduled
// time after now, with a small random jitter (0-15 minutes) to prevent
// thundering herd when many collections share the same schedule.
// Returns nil if the schedule is nil or empty.
func computeNextSyncAt(schedule *string) *time.Time {
if schedule == nil || *schedule == "" {
return nil
}
sched, err := cronParser.Parse(*schedule)
if err != nil {
return nil
}
next := sched.Next(time.Now())
jitter := time.Duration(rand.IntN(15*60)) * time.Second
next = next.Add(jitter)
return &next
}
// ComputeNextSyncAtFrom parses a cron expression and returns the next
// scheduled time after the given reference time, with a small random jitter
// (0-15 minutes) to prevent thundering herd on subsequent cycles.
func ComputeNextSyncAtFrom(schedule string, after time.Time) *time.Time {
sched, err := cronParser.Parse(schedule)
if err != nil {
return nil
}
next := sched.Next(after)
jitter := time.Duration(rand.IntN(15*60)) * time.Second
next = next.Add(jitter)
return &next
}