package pttodo import ( "encoding/json" "math" "regexp" "strconv" "time" cron "github.com/robfig/cron/v3" ) type Schedule string func (schedule Schedule) MarshalJSON() ([]byte, error) { return json.Marshal(string(schedule)) } func (schedule *Schedule) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, (*string)(schedule)) } func (schedule Schedule) Next(t time.Time) (time.Time, error) { scheduler := schedulerFactory(string(schedule)) return scheduler.next(t) } func (schedule Schedule) isFixedFuture() bool { if !schedule.isFixed() { return false } return schedule.isFuture() } func (schedule Schedule) isFixed() bool { if schedule.empty() { return false } scheduler := schedulerFactory(string(schedule)) switch scheduler.(type) { case scheduleEZDate, scheduleDue: default: return false } return true } func (schedule Schedule) isFuture() bool { if schedule.empty() { return false } scheduler := schedulerFactory(string(schedule)) t, err := scheduler.next(time.Now()) if err != nil { return false } return time.Now().Before(t) } func (schedule Schedule) empty() bool { return string(schedule) == "" } type scheduler interface { next(time.Time) (time.Time, error) } func schedulerFactory(s string) scheduler { if s == "" { return scheduleNever{} } else if scheduleDurationPattern.MatchString(s) { return scheduleDuration(s) } else if scheduleDuePattern.MatchString(s) { n, _ := strconv.Atoi(s) return scheduleDue(n) } else if scheduleEZDatePattern.MatchString(s) { return scheduleEZDate(s) } return scheduleCron(s) } // * * * * * type scheduleCron string func (c scheduleCron) next(t time.Time) (time.Time, error) { schedule, err := cron.ParseStandard(string(c)) if err != nil { return time.Time{}, err } return schedule.Next(t), nil } // * * * * * type scheduleNever struct{} var never = time.Date( math.MaxInt32, 1, 1, 1, 1, 1, 1, time.UTC, ) func (cron scheduleNever) next(time.Time) (time.Time, error) { return never, nil } // 4h5m type scheduleDuration string var scheduleDurationPattern = regexp.MustCompile(`^([0-9]+[a-z])+$`) func (dur scheduleDuration) next(t time.Time) (time.Time, error) { d, err := time.ParseDuration(string(dur)) return t.Add(d), err } // 123 type scheduleDue int64 var scheduleDuePattern = regexp.MustCompile(`^[0-9]+$`) func (due scheduleDue) next(time.Time) (time.Time, error) { return TS(due).time(), nil } // 2022-01-01 type scheduleEZDate string var scheduleEZDatePattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2})?$`) func (ezdate scheduleEZDate) next(time.Time) (time.Time, error) { if len(ezdate) == len("20xx-xx-xxTxx") { return time.ParseInLocation("2006-01-02T15", string(ezdate), time.Local) } return time.ParseInLocation("2006-01-02", string(ezdate), time.Local) }