From 835683e1c855dea7c82638142c51ee7deecb07ca Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Tue, 2 Feb 2021 08:47:58 +0100 Subject: [PATCH] multiple days in weekly recur --- internal/task/date.go | 30 ++++++++++ internal/task/date_test.go | 67 +++++++++++++++++++++ internal/task/recur.go | 37 +++++++++--- internal/task/recur_test.go | 113 ++++++++++++++++++++++++++++++++++-- 4 files changed, 233 insertions(+), 14 deletions(-) diff --git a/internal/task/date.go b/internal/task/date.go index 8051a0e..62464e0 100644 --- a/internal/task/date.go +++ b/internal/task/date.go @@ -2,6 +2,7 @@ package task import ( "fmt" + "sort" "strings" "time" ) @@ -17,6 +18,35 @@ func init() { Today = NewDate(year, int(month), day) } +type Weekdays []time.Weekday + +func (wds Weekdays) Len() int { return len(wds) } +func (wds Weekdays) Swap(i, j int) { wds[j], wds[i] = wds[i], wds[j] } +func (wds Weekdays) Less(i, j int) bool { + if wds[i] == time.Sunday { + return false + } + if wds[j] == time.Sunday { + return true + } + + return int(wds[i]) < int(wds[j]) +} + +func (wds Weekdays) Unique() Weekdays { + mwds := map[time.Weekday]bool{} + for _, wd := range wds { + mwds[wd] = true + } + newWds := Weekdays{} + for wd := range mwds { + newWds = append(newWds, wd) + } + sort.Sort(newWds) + + return newWds +} + type Date struct { t time.Time } diff --git a/internal/task/date_test.go b/internal/task/date_test.go index 3a8d5b4..480db58 100644 --- a/internal/task/date_test.go +++ b/internal/task/date_test.go @@ -1,12 +1,79 @@ package task_test import ( + "sort" "testing" + "time" "git.sr.ht/~ewintr/go-kit/test" "git.sr.ht/~ewintr/gte/internal/task" ) +func TestWeekdaysSort(t *testing.T) { + for _, tc := range []struct { + name string + input task.Weekdays + exp task.Weekdays + }{ + { + name: "empty", + }, + { + name: "one", + input: task.Weekdays{time.Tuesday}, + exp: task.Weekdays{time.Tuesday}, + }, + { + name: "multiple", + input: task.Weekdays{time.Wednesday, time.Tuesday, time.Monday}, + exp: task.Weekdays{time.Monday, time.Tuesday, time.Wednesday}, + }, + { + name: "sunday is last", + input: task.Weekdays{time.Saturday, time.Sunday, time.Monday}, + exp: task.Weekdays{time.Monday, time.Saturday, time.Sunday}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + sort.Sort(tc.input) + test.Equals(t, tc.exp, tc.input) + }) + } +} + +func TestWeekdaysUnique(t *testing.T) { + for _, tc := range []struct { + name string + input task.Weekdays + exp task.Weekdays + }{ + { + name: "empty", + input: task.Weekdays{}, + exp: task.Weekdays{}, + }, + { + name: "single", + input: task.Weekdays{time.Monday}, + exp: task.Weekdays{time.Monday}, + }, + { + name: "no doubles", + input: task.Weekdays{time.Monday, time.Tuesday, time.Wednesday}, + exp: task.Weekdays{time.Monday, time.Tuesday, time.Wednesday}, + }, + { + name: "doubles", + input: task.Weekdays{time.Monday, time.Monday, time.Wednesday, time.Monday}, + exp: task.Weekdays{time.Monday, time.Wednesday}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, tc.exp, tc.input.Unique()) + }) + } +} + func TestNewDateFromString(t *testing.T) { task.Today = task.NewDate(2021, 1, 30) for _, tc := range []struct { diff --git a/internal/task/recur.go b/internal/task/recur.go index b319029..e20172b 100644 --- a/internal/task/recur.go +++ b/internal/task/recur.go @@ -64,11 +64,11 @@ func (d Daily) String() string { } type Weekly struct { - Start Date - Weekday time.Weekday + Start Date + Weekdays Weekdays } -// yyyy-mm-dd, weekly, wednesday +// yyyy-mm-dd, weekly, wednesday & saturday & sunday func ParseWeekly(start Date, terms []string) (Recurrer, bool) { if len(terms) < 2 { return nil, false @@ -78,14 +78,21 @@ func ParseWeekly(start Date, terms []string) (Recurrer, bool) { return nil, false } - wd, ok := ParseWeekday(terms[1]) - if !ok { + wds := Weekdays{} + for _, wdStr := range strings.Split(terms[1], "&") { + wd, ok := ParseWeekday(wdStr) + if !ok { + continue + } + wds = append(wds, wd) + } + if len(wds) == 0 { return nil, false } return Weekly{ - Start: start, - Weekday: wd, + Start: start, + Weekdays: wds.Unique(), }, true } @@ -94,11 +101,23 @@ func (w Weekly) RecursOn(date Date) bool { return false } - return w.Weekday == date.Weekday() + for _, wd := range w.Weekdays { + if wd == date.Weekday() { + return true + } + } + + return false } func (w Weekly) String() string { - return fmt.Sprintf("%s, weekly, %s", w.Start.String(), strings.ToLower(w.Weekday.String())) + weekdayStrs := []string{} + for _, wd := range w.Weekdays { + weekdayStrs = append(weekdayStrs, wd.String()) + } + weekdayStr := strings.Join(weekdayStrs, " & ") + + return fmt.Sprintf("%s, weekly, %s", w.Start.String(), strings.ToLower(weekdayStr)) } type Biweekly struct { diff --git a/internal/task/recur_test.go b/internal/task/recur_test.go index 0e7e12c..8e53d89 100644 --- a/internal/task/recur_test.go +++ b/internal/task/recur_test.go @@ -50,12 +50,110 @@ func TestDaily(t *testing.T) { }) } +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) + } + }) + } +} + func TestWeekly(t *testing.T) { weekly := task.Weekly{ - Start: task.NewDate(2021, 1, 31), // a sunday - Weekday: time.Wednesday, + Start: task.NewDate(2021, 1, 31), // a sunday + Weekdays: task.Weekdays{ + time.Monday, + time.Wednesday, + time.Thursday, + }, } - weeklyStr := "2021-01-31 (sunday), weekly, wednesday" + weeklyStr := "2021-01-31 (sunday), weekly, monday & wednesday & thursday" t.Run("parse", func(t *testing.T) { test.Equals(t, weekly, task.NewRecurrer(weeklyStr)) @@ -76,14 +174,19 @@ func TestWeekly(t *testing.T) { date: task.NewDate(2021, 1, 27), // a wednesday }, { - name: "wrong weekday", + name: "right weekday", date: task.NewDate(2021, 2, 1), // a monday + exp: true, }, { - name: "right day", + 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))