diff --git a/item/recur.go b/item/recur.go new file mode 100644 index 0000000..48d37be --- /dev/null +++ b/item/recur.go @@ -0,0 +1,65 @@ +package item + +import "time" + +type RecurPeriod string + +const ( + PeriodDay RecurPeriod = "day" + PeriodMonth RecurPeriod = "month" +) + +type Recur struct { + Start time.Time + Period RecurPeriod + Count int +} + +func (r *Recur) On(date time.Time) bool { + switch r.Period { + case PeriodDay: + return r.onDays(date) + case PeriodMonth: + return r.onMonths(date) + default: + return false + } +} + +func (r *Recur) onDays(date time.Time) bool { + if r.Start.After(date) { + return false + } + + testDate := r.Start + for { + if testDate.Equal(date) { + return true + } + if testDate.After(date) { + return false + } + + dur := time.Duration(r.Count) * 24 * time.Hour + testDate = testDate.Add(dur) + } +} + +func (r *Recur) onMonths(date time.Time) bool { + if r.Start.After(date) { + return false + } + + tDate := r.Start + for { + if tDate.Equal(date) { + return true + } + if tDate.After(date) { + return false + } + + y, m, d := tDate.Date() + tDate = time.Date(y, m+time.Month(r.Count), d, 0, 0, 0, 0, time.UTC) + } +} diff --git a/item/recur_test.go b/item/recur_test.go new file mode 100644 index 0000000..3ccca11 --- /dev/null +++ b/item/recur_test.go @@ -0,0 +1,105 @@ +package item_test + +import ( + "testing" + "time" + + "go-mod.ewintr.nl/planner/item" +) + +func TestRecur(t *testing.T) { + t.Parallel() + + t.Run("days", func(t *testing.T) { + r := item.Recur{ + Start: time.Date(2024, 12, 1, 0, 0, 0, 0, time.UTC), + Period: item.PeriodDay, + Count: 5, + } + day := 24 * time.Hour + + for _, tc := range []struct { + name string + date time.Time + exp bool + }{ + { + name: "before", + date: time.Date(202, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "start", + date: r.Start, + exp: true, + }, + { + name: "after true", + date: r.Start.Add(15 * day), + exp: true, + }, + { + name: "after false", + date: r.Start.Add(16 * day), + }, + } { + t.Run(tc.name, func(t *testing.T) { + if act := r.On(tc.date); tc.exp != act { + t.Errorf("exp %v, got %v", tc.exp, act) + } + }) + } + }) + + t.Run("months", func(t *testing.T) { + r := item.Recur{ + Start: time.Date(2021, 2, 3, 0, 0, 0, 0, time.UTC), + Period: item.PeriodMonth, + Count: 3, + } + + for _, tc := range []struct { + name string + date time.Time + exp bool + }{ + { + name: "before start", + date: time.Date(2021, 1, 27, 0, 0, 0, 0, time.UTC), + }, + { + name: "on start", + date: time.Date(2021, 2, 3, 0, 0, 0, 0, time.UTC), + exp: true, + }, + { + name: "8 weeks after", + date: time.Date(2021, 3, 31, 0, 0, 0, 0, time.UTC), + }, + { + name: "one month", + date: time.Date(2021, 3, 3, 0, 0, 0, 0, time.UTC), + }, + { + name: "3 months", + date: time.Date(2021, 5, 3, 0, 0, 0, 0, time.UTC), + exp: true, + }, + { + name: "4 months", + date: time.Date(2021, 6, 3, 0, 0, 0, 0, time.UTC), + }, + { + name: "6 months", + date: time.Date(2021, 8, 3, 0, 0, 0, 0, time.UTC), + exp: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if act := r.On(tc.date); tc.exp != act { + t.Errorf("exp %v, got %v", tc.exp, act) + } + }) + } + }) + +}