package startimer import ( "context" "encoding/json" "errors" "sort" "sync" "time" ) func NewTimer(baseDate time.Time, opts ...TimerOptions) StarTimer { timer := StarTimer{ base: baseDate, nextDate: time.Time{}, mu: new(sync.RWMutex), } for _, opt := range opts { var op TimerOption opt(&op) switch op.idx { case 3: timer.repeat = append(timer.repeat, &Repeats{ Repeat: []Repeat{ { Unit: STAR_YEAR, Value: uint32(op.date.Year()), }, { Unit: STAR_MONTH, Value: uint32(op.date.Month()), }, { Unit: STAR_DAY, Value: uint32(op.date.Day()), }, { Unit: STAR_HOUR, Value: uint32(op.date.Hour()), }, { Unit: STAR_MINUTE, Value: uint32(op.date.Minute()), }, { Unit: STAR_SECOND, Value: uint32(op.date.Second()), }, }, Every: false, }) case 4: timer.repeat = append(timer.repeat, &Repeats{ Repeat: []Repeat{op.repeat}, Every: true, }) } if op.repeats != nil { timer.repeat = append(timer.repeat, op.repeats) } if op.tasks != nil { timer.tasks = append(timer.tasks, op.tasks) } } return timer } func (t *StarTimer) IsRunning() bool { t.mu.RLock() defer t.mu.RUnlock() return t.running } func (t *StarTimer) AddTask(task func()) { t.tasks = append(t.tasks, task) } func (t *StarTimer) SetTasks(tasks []func()) { t.tasks = tasks } func (t *StarTimer) Stop() error { t.mu.Lock() defer t.mu.Unlock() if !t.running { return nil } t.running = false t.stopFn() if t.timer != nil { t.timer.Stop() } return nil } func (t *StarTimer) NextTimer() time.Time { return t.nextDate } func (t *StarTimer) NextTimerAfterDate(date time.Time) time.Time { return t.parseNextDate(date, true) } func (t *StarTimer) ExportRepeats() (string, error) { var rep []Repeats for _, r := range t.repeat { rep = append(rep, *r) } data, err := json.Marshal(rep) if err != nil { return "", err } return string(data), nil } func (t *StarTimer) ImportRepeats(r string) error { t.mu.Lock() defer t.mu.Unlock() if t.running { return errors.New("coonot import repeats to already running timer") } var rep []Repeats err := json.Unmarshal([]byte(r), &rep) if err != nil { return err } t.repeat = make([]*Repeats, 0, len(rep)) for _, v := range rep { t.repeat = append(t.repeat, &v) } return nil } func (t *StarTimer) BaseDate() time.Time { return t.base } func (t *StarTimer) ResetWithRepeat(base time.Time, repeat []*Repeats) error { t.Stop() t.base = base t.repeat = repeat return t.Run() } func (t *StarTimer) Run() error { t.mu.Lock() defer t.mu.Unlock() if t.running { return nil } t.nextDate = t.parseNextDate(time.Now(), false) if t.nextDate.Before(time.Now()) { return errors.New("Invalid Timer Options,Please Check") } t.running = true t.stopCtx, t.stopFn = context.WithCancel(context.Background()) go func() { for { now := time.Now() t.mu.Lock() t.timer = time.NewTimer(t.nextDate.Sub(now)) t.mu.Unlock() select { case <-t.timer.C: t.nextDate = t.parseNextDate(t.nextDate, false) if t.nextDate.Before(now) { t.Stop() } for _, fn := range t.tasks { go fn() } if !t.running { return } case <-t.stopCtx.Done(): return } } }() return nil } func (t *StarTimer) parseNextDate(base time.Time, isMock bool) time.Time { if len(t.repeat) == 0 { return time.Time{} } var dates []time.Time for _, d := range t.repeat { if d == nil { continue } if d.Every { dates = append(dates, t.parseEveryNextDate(base, d, isMock)...) } else { dates = append(dates, t.parseStaticNextDate(base, d)) } } sort.SliceStable(dates, func(i, j int) bool { return dates[i].UnixNano() < dates[j].UnixNano() }) if len(dates) == 0 { return time.Time{} } now := time.Now().UnixNano() for _, v := range dates { if v.UnixNano() > now { return v } } return time.Time{} } func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { target := base if !r.Every { //固定日期 for _, d := range r.Repeat { switch d.Unit { case STAR_SECOND: sub := int(d.Value) - target.Second() if sub < 0 { sub += 60 } target = target.Add(time.Second * time.Duration(sub)) case STAR_MINUTE: sub := int(d.Value) - target.Minute() if sub < 0 { sub += 60 } target = target.Add(time.Minute * time.Duration(sub)) case STAR_HOUR: sub := int(d.Value) - target.Hour() if sub < 0 { sub += 24 } target = target.Add(time.Hour * time.Duration(sub)) case STAR_DAY: sub := int(d.Value) - target.Day() if sub >= 0 { target = target.Add(time.Hour * 24 * time.Duration(sub)) continue } target = time.Date(target.Year(), target.Month()+1, int(d.Value), target.Hour(), target.Minute(), target.Second(), 0, target.Location()) case STAR_MONTH: sub := int(d.Value) - int(target.Month()) if sub < 0 { sub += 12 } target = target.AddDate(0, sub, 0) case STAR_YEAR: sub := int(d.Value) - int(target.Year()) if sub < 0 { return time.Date(0, 0, 0, 0, 0, 0, 0, nil) } target = target.AddDate(sub, 0, 0) } } } if target == base { return time.Time{} } return target } func (t *StarTimer) parseEveryNextDate(target time.Time, r *Repeats, isMock bool) []time.Time { var res []time.Time if r.Every { //定期日期 for idx, d := range r.Repeat { if d.baseDate.Unix() == -62135596800 { d.baseDate = t.base if !isMock { r.Repeat[idx] = d } } if d.baseDate.After(target) { res = append(res, d.baseDate) continue } switch d.Unit { case STAR_SECOND: for { d.baseDate = d.baseDate.Add(time.Second * time.Duration(int(d.Value))) if d.baseDate.After(target) { if !isMock { r.Repeat[idx] = d } break } } res = append(res, d.baseDate) case STAR_MINUTE: for { d.baseDate = d.baseDate.Add(time.Minute * time.Duration(int(d.Value))) if d.baseDate.After(target) { if !isMock { r.Repeat[idx] = d } break } } res = append(res, d.baseDate) case STAR_HOUR: for { d.baseDate = d.baseDate.Add(time.Hour * time.Duration(int(d.Value))) if d.baseDate.After(target) { if !isMock { r.Repeat[idx] = d } break } } res = append(res, d.baseDate) case STAR_DAY: for { d.baseDate = d.baseDate.Add(time.Hour * 24 * time.Duration(int(d.Value))) if d.baseDate.After(target) { if !isMock { r.Repeat[idx] = d } break } } res = append(res, d.baseDate) case STAR_MONTH: for { d.baseDate = d.baseDate.AddDate(0, int(d.Value), 0) if d.baseDate.After(target) { if !isMock { r.Repeat[idx] = d } break } } res = append(res, d.baseDate) case STAR_YEAR: for { d.baseDate = d.baseDate.AddDate(int(d.Value), 0, 0) if d.baseDate.After(target) { if !isMock { r.Repeat[idx] = d } break } } res = append(res, d.baseDate) } } } return res }