From 7360744e34f540acaec057390222505d065f9a2d Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Sun, 22 Dec 2024 10:10:58 +0100 Subject: [PATCH] recurrer --- item/recur.go | 57 +--- item/recur_test.go | 789 +++++++++++++++++++++++---------------------- 2 files changed, 401 insertions(+), 445 deletions(-) diff --git a/item/recur.go b/item/recur.go index 9808183..dea7aa7 100644 --- a/item/recur.go +++ b/item/recur.go @@ -29,7 +29,7 @@ func NewRecurrer(recurStr string) Recurrer { } for _, parseFunc := range []func(Date, []string) (Recurrer, bool){ - ParseDaily, ParseEveryNDays, ParseWeekly, ParseBiweekly, + ParseDaily, ParseEveryNDays, ParseWeekly, ParseEveryNWeeks, ParseEveryNMonths, } { if recur, ok := parseFunc(start, terms); ok { @@ -178,61 +178,6 @@ type Biweekly struct { Weekday time.Weekday } -// yyyy-mm-dd, biweekly, wednesday -func ParseBiweekly(start Date, terms []string) (Recurrer, bool) { - if len(terms) < 2 { - return nil, false - } - - if terms[0] != "biweekly" { - return nil, false - } - - wd, ok := ParseWeekday(terms[1]) - if !ok { - return nil, false - } - - return Biweekly{ - Start: start, - Weekday: wd, - }, true -} - -func (b Biweekly) RecursOn(date Date) bool { - if b.Start.After(date) { - return false - } - - if b.Weekday != date.Weekday() { - return false - } - - // find first - tDate := b.Start - for { - if tDate.Weekday() == b.Weekday { - break - } - tDate = tDate.AddDays(1) - } - - // add weeks - for { - switch { - case tDate.Equal(date): - return true - case tDate.After(date): - return false - } - tDate = tDate.AddDays(14) - } -} - -func (b Biweekly) String() string { - return fmt.Sprintf("%s, biweekly, %s", b.Start.String(), strings.ToLower(b.Weekday.String())) -} - type EveryNWeeks struct { Start Date N int diff --git a/item/recur_test.go b/item/recur_test.go index 152862c..a3e89f7 100644 --- a/item/recur_test.go +++ b/item/recur_test.go @@ -1,417 +1,428 @@ package item_test -// func TestDaily(t *testing.T) { -// daily := task.Daily{ -// Start: task.NewDate(2021, 1, 31), // a sunday -// } -// dailyStr := "2021-01-31 (sunday), daily" +import ( + "testing" + "time" -// t.Run("parse", func(t *testing.T) { -// test.Equals(t, daily, task.NewRecurrer(dailyStr)) -// }) + "github.com/google/go-cmp/cmp" + "go-mod.ewintr.nl/planner/item" +) -// t.Run("string", func(t *testing.T) { -// test.Equals(t, dailyStr, daily.String()) -// }) +func TestDaily(t *testing.T) { + t.Parallel() -// t.Run("recurs_on", func(t *testing.T) { -// for _, tc := range []struct { -// name string -// date task.Date -// exp bool -// }{ -// { -// name: "before", -// date: task.NewDate(2021, 1, 30), -// }, -// { -// name: "on", -// date: daily.Start, -// exp: true, -// }, -// { -// name: "after", -// date: task.NewDate(2021, 2, 1), -// exp: true, -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// test.Equals(t, tc.exp, daily.RecursOn(tc.date)) -// }) -// } -// }) -// } + daily := item.Daily{ + Start: item.NewDate(2021, 1, 31), // a sunday + } + dailyStr := "2021-01-31, daily" -// func TestEveryNDays(t *testing.T) { -// every := task.EveryNDays{ -// Start: task.NewDate(2022, 6, 8), -// N: 5, -// } -// everyStr := "2022-06-08 (wednesday), every 5 days" + t.Run("parse", func(t *testing.T) { + if diff := cmp.Diff(daily, item.NewRecurrer(dailyStr)); diff != "" { + t.Errorf("(-exp +got):\n%s", diff) + } + }) -// t.Run("parse", func(t *testing.T) { -// test.Equals(t, every, task.NewRecurrer(everyStr)) -// }) + t.Run("string", func(t *testing.T) { + if dailyStr != daily.String() { + t.Errorf("exp %v, got %v", dailyStr, daily.String()) + } + }) -// t.Run("string", func(t *testing.T) { -// test.Equals(t, everyStr, every.String()) -// }) + t.Run("recurs_on", func(t *testing.T) { + for _, tc := range []struct { + name string + date item.Date + exp bool + }{ + { + name: "before", + date: item.NewDate(2021, 1, 30), + }, + { + name: "on", + date: daily.Start, + exp: true, + }, + { + name: "after", + date: item.NewDate(2021, 2, 1), + exp: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.exp != daily.RecursOn(tc.date) { + t.Errorf("exp %v, got %v", tc.exp, daily.RecursOn(tc.date)) + } + }) + } + }) +} -// t.Run("recurs on", func(t *testing.T) { -// for _, tc := range []struct { -// name string -// date task.Date -// exp bool -// }{ -// { -// name: "before", -// date: task.NewDate(2022, 1, 1), -// }, -// { -// name: "start", -// date: every.Start, -// exp: true, -// }, -// { -// name: "after true", -// date: every.Start.Add(15), -// exp: true, -// }, -// { -// name: "after false", -// date: every.Start.Add(16), -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// test.Equals(t, tc.exp, every.RecursOn(tc.date)) -// }) -// } -// }) -// } +func TestEveryNDays(t *testing.T) { + t.Parallel() -// func TestParseWeekly(t *testing.T) { -// start := task.NewDate(2021, 2, 7) -// for _, tc := range []struct { -// name string -// input []string -// expOk bool -// expWeekly task.Weekly -// }{ -// { -// name: "empty", -// }, -// { -// name: "wrong type", -// input: []string{"daily"}, -// }, -// { -// name: "wrong count", -// input: []string{"weeekly"}, -// }, -// { -// name: "unknown day", -// input: []string{"weekly", "festivus"}, -// }, -// { -// name: "one day", -// input: []string{"weekly", "monday"}, -// expOk: true, -// expWeekly: task.Weekly{ -// Start: start, -// Weekdays: task.Weekdays{ -// time.Monday, -// }, -// }, -// }, -// { -// name: "multiple days", -// input: []string{"weekly", "monday & thursday & saturday"}, -// expOk: true, -// expWeekly: task.Weekly{ -// Start: start, -// Weekdays: task.Weekdays{ -// time.Monday, -// time.Thursday, -// time.Saturday, -// }, -// }, -// }, -// { -// name: "wrong order", -// input: []string{"weekly", "sunday & thursday & wednesday"}, -// expOk: true, -// expWeekly: task.Weekly{ -// Start: start, -// Weekdays: task.Weekdays{ -// time.Wednesday, -// time.Thursday, -// time.Sunday, -// }, -// }, -// }, -// { -// name: "doubles", -// input: []string{"weekly", "sunday & sunday & monday"}, -// expOk: true, -// expWeekly: task.Weekly{ -// Start: start, -// Weekdays: task.Weekdays{ -// time.Monday, -// time.Sunday, -// }, -// }, -// }, -// { -// name: "one unknown", -// input: []string{"weekly", "sunday & someday"}, -// expOk: true, -// expWeekly: task.Weekly{ -// Start: start, -// Weekdays: task.Weekdays{ -// time.Sunday, -// }, -// }, -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// weekly, ok := task.ParseWeekly(start, tc.input) -// test.Equals(t, tc.expOk, ok) -// if tc.expOk { -// test.Equals(t, tc.expWeekly, weekly) -// } -// }) -// } -// } + every := item.EveryNDays{ + Start: item.NewDate(2022, 6, 8), + N: 5, + } + everyStr := "2022-06-08, every 5 days" -// func TestWeekly(t *testing.T) { -// weekly := task.Weekly{ -// Start: task.NewDate(2021, 1, 31), // a sunday -// Weekdays: task.Weekdays{ -// time.Monday, -// time.Wednesday, -// time.Thursday, -// }, -// } -// weeklyStr := "2021-01-31 (sunday), weekly, monday & wednesday & thursday" + t.Run("parse", func(t *testing.T) { + if diff := cmp.Diff(every, item.NewRecurrer(everyStr)); diff != "" { + t.Errorf("(-exp +got):\n%s", diff) + } + }) -// t.Run("parse", func(t *testing.T) { -// test.Equals(t, weekly, task.NewRecurrer(weeklyStr)) -// }) + t.Run("string", func(t *testing.T) { + if everyStr != every.String() { + t.Errorf("exp %v, got %v", everyStr, every.String()) + } + }) -// t.Run("string", func(t *testing.T) { -// test.Equals(t, weeklyStr, weekly.String()) -// }) + t.Run("recurs on", func(t *testing.T) { + for _, tc := range []struct { + name string + date item.Date + exp bool + }{ + { + name: "before", + date: item.NewDate(2022, 1, 1), + }, + { + name: "start", + date: every.Start, + exp: true, + }, + { + name: "after true", + date: every.Start.Add(15), + exp: true, + }, + { + name: "after false", + date: every.Start.Add(16), + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.exp != every.RecursOn(tc.date) { + t.Errorf("exp %v, got %v", tc.exp, tc.date) + } + }) + } + }) +} -// t.Run("recurs_on", func(t *testing.T) { -// for _, tc := range []struct { -// name string -// date task.Date -// exp bool -// }{ -// { -// name: "before start", -// date: task.NewDate(2021, 1, 27), // a wednesday -// }, -// { -// name: "right weekday", -// date: task.NewDate(2021, 2, 1), // a monday -// exp: true, -// }, -// { -// name: "another right day", -// date: task.NewDate(2021, 2, 3), // a wednesday -// exp: true, -// }, -// { -// name: "wrong weekday", -// date: task.NewDate(2021, 2, 5), // a friday -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// test.Equals(t, tc.exp, weekly.RecursOn(tc.date)) -// }) -// } -// }) -// } +func TestParseWeekly(t *testing.T) { + t.Parallel() -// func TestBiweekly(t *testing.T) { -// biweekly := task.Biweekly{ -// Start: task.NewDate(2021, 1, 31), // a sunday -// Weekday: time.Wednesday, -// } -// biweeklyStr := "2021-01-31 (sunday), biweekly, wednesday" + start := item.NewDate(2021, 2, 7) + for _, tc := range []struct { + name string + input []string + expOK bool + expWeekly item.Weekly + }{ + { + name: "empty", + }, + { + name: "wrong type", + input: []string{"daily"}, + }, + { + name: "wrong count", + input: []string{"weeekly"}, + }, + { + name: "unknown day", + input: []string{"weekly", "festivus"}, + }, + { + name: "one day", + input: []string{"weekly", "monday"}, + expOK: true, + expWeekly: item.Weekly{ + Start: start, + Weekdays: item.Weekdays{ + time.Monday, + }, + }, + }, + { + name: "multiple days", + input: []string{"weekly", "monday & thursday & saturday"}, + expOK: true, + expWeekly: item.Weekly{ + Start: start, + Weekdays: item.Weekdays{ + time.Monday, + time.Thursday, + time.Saturday, + }, + }, + }, + { + name: "wrong order", + input: []string{"weekly", "sunday & thursday & wednesday"}, + expOK: true, + expWeekly: item.Weekly{ + Start: start, + Weekdays: item.Weekdays{ + time.Wednesday, + time.Thursday, + time.Sunday, + }, + }, + }, + { + name: "doubles", + input: []string{"weekly", "sunday & sunday & monday"}, + expOK: true, + expWeekly: item.Weekly{ + Start: start, + Weekdays: item.Weekdays{ + time.Monday, + time.Sunday, + }, + }, + }, + { + name: "one unknown", + input: []string{"weekly", "sunday & someday"}, + expOK: true, + expWeekly: item.Weekly{ + Start: start, + Weekdays: item.Weekdays{ + time.Sunday, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + actWeekly, actOK := item.ParseWeekly(start, tc.input) + if tc.expOK != actOK { + t.Errorf("exp %v, got %v", tc.expOK, actOK) + } + if !tc.expOK { + return + } + if diff := cmp.Diff(tc.expWeekly, actWeekly); diff != "" { + t.Errorf("(-exp, +got)%s\n", diff) + } + }) + } +} -// t.Run("parse", func(t *testing.T) { -// test.Equals(t, biweekly, task.NewRecurrer(biweeklyStr)) -// }) +func TestWeekly(t *testing.T) { + t.Parallel() -// t.Run("string", func(t *testing.T) { -// test.Equals(t, biweeklyStr, biweekly.String()) -// }) + weekly := item.Weekly{ + Start: item.NewDate(2021, 1, 31), // a sunday + Weekdays: item.Weekdays{ + time.Monday, + time.Wednesday, + time.Thursday, + }, + } + weeklyStr := "2021-01-31, weekly, monday & wednesday & thursday" -// t.Run("recurs_on", func(t *testing.T) { -// for _, tc := range []struct { -// name string -// date task.Date -// exp bool -// }{ -// { -// name: "before start", -// date: task.NewDate(2021, 1, 27), // a wednesday -// }, -// { -// name: "wrong weekday", -// date: task.NewDate(2021, 2, 1), // a monday -// }, -// { -// name: "odd week count", -// date: task.NewDate(2021, 2, 10), // a wednesday -// }, -// { -// name: "right", -// date: task.NewDate(2021, 2, 17), // a wednesday -// exp: true, -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// test.Equals(t, tc.exp, biweekly.RecursOn(tc.date)) -// }) -// } -// }) -// } + t.Run("parse", func(t *testing.T) { + if diff := cmp.Diff(weekly, item.NewRecurrer(weeklyStr)); diff != "" { + t.Errorf("(-exp, +got)%s\n", diff) + } + }) -// func TestEveryNWeeks(t *testing.T) { -// everyNWeeks := task.EveryNWeeks{ -// Start: task.NewDate(2021, 2, 3), -// N: 3, -// } -// everyNWeeksStr := "2021-02-03 (wednesday), every 3 weeks" + t.Run("string", func(t *testing.T) { + if weeklyStr != weekly.String() { + t.Errorf("exp %v, got %v", weeklyStr, weekly.String()) + } + }) -// t.Run("parse", func(t *testing.T) { -// test.Equals(t, everyNWeeks, task.NewRecurrer(everyNWeeksStr)) -// }) + t.Run("recurs_on", func(t *testing.T) { + for _, tc := range []struct { + name string + date item.Date + exp bool + }{ + { + name: "before start", + date: item.NewDate(2021, 1, 27), // a wednesday + }, + { + name: "right weekday", + date: item.NewDate(2021, 2, 1), // a monday + exp: true, + }, + { + name: "another right day", + date: item.NewDate(2021, 2, 3), // a wednesday + exp: true, + }, + { + name: "wrong weekday", + date: item.NewDate(2021, 2, 5), // a friday + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.exp != weekly.RecursOn(tc.date) { + t.Errorf("exp %v, got %v", tc.exp, weekly.RecursOn(tc.date)) + } + }) + } + }) +} -// t.Run("string", func(t *testing.T) { -// test.Equals(t, everyNWeeksStr, everyNWeeks.String()) -// }) +func TestEveryNWeeks(t *testing.T) { + t.Parallel() -// t.Run("recurs on", func(t *testing.T) { -// for _, tc := range []struct { -// name string -// date task.Date -// exp bool -// }{ -// { -// name: "before start", -// date: task.NewDate(2021, 1, 27), -// }, -// { -// name: "on start", -// date: task.NewDate(2021, 2, 3), -// exp: true, -// }, -// { -// name: "wrong day", -// date: task.NewDate(2021, 2, 4), -// }, -// { -// name: "one week after", -// date: task.NewDate(2021, 2, 10), -// }, -// { -// name: "first interval", -// date: task.NewDate(2021, 2, 24), -// exp: true, -// }, -// { -// name: "second interval", -// date: task.NewDate(2021, 3, 17), -// exp: true, -// }, -// { -// name: "second interval plus one week", -// date: task.NewDate(2021, 3, 24), -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// test.Equals(t, tc.exp, everyNWeeks.RecursOn(tc.date)) -// }) -// } -// }) -// } + everyNWeeks := item.EveryNWeeks{ + Start: item.NewDate(2021, 2, 3), + N: 3, + } + everyNWeeksStr := "2021-02-03, every 3 weeks" -// func TestEveryNMonths(t *testing.T) { -// everyNMonths := task.EveryNMonths{ -// Start: task.NewDate(2021, 2, 3), -// N: 3, -// } -// everyNMonthsStr := "2021-02-03 (wednesday), every 3 months" + t.Run("parse", func(t *testing.T) { + if everyNWeeks != item.NewRecurrer(everyNWeeksStr) { + t.Errorf("exp %v, got %v", everyNWeeks, item.NewRecurrer(everyNWeeksStr)) + } + }) -// t.Run("parse", func(t *testing.T) { -// test.Equals(t, everyNMonths, task.NewRecurrer(everyNMonthsStr)) -// }) + t.Run("string", func(t *testing.T) { + if everyNWeeksStr != everyNWeeks.String() { + t.Errorf("exp %v, got %v", everyNWeeksStr, everyNWeeks.String()) + } + }) -// t.Run("string", func(t *testing.T) { -// test.Equals(t, everyNMonthsStr, everyNMonths.String()) -// }) + t.Run("recurs on", func(t *testing.T) { + for _, tc := range []struct { + name string + date item.Date + exp bool + }{ + { + name: "before start", + date: item.NewDate(2021, 1, 27), + }, + { + name: "on start", + date: item.NewDate(2021, 2, 3), + exp: true, + }, + { + name: "wrong day", + date: item.NewDate(2021, 2, 4), + }, + { + name: "one week after", + date: item.NewDate(2021, 2, 10), + }, + { + name: "first interval", + date: item.NewDate(2021, 2, 24), + exp: true, + }, + { + name: "second interval", + date: item.NewDate(2021, 3, 17), + exp: true, + }, + { + name: "second interval plus one week", + date: item.NewDate(2021, 3, 24), + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.exp != everyNWeeks.RecursOn(tc.date) { + t.Errorf("exp %v, got %v", tc.exp, everyNWeeks.RecursOn(tc.date)) + } + }) + } + }) +} -// t.Run("recurs on", func(t *testing.T) { -// for _, tc := range []struct { -// name string -// date task.Date -// exp bool -// }{ -// { -// name: "before start", -// date: task.NewDate(2021, 1, 27), -// }, -// { -// name: "on start", -// date: task.NewDate(2021, 2, 3), -// exp: true, -// }, -// { -// name: "8 weeks after", -// date: task.NewDate(2021, 3, 31), -// }, -// { -// name: "one month", -// date: task.NewDate(2021, 3, 3), -// }, -// { -// name: "3 months", -// date: task.NewDate(2021, 5, 3), -// exp: true, -// }, -// { -// name: "4 months", -// date: task.NewDate(2021, 6, 3), -// }, -// { -// name: "6 months", -// date: task.NewDate(2021, 8, 3), -// exp: true, -// }, -// } { -// t.Run(tc.name, func(t *testing.T) { -// test.Equals(t, tc.exp, everyNMonths.RecursOn(tc.date)) -// }) -// } -// }) +func TestEveryNMonths(t *testing.T) { + everyNMonths := item.EveryNMonths{ + Start: item.NewDate(2021, 2, 3), + N: 3, + } + everyNMonthsStr := "2021-02-03, every 3 months" -// t.Run("recurs every year", func(t *testing.T) { -// recur := task.EveryNMonths{ -// Start: task.NewDate(2021, 3, 1), -// N: 12, -// } -// test.Equals(t, false, recur.RecursOn(task.NewDate(2021, 3, 9))) -// }) + t.Run("parse", func(t *testing.T) { + if diff := cmp.Diff(everyNMonths, item.NewRecurrer(everyNMonthsStr)); diff != "" { + t.Errorf("(-exp, +got)%s\n", diff) + } + }) -// t.Run("bug", func(t *testing.T) { -// recur := task.EveryNMonths{ -// Start: task.NewDate(2021, 3, 1), -// N: 1, -// } -// test.Equals(t, false, recur.RecursOn(task.NewDate(2021, 11, 3))) -// }) -// } + t.Run("string", func(t *testing.T) { + if everyNMonthsStr != everyNMonths.String() { + t.Errorf("exp %v, got %v", everyNMonthsStr, everyNMonths.String()) + } + }) + + t.Run("recurs on", func(t *testing.T) { + for _, tc := range []struct { + name string + date item.Date + exp bool + }{ + { + name: "before start", + date: item.NewDate(2021, 1, 27), + }, + { + name: "on start", + date: item.NewDate(2021, 2, 3), + exp: true, + }, + { + name: "8 weeks after", + date: item.NewDate(2021, 3, 31), + }, + { + name: "one month", + date: item.NewDate(2021, 3, 3), + }, + { + name: "3 months", + date: item.NewDate(2021, 5, 3), + exp: true, + }, + { + name: "4 months", + date: item.NewDate(2021, 6, 3), + }, + { + name: "6 months", + date: item.NewDate(2021, 8, 3), + exp: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.exp != everyNMonths.RecursOn(tc.date) { + t.Errorf("exp %v, got %v", tc.exp, everyNMonths.RecursOn(tc.date)) + } + }) + } + }) + + t.Run("recurs every year", func(t *testing.T) { + recur := item.EveryNMonths{ + Start: item.NewDate(2021, 3, 1), + N: 12, + } + if recur.RecursOn(item.NewDate(2021, 3, 9)) { + t.Errorf("exp false, got true") + } + }) + + t.Run("bug", func(t *testing.T) { + recur := item.EveryNMonths{ + Start: item.NewDate(2021, 3, 1), + N: 1, + } + if recur.RecursOn(item.NewDate(2021, 11, 3)) { + t.Errorf("exp false, got true") + } + }) +}