Compare commits
No commits in common. "5df7d3ff3d464b920d6676f81a39037e4ff5e486" and "3646aa096126ac2bcf1cda1aeba413c67a96a5de" have entirely different histories.
5df7d3ff3d
...
3646aa0961
|
@ -1 +1,3 @@
|
||||||
*.db*
|
test.db*
|
||||||
|
plannersync
|
||||||
|
plan
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -7,15 +7,5 @@ sync-run:
|
||||||
sync-debug:
|
sync-debug:
|
||||||
cd sync/service && dlv debug . -- -dbname localhost -dbport 5432 -dbname planner -dbuser test -dbpassword test -port 8092 -key testKey
|
cd sync/service && dlv debug . -- -dbname localhost -dbport 5432 -dbname planner -dbuser test -dbpassword test -port 8092 -key testKey
|
||||||
|
|
||||||
sync-build:
|
|
||||||
go build -o dist/plannersync ./sync/service/
|
|
||||||
|
|
||||||
sync-deploy:
|
|
||||||
ssh server sudo /usr/bin/systemctl stop plannersync.service
|
|
||||||
scp dist/plannersync server:/usr/local/bin/plannersync
|
|
||||||
ssh server sudo /usr/bin/systemctl start plannersync.service
|
|
||||||
|
|
||||||
database:
|
database:
|
||||||
docker run -e POSTGRES_USER=test -e POSTGRES_PASSWORD=test -e POSTGRES_DB=planner -p 5432:5432 postgres:16
|
docker run -e POSTGRES_USER=test -e POSTGRES_PASSWORD=test -e POSTGRES_DB=planner -p 5432:5432 postgres:16
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
279
item/date.go
279
item/date.go
|
@ -1,279 +0,0 @@
|
||||||
package item
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DateFormat = "2006-01-02"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Today() Date {
|
|
||||||
year, month, day := time.Now().Date()
|
|
||||||
return 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Date) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(d.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Date) UnmarshalJSON(data []byte) error {
|
|
||||||
dateString := ""
|
|
||||||
if err := json.Unmarshal(data, &dateString); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nd := NewDateFromString(dateString)
|
|
||||||
d.t = nd.Time()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDate(year, month, day int) Date {
|
|
||||||
|
|
||||||
if year == 0 || month == 0 || month > 12 || day == 0 {
|
|
||||||
return Date{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var m time.Month
|
|
||||||
switch month {
|
|
||||||
case 1:
|
|
||||||
m = time.January
|
|
||||||
case 2:
|
|
||||||
m = time.February
|
|
||||||
case 3:
|
|
||||||
m = time.March
|
|
||||||
case 4:
|
|
||||||
m = time.April
|
|
||||||
case 5:
|
|
||||||
m = time.May
|
|
||||||
case 6:
|
|
||||||
m = time.June
|
|
||||||
case 7:
|
|
||||||
m = time.July
|
|
||||||
case 8:
|
|
||||||
m = time.August
|
|
||||||
case 9:
|
|
||||||
m = time.September
|
|
||||||
case 10:
|
|
||||||
m = time.October
|
|
||||||
case 11:
|
|
||||||
m = time.November
|
|
||||||
case 12:
|
|
||||||
m = time.December
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.Date(year, m, day, 0, 0, 0, 0, time.UTC)
|
|
||||||
|
|
||||||
return Date{
|
|
||||||
t: t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDateFromString(date string) Date {
|
|
||||||
date = strings.ToLower(strings.TrimSpace(date))
|
|
||||||
|
|
||||||
switch date {
|
|
||||||
case "":
|
|
||||||
fallthrough
|
|
||||||
case "no-date":
|
|
||||||
fallthrough
|
|
||||||
case "no date":
|
|
||||||
return Date{}
|
|
||||||
case "today":
|
|
||||||
return Today()
|
|
||||||
case "tod":
|
|
||||||
return Today()
|
|
||||||
case "tomorrow":
|
|
||||||
return Today().AddDays(1)
|
|
||||||
case "tom":
|
|
||||||
return Today().AddDays(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := time.Parse("2006-01-02", fmt.Sprintf("%.10s", date))
|
|
||||||
if err == nil {
|
|
||||||
return Date{t: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
newWeekday, ok := ParseWeekday(date)
|
|
||||||
if !ok {
|
|
||||||
return Date{}
|
|
||||||
}
|
|
||||||
daysToAdd := findDaysToWeekday(Today().Weekday(), newWeekday)
|
|
||||||
|
|
||||||
return Today().Add(daysToAdd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findDaysToWeekday(current, wanted time.Weekday) int {
|
|
||||||
daysToAdd := int(wanted) - int(current)
|
|
||||||
if daysToAdd <= 0 {
|
|
||||||
daysToAdd += 7
|
|
||||||
}
|
|
||||||
|
|
||||||
return daysToAdd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) DaysBetween(d2 Date) int {
|
|
||||||
tDate := d2
|
|
||||||
end := d
|
|
||||||
if !end.After(tDate) {
|
|
||||||
end = d2
|
|
||||||
tDate = d
|
|
||||||
}
|
|
||||||
|
|
||||||
days := 0
|
|
||||||
for {
|
|
||||||
if tDate.Add(days).Equal(end) {
|
|
||||||
return days
|
|
||||||
}
|
|
||||||
days++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) String() string {
|
|
||||||
if d.t.IsZero() {
|
|
||||||
return "no date"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.ToLower(d.t.Format(DateFormat))
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (d Date) Human() string {
|
|
||||||
// switch {
|
|
||||||
// case d.IsZero():
|
|
||||||
// return "-"
|
|
||||||
// case d.Equal(Today()):
|
|
||||||
// return "today"
|
|
||||||
// case d.Equal(Today().Add(1)):
|
|
||||||
// return "tomorrow"
|
|
||||||
// case d.After(Today()) && Today().Add(8).After(d):
|
|
||||||
// return strings.ToLower(d.t.Format("Monday"))
|
|
||||||
// default:
|
|
||||||
// return strings.ToLower(d.t.Format(DateFormat))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (d Date) IsZero() bool {
|
|
||||||
return d.t.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) Time() time.Time {
|
|
||||||
return d.t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) Weekday() time.Weekday {
|
|
||||||
return d.t.Weekday()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) Day() int {
|
|
||||||
return d.t.Day()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) Add(days int) Date {
|
|
||||||
year, month, day := d.t.Date()
|
|
||||||
return NewDate(year, int(month), day+days)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) AddMonths(addMonths int) Date {
|
|
||||||
year, mmonth, day := d.t.Date()
|
|
||||||
month := int(mmonth)
|
|
||||||
for m := 1; m <= addMonths; m++ {
|
|
||||||
month += 1
|
|
||||||
if month == 12 {
|
|
||||||
year += 1
|
|
||||||
month = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewDate(year, month, day)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) Equal(ud Date) bool {
|
|
||||||
return d.t.Equal(ud.Time())
|
|
||||||
}
|
|
||||||
|
|
||||||
// After reports whether d is after ud
|
|
||||||
func (d Date) After(ud Date) bool {
|
|
||||||
return d.t.After(ud.Time())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) AddDays(amount int) Date {
|
|
||||||
year, month, date := d.t.Date()
|
|
||||||
|
|
||||||
return NewDate(year, int(month), date+amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseWeekday(wd string) (time.Weekday, bool) {
|
|
||||||
switch lowerAndTrim(wd) {
|
|
||||||
case "monday":
|
|
||||||
return time.Monday, true
|
|
||||||
case "mon":
|
|
||||||
return time.Monday, true
|
|
||||||
case "tuesday":
|
|
||||||
return time.Tuesday, true
|
|
||||||
case "tue":
|
|
||||||
return time.Tuesday, true
|
|
||||||
case "wednesday":
|
|
||||||
return time.Wednesday, true
|
|
||||||
case "wed":
|
|
||||||
return time.Wednesday, true
|
|
||||||
case "thursday":
|
|
||||||
return time.Thursday, true
|
|
||||||
case "thu":
|
|
||||||
return time.Thursday, true
|
|
||||||
case "friday":
|
|
||||||
return time.Friday, true
|
|
||||||
case "fri":
|
|
||||||
return time.Friday, true
|
|
||||||
case "saturday":
|
|
||||||
return time.Saturday, true
|
|
||||||
case "sat":
|
|
||||||
return time.Saturday, true
|
|
||||||
case "sunday":
|
|
||||||
return time.Sunday, true
|
|
||||||
case "sun":
|
|
||||||
return time.Sunday, true
|
|
||||||
default:
|
|
||||||
return time.Monday, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lowerAndTrim(str string) string {
|
|
||||||
return strings.TrimSpace(strings.ToLower(str))
|
|
||||||
}
|
|
|
@ -1,327 +0,0 @@
|
||||||
package item_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"go-mod.ewintr.nl/planner/item"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWeekdaysSort(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
input item.Weekdays
|
|
||||||
exp item.Weekdays
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one",
|
|
||||||
input: item.Weekdays{time.Tuesday},
|
|
||||||
exp: item.Weekdays{time.Tuesday},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple",
|
|
||||||
input: item.Weekdays{time.Wednesday, time.Tuesday, time.Monday},
|
|
||||||
exp: item.Weekdays{time.Monday, time.Tuesday, time.Wednesday},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sunday is last",
|
|
||||||
input: item.Weekdays{time.Saturday, time.Sunday, time.Monday},
|
|
||||||
exp: item.Weekdays{time.Monday, time.Saturday, time.Sunday},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
sort.Sort(tc.input)
|
|
||||||
if diff := cmp.Diff(tc.exp, tc.input); diff != "" {
|
|
||||||
t.Errorf("(-exp, +got)%s\n", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWeekdaysUnique(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
input item.Weekdays
|
|
||||||
exp item.Weekdays
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
input: item.Weekdays{},
|
|
||||||
exp: item.Weekdays{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single",
|
|
||||||
input: item.Weekdays{time.Monday},
|
|
||||||
exp: item.Weekdays{time.Monday},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no doubles",
|
|
||||||
input: item.Weekdays{time.Monday, time.Tuesday, time.Wednesday},
|
|
||||||
exp: item.Weekdays{time.Monday, time.Tuesday, time.Wednesday},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "doubles",
|
|
||||||
input: item.Weekdays{time.Monday, time.Monday, time.Wednesday, time.Monday},
|
|
||||||
exp: item.Weekdays{time.Monday, time.Wednesday},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if diff := cmp.Diff(tc.exp, tc.input.Unique()); diff != "" {
|
|
||||||
t.Errorf("(-exp, +got)%s\n", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewDateFromString(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("simple", func(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
exp item.Date
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
exp: item.Date{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no date",
|
|
||||||
input: "no date",
|
|
||||||
exp: item.Date{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "short",
|
|
||||||
input: "2021-01-30",
|
|
||||||
exp: item.NewDate(2021, 1, 30),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if diff := cmp.Diff(tc.exp, item.NewDateFromString(tc.input)); diff != "" {
|
|
||||||
t.Errorf("(-exp, +got)%s\n", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("day name", func(t *testing.T) {
|
|
||||||
monday := item.Today().Add(1)
|
|
||||||
for {
|
|
||||||
if monday.Weekday() == time.Monday {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
monday = monday.Add(1)
|
|
||||||
}
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "dayname lowercase",
|
|
||||||
input: "monday",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dayname capitalized",
|
|
||||||
input: "Monday",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dayname short",
|
|
||||||
input: "mon",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if diff := cmp.Diff(monday, item.NewDateFromString(tc.input)); diff != "" {
|
|
||||||
t.Errorf("(-exp, +got)%s\n", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("relative days", func(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
exp item.Date
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "today",
|
|
||||||
exp: item.Today(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tod",
|
|
||||||
exp: item.Today(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tomorrow",
|
|
||||||
exp: item.Today().Add(1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "tom",
|
|
||||||
exp: item.Today().Add(1),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if diff := cmp.Diff(tc.exp, item.NewDateFromString(tc.name)); diff != "" {
|
|
||||||
t.Errorf("(-exp, +got)%s\n", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateDaysBetween(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
d1 item.Date
|
|
||||||
d2 item.Date
|
|
||||||
exp int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "same",
|
|
||||||
d1: item.NewDate(2021, 6, 23),
|
|
||||||
d2: item.NewDate(2021, 6, 23),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one",
|
|
||||||
d1: item.NewDate(2021, 6, 23),
|
|
||||||
d2: item.NewDate(2021, 6, 24),
|
|
||||||
exp: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "many",
|
|
||||||
d1: item.NewDate(2021, 6, 23),
|
|
||||||
d2: item.NewDate(2024, 3, 7),
|
|
||||||
exp: 988,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "edge",
|
|
||||||
d1: item.NewDate(2020, 12, 30),
|
|
||||||
d2: item.NewDate(2021, 1, 3),
|
|
||||||
exp: 4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "reverse",
|
|
||||||
d1: item.NewDate(2021, 6, 23),
|
|
||||||
d2: item.NewDate(2021, 5, 23),
|
|
||||||
exp: 31,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if tc.exp != tc.d1.DaysBetween(tc.d2) {
|
|
||||||
t.Errorf("exp %v, got %v", tc.exp, tc.d1.DaysBetween(tc.d2))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateString(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
date item.Date
|
|
||||||
exp string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "zero",
|
|
||||||
date: item.NewDate(0, 0, 0),
|
|
||||||
exp: "no date",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "normal",
|
|
||||||
date: item.NewDate(2021, 5, 30),
|
|
||||||
exp: "2021-05-30",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "normalize",
|
|
||||||
date: item.NewDate(2021, 5, 32),
|
|
||||||
exp: "2021-06-01",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if tc.exp != tc.date.String() {
|
|
||||||
t.Errorf("exp %v, got %v", tc.exp, tc.date.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// func TestDateHuman(t *testing.T) {
|
|
||||||
// for _, tc := range []struct {
|
|
||||||
// name string
|
|
||||||
// date task.Date
|
|
||||||
// exp string
|
|
||||||
// }{
|
|
||||||
// {
|
|
||||||
// name: "zero",
|
|
||||||
// date: task.NewDate(0, 0, 0),
|
|
||||||
// exp: "-",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "default",
|
|
||||||
// date: task.NewDate(2020, 1, 1),
|
|
||||||
// exp: "2020-01-01 (wednesday)",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "today",
|
|
||||||
// date: task.Today(),
|
|
||||||
// exp: "today",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "tomorrow",
|
|
||||||
// date: task.Today().Add(1),
|
|
||||||
// exp: "tomorrow",
|
|
||||||
// },
|
|
||||||
// } {
|
|
||||||
// t.Run(tc.name, func(t *testing.T) {
|
|
||||||
// test.Equals(t, tc.exp, tc.date.Human())
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func TestDateIsZero(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if !(item.Date{}.IsZero()) {
|
|
||||||
t.Errorf("exp true, got false")
|
|
||||||
}
|
|
||||||
if item.NewDate(2021, 6, 24).IsZero() {
|
|
||||||
t.Errorf("exp false, got true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDateAfter(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
day := item.NewDate(2021, 1, 31)
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
tDay item.Date
|
|
||||||
exp bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "after",
|
|
||||||
tDay: item.NewDate(2021, 1, 30),
|
|
||||||
exp: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "on",
|
|
||||||
tDay: day,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "before",
|
|
||||||
tDay: item.NewDate(2021, 2, 1),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
if diff := cmp.Diff(tc.exp, day.After(tc.tDay)); diff != "" {
|
|
||||||
t.Errorf("(-exp, +got)%s\n", diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -52,7 +52,7 @@ func (e *EventBody) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Recurrer Recurrer `json:"recurrer"`
|
Recurrer *Recur `json:"recurrer"`
|
||||||
RecurNext time.Time `json:"recurNext"`
|
RecurNext time.Time `json:"recurNext"`
|
||||||
EventBody
|
EventBody
|
||||||
}
|
}
|
||||||
|
@ -103,9 +103,9 @@ func (e Event) Valid() bool {
|
||||||
if e.Duration.Seconds() < 1 {
|
if e.Duration.Seconds() < 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if e.Recurrer != nil && !e.Recurrer.Valid() {
|
if e.Recurrer != nil && !e.Recurrer.Valid() {
|
||||||
// return false
|
return false
|
||||||
// }
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,205 +1,213 @@
|
||||||
package item_test
|
package item_test
|
||||||
|
|
||||||
// func TestNewEvent(t *testing.T) {
|
import (
|
||||||
// t.Parallel()
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
// oneHour, err := time.ParseDuration("1h")
|
"github.com/google/go-cmp/cmp"
|
||||||
// if err != nil {
|
"go-mod.ewintr.nl/planner/item"
|
||||||
// t.Errorf("exp nil, got %v", err)
|
)
|
||||||
// }
|
|
||||||
// for _, tc := range []struct {
|
|
||||||
// name string
|
|
||||||
// it item.Item
|
|
||||||
// expEvent item.Event
|
|
||||||
// expErr bool
|
|
||||||
// }{
|
|
||||||
// {
|
|
||||||
// name: "wrong kind",
|
|
||||||
// it: item.Item{
|
|
||||||
// ID: "a",
|
|
||||||
// Kind: item.KindTask,
|
|
||||||
// Body: `{
|
|
||||||
// "title":"title",
|
|
||||||
// "start":"2024-09-20T08:00:00Z",
|
|
||||||
// "duration":"1h"
|
|
||||||
// }`,
|
|
||||||
// },
|
|
||||||
// expErr: true,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "invalid json",
|
|
||||||
// it: item.Item{
|
|
||||||
// ID: "a",
|
|
||||||
// Kind: item.KindEvent,
|
|
||||||
// Body: `{"id":"a"`,
|
|
||||||
// },
|
|
||||||
// expErr: true,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "valid",
|
|
||||||
// it: item.Item{
|
|
||||||
// ID: "a",
|
|
||||||
// Kind: item.KindEvent,
|
|
||||||
// Recurrer: &item.Recur{
|
|
||||||
// Start: time.Date(2024, 12, 8, 9, 0, 0, 0, time.UTC),
|
|
||||||
// Period: item.PeriodDay,
|
|
||||||
// Count: 1,
|
|
||||||
// },
|
|
||||||
// Body: `{
|
|
||||||
// "title":"title",
|
|
||||||
// "start":"2024-09-20T08:00:00Z",
|
|
||||||
// "duration":"1h"
|
|
||||||
// }`,
|
|
||||||
// },
|
|
||||||
// expEvent: item.Event{
|
|
||||||
// ID: "a",
|
|
||||||
// Recurrer: &item.Recur{
|
|
||||||
// Start: time.Date(2024, 12, 8, 9, 0, 0, 0, time.UTC),
|
|
||||||
// Period: item.PeriodDay,
|
|
||||||
// Count: 1,
|
|
||||||
// },
|
|
||||||
// EventBody: item.EventBody{
|
|
||||||
// Title: "title",
|
|
||||||
// Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
|
||||||
// Duration: oneHour,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// } {
|
|
||||||
// t.Run(tc.name, func(t *testing.T) {
|
|
||||||
// actEvent, actErr := item.NewEvent(tc.it)
|
|
||||||
// if tc.expErr != (actErr != nil) {
|
|
||||||
// t.Errorf("exp nil, got %v", actErr)
|
|
||||||
// }
|
|
||||||
// if tc.expErr {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if diff := cmp.Diff(tc.expEvent, actEvent); diff != "" {
|
|
||||||
// t.Errorf("(exp +, got -)\n%s", diff)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestEventItem(t *testing.T) {
|
func TestNewEvent(t *testing.T) {
|
||||||
// t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// oneHour, err := time.ParseDuration("1h")
|
oneHour, err := time.ParseDuration("1h")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
||||||
// }
|
}
|
||||||
// for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
// name string
|
name string
|
||||||
// event item.Event
|
it item.Item
|
||||||
// expItem item.Item
|
expEvent item.Event
|
||||||
// expErr bool
|
expErr bool
|
||||||
// }{
|
}{
|
||||||
// {
|
{
|
||||||
// name: "empty",
|
name: "wrong kind",
|
||||||
// expItem: item.Item{
|
it: item.Item{
|
||||||
// Kind: item.KindEvent,
|
ID: "a",
|
||||||
// Updated: time.Time{},
|
Kind: item.KindTask,
|
||||||
// Body: `{"start":"0001-01-01T00:00:00Z","duration":"0s","title":""}`,
|
Body: `{
|
||||||
// },
|
"title":"title",
|
||||||
// },
|
"start":"2024-09-20T08:00:00Z",
|
||||||
// {
|
"duration":"1h"
|
||||||
// name: "normal",
|
}`,
|
||||||
// event: item.Event{
|
},
|
||||||
// ID: "a",
|
expErr: true,
|
||||||
// EventBody: item.EventBody{
|
},
|
||||||
// Title: "title",
|
{
|
||||||
// Start: time.Date(2024, 9, 23, 8, 0, 0, 0, time.UTC),
|
name: "invalid json",
|
||||||
// Duration: oneHour,
|
it: item.Item{
|
||||||
// },
|
ID: "a",
|
||||||
// },
|
Kind: item.KindEvent,
|
||||||
// expItem: item.Item{
|
Body: `{"id":"a"`,
|
||||||
// ID: "a",
|
},
|
||||||
// Kind: item.KindEvent,
|
expErr: true,
|
||||||
// Updated: time.Time{},
|
},
|
||||||
// Body: `{"start":"2024-09-23T08:00:00Z","duration":"1h0m0s","title":"title"}`,
|
{
|
||||||
// },
|
name: "valid",
|
||||||
// },
|
it: item.Item{
|
||||||
// } {
|
ID: "a",
|
||||||
// t.Run(tc.name, func(t *testing.T) {
|
Kind: item.KindEvent,
|
||||||
// actItem, actErr := tc.event.Item()
|
Recurrer: &item.Recur{
|
||||||
// if tc.expErr != (actErr != nil) {
|
Start: time.Date(2024, 12, 8, 9, 0, 0, 0, time.UTC),
|
||||||
// t.Errorf("exp nil, got %v", actErr)
|
Period: item.PeriodDay,
|
||||||
// }
|
Count: 1,
|
||||||
// if tc.expErr {
|
},
|
||||||
// return
|
Body: `{
|
||||||
// }
|
"title":"title",
|
||||||
// if diff := cmp.Diff(tc.expItem, actItem); diff != "" {
|
"start":"2024-09-20T08:00:00Z",
|
||||||
// t.Errorf("(exp+, got -)\n%s", diff)
|
"duration":"1h"
|
||||||
// }
|
}`,
|
||||||
// })
|
},
|
||||||
// }
|
expEvent: item.Event{
|
||||||
// }
|
ID: "a",
|
||||||
|
Recurrer: &item.Recur{
|
||||||
|
Start: time.Date(2024, 12, 8, 9, 0, 0, 0, time.UTC),
|
||||||
|
Period: item.PeriodDay,
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
EventBody: item.EventBody{
|
||||||
|
Title: "title",
|
||||||
|
Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
||||||
|
Duration: oneHour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actEvent, actErr := item.NewEvent(tc.it)
|
||||||
|
if tc.expErr != (actErr != nil) {
|
||||||
|
t.Errorf("exp nil, got %v", actErr)
|
||||||
|
}
|
||||||
|
if tc.expErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.expEvent, actEvent); diff != "" {
|
||||||
|
t.Errorf("(exp +, got -)\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// func TestEventValidate(t *testing.T) {
|
func TestEventItem(t *testing.T) {
|
||||||
// t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// oneHour, err := time.ParseDuration("1h")
|
oneHour, err := time.ParseDuration("1h")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
||||||
// }
|
}
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
event item.Event
|
||||||
|
expItem item.Item
|
||||||
|
expErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
expItem: item.Item{
|
||||||
|
Kind: item.KindEvent,
|
||||||
|
Updated: time.Time{},
|
||||||
|
Body: `{"start":"0001-01-01T00:00:00Z","duration":"0s","title":""}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "normal",
|
||||||
|
event: item.Event{
|
||||||
|
ID: "a",
|
||||||
|
EventBody: item.EventBody{
|
||||||
|
Title: "title",
|
||||||
|
Start: time.Date(2024, 9, 23, 8, 0, 0, 0, time.UTC),
|
||||||
|
Duration: oneHour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expItem: item.Item{
|
||||||
|
ID: "a",
|
||||||
|
Kind: item.KindEvent,
|
||||||
|
Updated: time.Time{},
|
||||||
|
Body: `{"start":"2024-09-23T08:00:00Z","duration":"1h0m0s","title":"title"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actItem, actErr := tc.event.Item()
|
||||||
|
if tc.expErr != (actErr != nil) {
|
||||||
|
t.Errorf("exp nil, got %v", actErr)
|
||||||
|
}
|
||||||
|
if tc.expErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.expItem, actItem); diff != "" {
|
||||||
|
t.Errorf("(exp+, got -)\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// for _, tc := range []struct {
|
func TestEventValidate(t *testing.T) {
|
||||||
// name string
|
t.Parallel()
|
||||||
// event item.Event
|
|
||||||
// exp bool
|
|
||||||
// }{
|
|
||||||
// {
|
|
||||||
// name: "empty",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "missing title",
|
|
||||||
// event: item.Event{
|
|
||||||
// ID: "a",
|
|
||||||
// EventBody: item.EventBody{
|
|
||||||
// Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
|
||||||
// Duration: oneHour,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "no date",
|
|
||||||
// event: item.Event{
|
|
||||||
// ID: "a",
|
|
||||||
// EventBody: item.EventBody{
|
|
||||||
// Title: "title",
|
|
||||||
// Start: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC),
|
|
||||||
// Duration: oneHour,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "no duration",
|
|
||||||
// event: item.Event{
|
|
||||||
// ID: "a",
|
|
||||||
// EventBody: item.EventBody{
|
|
||||||
// Title: "title",
|
|
||||||
// Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "valid",
|
|
||||||
// event: item.Event{
|
|
||||||
// ID: "a",
|
|
||||||
// EventBody: item.EventBody{
|
|
||||||
// Title: "title",
|
|
||||||
// Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
|
||||||
// Duration: oneHour,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// exp: true,
|
|
||||||
// },
|
|
||||||
// } {
|
|
||||||
// t.Run(tc.name, func(t *testing.T) {
|
|
||||||
// if act := tc.event.Valid(); tc.exp != act {
|
|
||||||
// t.Errorf("exp %v, got %v", tc.exp, act)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// })
|
oneHour, err := time.ParseDuration("1h")
|
||||||
// }
|
if err != nil {
|
||||||
// }
|
t.Errorf("exp nil, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
event item.Event
|
||||||
|
exp bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing title",
|
||||||
|
event: item.Event{
|
||||||
|
ID: "a",
|
||||||
|
EventBody: item.EventBody{
|
||||||
|
Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
||||||
|
Duration: oneHour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no date",
|
||||||
|
event: item.Event{
|
||||||
|
ID: "a",
|
||||||
|
EventBody: item.EventBody{
|
||||||
|
Title: "title",
|
||||||
|
Start: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC),
|
||||||
|
Duration: oneHour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no duration",
|
||||||
|
event: item.Event{
|
||||||
|
ID: "a",
|
||||||
|
EventBody: item.EventBody{
|
||||||
|
Title: "title",
|
||||||
|
Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid",
|
||||||
|
event: item.Event{
|
||||||
|
ID: "a",
|
||||||
|
EventBody: item.EventBody{
|
||||||
|
Title: "title",
|
||||||
|
Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC),
|
||||||
|
Duration: oneHour,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exp: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if act := tc.event.Valid(); tc.exp != act {
|
||||||
|
t.Errorf("exp %v, got %v", tc.exp, act)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ type Item struct {
|
||||||
Kind Kind `json:"kind"`
|
Kind Kind `json:"kind"`
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Recurrer Recurrer `json:"recurrer"`
|
Recurrer *Recur `json:"recurrer"`
|
||||||
RecurNext time.Time `json:"recurNext"`
|
RecurNext time.Time `json:"recurNext"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
337
item/recur.go
337
item/recur.go
|
@ -1,312 +1,61 @@
|
||||||
package item
|
package item
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Recurrer interface {
|
type RecurPeriod string
|
||||||
RecursOn(date Date) bool
|
|
||||||
String() string
|
const (
|
||||||
|
PeriodDay RecurPeriod = "day"
|
||||||
|
PeriodMonth RecurPeriod = "month"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ValidPeriods = []RecurPeriod{PeriodDay, PeriodMonth}
|
||||||
|
|
||||||
|
type Recur struct {
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
Period RecurPeriod `json:"period"`
|
||||||
|
Count int `json:"count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRecurrer(recurStr string) Recurrer {
|
func (r *Recur) On(date time.Time) bool {
|
||||||
terms := strings.Split(recurStr, ",")
|
switch r.Period {
|
||||||
if len(terms) < 2 {
|
case PeriodDay:
|
||||||
return nil
|
return r.onDays(date)
|
||||||
|
case PeriodMonth:
|
||||||
|
return r.onMonths(date)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
start := NewDateFromString(terms[0])
|
|
||||||
if start.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
terms = terms[1:]
|
|
||||||
for i, t := range terms {
|
|
||||||
terms[i] = strings.TrimSpace(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, parseFunc := range []func(Date, []string) (Recurrer, bool){
|
|
||||||
ParseDaily, ParseEveryNDays, ParseWeekly, ParseBiweekly,
|
|
||||||
ParseEveryNWeeks, ParseEveryNMonths,
|
|
||||||
} {
|
|
||||||
if recur, ok := parseFunc(start, terms); ok {
|
|
||||||
return recur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Daily struct {
|
func (r *Recur) onDays(date time.Time) bool {
|
||||||
Start Date
|
if r.Start.After(date) {
|
||||||
}
|
|
||||||
|
|
||||||
// yyyy-mm-dd, daily
|
|
||||||
func ParseDaily(start Date, terms []string) (Recurrer, bool) {
|
|
||||||
if len(terms) < 1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if terms[0] != "daily" {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return Daily{
|
|
||||||
Start: start,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Daily) RecursOn(date Date) bool {
|
|
||||||
return date.Equal(d.Start) || date.After(d.Start)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Daily) String() string {
|
|
||||||
return fmt.Sprintf("%s, daily", d.Start.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
type EveryNDays struct {
|
|
||||||
Start Date
|
|
||||||
N int
|
|
||||||
}
|
|
||||||
|
|
||||||
// yyyy-mm-dd, every 3 days
|
|
||||||
func ParseEveryNDays(start Date, terms []string) (Recurrer, bool) {
|
|
||||||
if len(terms) != 1 {
|
|
||||||
return EveryNDays{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
terms = strings.Split(terms[0], " ")
|
|
||||||
if len(terms) != 3 || terms[0] != "every" || terms[2] != "days" {
|
|
||||||
return EveryNDays{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := strconv.Atoi(terms[1])
|
|
||||||
if err != nil {
|
|
||||||
return EveryNDays{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return EveryNDays{
|
|
||||||
Start: start,
|
|
||||||
N: n,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nd EveryNDays) RecursOn(date Date) bool {
|
|
||||||
if nd.Start.After(date) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
testDate := nd.Start
|
testDate := r.Start
|
||||||
for {
|
for {
|
||||||
switch {
|
if testDate.Equal(date) {
|
||||||
case testDate.Equal(date):
|
|
||||||
return true
|
|
||||||
case testDate.After(date):
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
testDate = testDate.Add(nd.N)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nd EveryNDays) String() string {
|
|
||||||
return fmt.Sprintf("%s, every %d days", nd.Start.String(), nd.N)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Weekly struct {
|
|
||||||
Start Date
|
|
||||||
Weekdays Weekdays
|
|
||||||
}
|
|
||||||
|
|
||||||
// yyyy-mm-dd, weekly, wednesday & saturday & sunday
|
|
||||||
func ParseWeekly(start Date, terms []string) (Recurrer, bool) {
|
|
||||||
if len(terms) < 2 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if terms[0] != "weekly" {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
Weekdays: wds.Unique(),
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Weekly) RecursOn(date Date) bool {
|
|
||||||
if w.Start.After(date) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, wd := range w.Weekdays {
|
|
||||||
if wd == date.Weekday() {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
if testDate.After(date) {
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Weekly) String() 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 {
|
|
||||||
Start Date
|
|
||||||
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
|
return false
|
||||||
}
|
}
|
||||||
tDate = tDate.AddDays(14)
|
|
||||||
|
dur := time.Duration(r.Count) * 24 * time.Hour
|
||||||
|
testDate = testDate.Add(dur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Biweekly) String() string {
|
func (r *Recur) onMonths(date time.Time) bool {
|
||||||
return fmt.Sprintf("%s, biweekly, %s", b.Start.String(), strings.ToLower(b.Weekday.String()))
|
if r.Start.After(date) {
|
||||||
}
|
|
||||||
|
|
||||||
type EveryNWeeks struct {
|
|
||||||
Start Date
|
|
||||||
N int
|
|
||||||
}
|
|
||||||
|
|
||||||
// yyyy-mm-dd, every 3 weeks
|
|
||||||
func ParseEveryNWeeks(start Date, terms []string) (Recurrer, bool) {
|
|
||||||
if len(terms) != 1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
terms = strings.Split(terms[0], " ")
|
|
||||||
if len(terms) != 3 || terms[0] != "every" || terms[2] != "weeks" {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(terms[1])
|
|
||||||
if err != nil || n < 1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return EveryNWeeks{
|
|
||||||
Start: start,
|
|
||||||
N: n,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enw EveryNWeeks) RecursOn(date Date) bool {
|
|
||||||
if enw.Start.After(date) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if enw.Start.Equal(date) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
intervalDays := enw.N * 7
|
|
||||||
return enw.Start.DaysBetween(date)%intervalDays == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enw EveryNWeeks) String() string {
|
|
||||||
return fmt.Sprintf("%s, every %d weeks", enw.Start.String(), enw.N)
|
|
||||||
}
|
|
||||||
|
|
||||||
type EveryNMonths struct {
|
|
||||||
Start Date
|
|
||||||
N int
|
|
||||||
}
|
|
||||||
|
|
||||||
// yyyy-mm-dd, every 3 months
|
|
||||||
func ParseEveryNMonths(start Date, terms []string) (Recurrer, bool) {
|
|
||||||
if len(terms) != 1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
terms = strings.Split(terms[0], " ")
|
|
||||||
if len(terms) != 3 || terms[0] != "every" || terms[2] != "months" {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(terms[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return EveryNMonths{
|
|
||||||
Start: start,
|
|
||||||
N: n,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enm EveryNMonths) RecursOn(date Date) bool {
|
|
||||||
if enm.Start.After(date) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
tDate := enm.Start
|
tDate := r.Start
|
||||||
for {
|
for {
|
||||||
if tDate.Equal(date) {
|
if tDate.Equal(date) {
|
||||||
return true
|
return true
|
||||||
|
@ -314,11 +63,23 @@ func (enm EveryNMonths) RecursOn(date Date) bool {
|
||||||
if tDate.After(date) {
|
if tDate.After(date) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
tDate = tDate.AddMonths(enm.N)
|
|
||||||
|
y, m, d := tDate.Date()
|
||||||
|
tDate = time.Date(y, m+time.Month(r.Count), d, 0, 0, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enm EveryNMonths) String() string {
|
func (r *Recur) NextAfter(old time.Time) time.Time {
|
||||||
return fmt.Sprintf("%s, every %d months", enm.Start.String(), enm.N)
|
day, _ := time.ParseDuration("24h")
|
||||||
|
test := old.Add(day)
|
||||||
|
for {
|
||||||
|
if r.On(test) || test.After(time.Date(2500, 1, 1, 0, 0, 0, 0, time.UTC)) {
|
||||||
|
return test
|
||||||
|
}
|
||||||
|
test.Add(day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Recur) Valid() bool {
|
||||||
|
return r.Start.IsZero() || !slices.Contains(ValidPeriods, r.Period)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,417 +1,105 @@
|
||||||
package item_test
|
package item_test
|
||||||
|
|
||||||
// func TestDaily(t *testing.T) {
|
import (
|
||||||
// daily := task.Daily{
|
"testing"
|
||||||
// Start: task.NewDate(2021, 1, 31), // a sunday
|
"time"
|
||||||
// }
|
|
||||||
// dailyStr := "2021-01-31 (sunday), daily"
|
|
||||||
|
|
||||||
// t.Run("parse", func(t *testing.T) {
|
"go-mod.ewintr.nl/planner/item"
|
||||||
// test.Equals(t, daily, task.NewRecurrer(dailyStr))
|
)
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("string", func(t *testing.T) {
|
func TestRecur(t *testing.T) {
|
||||||
// test.Equals(t, dailyStr, daily.String())
|
t.Parallel()
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("recurs_on", func(t *testing.T) {
|
t.Run("days", func(t *testing.T) {
|
||||||
// for _, tc := range []struct {
|
r := item.Recur{
|
||||||
// name string
|
Start: time.Date(2024, 12, 1, 0, 0, 0, 0, time.UTC),
|
||||||
// date task.Date
|
Period: item.PeriodDay,
|
||||||
// exp bool
|
Count: 5,
|
||||||
// }{
|
}
|
||||||
// {
|
day := 24 * time.Hour
|
||||||
// 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))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestEveryNDays(t *testing.T) {
|
for _, tc := range []struct {
|
||||||
// every := task.EveryNDays{
|
name string
|
||||||
// Start: task.NewDate(2022, 6, 8),
|
date time.Time
|
||||||
// N: 5,
|
exp bool
|
||||||
// }
|
}{
|
||||||
// everyStr := "2022-06-08 (wednesday), every 5 days"
|
{
|
||||||
|
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("parse", func(t *testing.T) {
|
t.Run("months", func(t *testing.T) {
|
||||||
// test.Equals(t, every, task.NewRecurrer(everyStr))
|
r := item.Recur{
|
||||||
// })
|
Start: time.Date(2021, 2, 3, 0, 0, 0, 0, time.UTC),
|
||||||
|
Period: item.PeriodMonth,
|
||||||
|
Count: 3,
|
||||||
|
}
|
||||||
|
|
||||||
// t.Run("string", func(t *testing.T) {
|
for _, tc := range []struct {
|
||||||
// test.Equals(t, everyStr, every.String())
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 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 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
|
|
||||||
// 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) {
|
|
||||||
// test.Equals(t, weekly, task.NewRecurrer(weeklyStr))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 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 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 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"
|
|
||||||
|
|
||||||
// t.Run("parse", func(t *testing.T) {
|
|
||||||
// test.Equals(t, biweekly, task.NewRecurrer(biweeklyStr))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("string", func(t *testing.T) {
|
|
||||||
// test.Equals(t, biweeklyStr, biweekly.String())
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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("parse", func(t *testing.T) {
|
|
||||||
// test.Equals(t, everyNWeeks, task.NewRecurrer(everyNWeeksStr))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("string", func(t *testing.T) {
|
|
||||||
// test.Equals(t, everyNWeeksStr, everyNWeeks.String())
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
// test.Equals(t, everyNMonths, task.NewRecurrer(everyNMonthsStr))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 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 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))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// 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("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)))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
db_path: ./plan.db
|
|
||||||
sync_url: http://localhost:8092
|
|
||||||
api_key: testKey
|
|
|
@ -66,7 +66,6 @@ func (p *Postgres) Update(i item.Item, ts time.Time) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
return fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
}
|
}
|
||||||
i.RecurNext = i.Recurrer.Start
|
|
||||||
} else {
|
} else {
|
||||||
recurrerJSON = nil
|
recurrerJSON = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -36,26 +35,21 @@ func (r *Recur) Run(interval time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Recur) Recur() error {
|
func (r *Recur) Recur() error {
|
||||||
r.logger.Info("start looking for recurring items")
|
|
||||||
items, err := r.repoRecur.RecursBefore(time.Now())
|
items, err := r.repoRecur.RecursBefore(time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.logger.Info("found recurring items", "count", len(items))
|
|
||||||
for _, i := range items {
|
for _, i := range items {
|
||||||
r.logger.Info("processing recurring item", "item", fmt.Sprintf("%+v", i))
|
|
||||||
// spawn instance
|
// spawn instance
|
||||||
ne, err := item.NewEvent(i)
|
ne, err := item.NewEvent(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.logger.Info("processing recurring event", "event", fmt.Sprintf("%+v", ne))
|
|
||||||
y, m, d := i.RecurNext.Date()
|
y, m, d := i.RecurNext.Date()
|
||||||
ne.ID = uuid.New().String()
|
ne.ID = uuid.New().String()
|
||||||
ne.Recurrer = nil
|
ne.Recurrer = nil
|
||||||
ne.RecurNext = time.Time{}
|
ne.RecurNext = time.Time{}
|
||||||
ne.Start = time.Date(y, m, d, ne.Start.Hour(), ne.Start.Minute(), 0, 0, time.UTC)
|
ne.Start = time.Date(y, m, d, ne.Start.Hour(), ne.Start.Minute(), 0, 0, time.UTC)
|
||||||
r.logger.Info("created instance of recurring event", "event", fmt.Sprintf("%+v", ne))
|
|
||||||
|
|
||||||
ni, err := ne.Item()
|
ni, err := ne.Item()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -64,14 +58,11 @@ func (r *Recur) Recur() error {
|
||||||
if err := r.repoSync.Update(ni, time.Now()); err != nil {
|
if err := r.repoSync.Update(ni, time.Now()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.logger.Info("storen instance of recurring event", "recEventID", ne.ID, "instanceID", ni.ID)
|
|
||||||
|
|
||||||
// set next
|
// set next
|
||||||
next := i.Recurrer.NextAfter(i.RecurNext)
|
if err := r.repoRecur.RecursNext(i.ID, i.Recurrer.NextAfter(i.RecurNext), time.Now()); err != nil {
|
||||||
if err := r.repoRecur.RecursNext(i.ID, next, time.Now()); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.logger.Info("updated recur date", "recEventID", ne.ID, "next", next)
|
|
||||||
}
|
}
|
||||||
r.logger.Info("processed recurring items", "count", len(items))
|
r.logger.Info("processed recurring items", "count", len(items))
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ func main() {
|
||||||
"dbUser": *dbUser,
|
"dbUser": *dbUser,
|
||||||
})
|
})
|
||||||
recurrer := NewRecur(repo, repo, logger)
|
recurrer := NewRecur(repo, repo, logger)
|
||||||
go recurrer.Run(10 * time.Second)
|
go recurrer.Run(12 * time.Hour)
|
||||||
|
|
||||||
srv := NewServer(repo, *apiKey, logger)
|
srv := NewServer(repo, *apiKey, logger)
|
||||||
go http.ListenAndServe(fmt.Sprintf(":%s", *apiPort), srv)
|
go http.ListenAndServe(fmt.Sprintf(":%s", *apiPort), srv)
|
||||||
|
|
Loading…
Reference in New Issue