package core import ( "fmt" "strings" "time" ) func GetNextUpdate(lastUpdated time.Time, spec string) (nextUpdate time.Time, err error) { var nextUpdates []time.Time switch { case strings.HasPrefix(spec, "every "): nextUpdates, err = parseEverySpec(lastUpdated, spec[len("every "):]) case strings.HasPrefix(spec, "at "): nextUpdates, err = parseAtSpec(lastUpdated, spec[len("at "):]) default: return time.Time{}, fmt.Errorf("unknown spec format: %v", spec) } if err != nil { return time.Time{}, err } for _, next := range nextUpdates { if next.After(lastUpdated) && (nextUpdate.IsZero() || next.Before(nextUpdate)) { nextUpdate = next } } return } // Get the next instance of the every-spec after the base time. // An every-spec is a Go duration string. func parseEverySpec(base time.Time, everySpec string) (nextUpdates []time.Time, err error) { var duration time.Duration duration, err = time.ParseDuration(everySpec) if err == nil { next := base.Round(duration) if !next.After(base) { next = next.Add(duration) } nextUpdates = []time.Time{next} } return } // Get the next instances of the at-spec times after the base time. // An at-spec is in the patterm HH:MM[,HH:MM,[...]]. func parseAtSpec(base time.Time, atSpec string) (nextUpdates []time.Time, err error) { timeSpecs := strings.Split(atSpec, ",") for _, timeSpec := range timeSpecs { var hour, minute int _, err = fmt.Sscanf(timeSpec, "%d:%d", &hour, &minute) if err != nil { return nil, fmt.Errorf("could not parse %s: %v", timeSpec, err) } // The time instance on the same day as the base time specOfDay := time.Date(base.Year(), base.Month(), base.Day(), hour, minute, 0, 0, base.Location()) // Bump it forward one day if it's before the base time if !specOfDay.After(base) { specOfDay = specOfDay.Add(24 * time.Hour) } nextUpdates = append(nextUpdates, specOfDay) } return }