commit 441b5523802a57df5b2a13ed717237f9427964c1 Author: ren yuze Date: Tue Dec 13 15:49:35 2022 +0800 init diff --git a/time_test.go b/time_test.go new file mode 100644 index 0000000..bde4070 --- /dev/null +++ b/time_test.go @@ -0,0 +1,55 @@ +package startimer + +import ( + "fmt" + "testing" + "time" +) + +func TestTimer(t *testing.T) { + tk:= StarTimer{ + base: time.Now(), + repeat: []*Repeats{ + { + Every: false, + Repeat: []Repeat{ + { + Unit: STAR_MINUTE, + Value: 7, + }, + { + Unit: STAR_HOUR, + Value: 18, + }, + }, + }, + { + Every: false, + Repeat: []Repeat{ + { + Unit: STAR_MINUTE, + Value: 15, + }, + { + Unit: STAR_HOUR, + Value: 14, + }, + }, + }, + { + Every: true, + Repeat: []Repeat{ + { + Unit: STAR_HOUR, + Value: 5, + }, + }, + }, + }, + } + base:=tk.base + for i:=0;i<10;i++{ + base=tk.parseNextDate(base) + fmt.Println(base) + } +} diff --git a/timer.go b/timer.go new file mode 100644 index 0000000..9a2313b --- /dev/null +++ b/timer.go @@ -0,0 +1,300 @@ +package startimer + +import ( + "context" + "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) 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(t.base) + 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) + 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) 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)...) + } 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) []time.Time { + var res []time.Time + if r.Every { //定期日期 + for idx, d := range r.Repeat { + if d.baseDate.Unix() == -62135596800 { + d.baseDate = t.base + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + r.Repeat[idx] = d + break + } + } + res = append(res, d.baseDate) + } + } + } + return res +} diff --git a/typed.go b/typed.go new file mode 100644 index 0000000..9fbdcd9 --- /dev/null +++ b/typed.go @@ -0,0 +1,79 @@ +package startimer + +import ( + "context" + "sync" + "time" +) + +type Unit uint8 + +const ( + STAR_SECOND Unit = iota + STAR_MINUTE + STAR_HOUR + STAR_DAY + STAR_MONTH + STAR_YEAR +) + +type Repeats struct { + Repeat []Repeat + Every bool // false=static true=every +} + +type Repeat struct { + Unit Unit + baseDate time.Time + Value uint32 +} + +type StarTimer struct { + base time.Time + nextDate time.Time + timer *time.Timer + stopFn context.CancelFunc + stopCtx context.Context + mu *sync.RWMutex + running bool + repeat []*Repeats + tasks []func() +} + +type TimerOptions func(option *TimerOption) + +type TimerOption struct { + idx uint8 + repeats *Repeats + tasks func() + date time.Time + repeat Repeat +} + +func WithRepeats(r *Repeats) TimerOptions { + return func(option *TimerOption) { + option.idx = 1 + option.repeats = r + } +} + +func WithRepeat(r Repeat) TimerOptions { + return func(option *TimerOption) { + option.idx = 4 + option.repeat = r + } +} + +func WithTask(t func()) TimerOptions { + return func(option *TimerOption) { + option.idx = 2 + option.tasks = t + } +} + +func WithStaticDate(t time.Time) TimerOptions { + return func(option *TimerOption) { + option.idx = 3 + option.date = t + } +}