switch to datew

This commit is contained in:
Erik Winter 2024-12-22 08:37:12 +01:00
parent 3646aa0961
commit 22b8852a0a
11 changed files with 1313 additions and 152 deletions

4
.gitignore vendored
View File

@ -1,3 +1 @@
test.db*
plannersync
plan
*.db*

View File

@ -7,5 +7,15 @@ sync-run:
sync-debug:
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:
docker run -e POSTGRES_USER=test -e POSTGRES_PASSWORD=test -e POSTGRES_DB=planner -p 5432:5432 postgres:16

BIN
dist/plannersync vendored Executable file

Binary file not shown.

279
item/date.go Normal file
View File

@ -0,0 +1,279 @@
package item
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
)
const (
DateFormat = "2006-01-02 (Monday)"
)
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))
}

310
item/date_test.go Normal file
View File

@ -0,0 +1,310 @@
package item_test
// 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) {
// t.Run("no date", func(t *testing.T) {
// for _, tc := range []struct {
// name string
// input string
// exp task.Date
// }{
// {
// name: "empty",
// exp: task.Date{},
// },
// {
// name: "no date",
// input: "no date",
// exp: task.Date{},
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, tc.exp, task.NewDateFromString(tc.input))
// })
// }
// })
// t.Run("digits", func(t *testing.T) {
// for _, tc := range []struct {
// name string
// input string
// exp task.Date
// }{
// {
// name: "normal",
// input: "2021-01-30 (saturday)",
// exp: task.NewDate(2021, 1, 30),
// },
// {
// name: "short",
// input: "2021-01-30",
// exp: task.NewDate(2021, 1, 30),
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, tc.exp, task.NewDateFromString(tc.input))
// })
// }
// })
// t.Run("day name", func(t *testing.T) {
// monday := task.Today().Add(1)
// for {
// if monday.Weekday() == time.Monday {
// break
// }
// monday = monday.Add(1)
// }
// for _, tc := range []struct {
// name string
// input string
// exp task.Date
// }{
// {
// name: "dayname lowercase",
// input: "monday",
// },
// {
// name: "dayname capitalized",
// input: "Monday",
// },
// {
// name: "dayname short",
// input: "mon",
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, monday, task.NewDateFromString(tc.input))
// })
// }
// })
// t.Run("relative days", func(t *testing.T) {
// for _, tc := range []struct {
// name string
// exp task.Date
// }{
// {
// name: "today",
// exp: task.Today(),
// },
// {
// name: "tod",
// exp: task.Today(),
// },
// {
// name: "tomorrow",
// exp: task.Today().Add(1),
// },
// {
// name: "tom",
// exp: task.Today().Add(1),
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, tc.exp, task.NewDateFromString(tc.name))
// })
// }
// })
// }
// func TestDateDaysBetween(t *testing.T) {
// for _, tc := range []struct {
// name string
// d1 task.Date
// d2 task.Date
// exp int
// }{
// {
// name: "same",
// d1: task.NewDate(2021, 6, 23),
// d2: task.NewDate(2021, 6, 23),
// },
// {
// name: "one",
// d1: task.NewDate(2021, 6, 23),
// d2: task.NewDate(2021, 6, 24),
// exp: 1,
// },
// {
// name: "many",
// d1: task.NewDate(2021, 6, 23),
// d2: task.NewDate(2024, 3, 7),
// exp: 988,
// },
// {
// name: "edge",
// d1: task.NewDate(2020, 12, 30),
// d2: task.NewDate(2021, 1, 3),
// exp: 4,
// },
// {
// name: "reverse",
// d1: task.NewDate(2021, 6, 23),
// d2: task.NewDate(2021, 5, 23),
// exp: 31,
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, tc.exp, tc.d1.DaysBetween(tc.d2))
// })
// }
// }
// func TestDateString(t *testing.T) {
// for _, tc := range []struct {
// name string
// date task.Date
// exp string
// }{
// {
// name: "zero",
// date: task.NewDate(0, 0, 0),
// exp: "no date",
// },
// {
// name: "normal",
// date: task.NewDate(2021, 5, 30),
// exp: "2021-05-30 (sunday)",
// },
// {
// name: "normalize",
// date: task.NewDate(2021, 5, 32),
// exp: "2021-06-01 (tuesday)",
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, 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) {
// test.Equals(t, true, task.Date{}.IsZero())
// test.Equals(t, false, task.NewDate(2021, 6, 24).IsZero())
// }
// func TestDateAfter(t *testing.T) {
// day := task.NewDate(2021, 1, 31)
// for _, tc := range []struct {
// name string
// tDay task.Date
// exp bool
// }{
// {
// name: "after",
// tDay: task.NewDate(2021, 1, 30),
// exp: true,
// },
// {
// name: "on",
// tDay: day,
// },
// {
// name: "before",
// tDay: task.NewDate(2021, 2, 1),
// },
// } {
// t.Run(tc.name, func(t *testing.T) {
// test.Equals(t, tc.exp, day.After(tc.tDay))
// })
// }
// }

View File

@ -1,61 +1,312 @@
package item
import (
"slices"
"fmt"
"strconv"
"strings"
"time"
)
type RecurPeriod 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"`
type Recurrer interface {
RecursOn(date Date) bool
String() string
}
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 NewRecurrer(recurStr string) Recurrer {
terms := strings.Split(recurStr, ",")
if len(terms) < 2 {
return nil
}
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
}
func (r *Recur) onDays(date time.Time) bool {
if r.Start.After(date) {
type Daily struct {
Start 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
}
testDate := r.Start
testDate := nd.Start
for {
if testDate.Equal(date) {
switch {
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
}
if testDate.After(date) {
return false
}
dur := time.Duration(r.Count) * 24 * time.Hour
testDate = testDate.Add(dur)
}
return false
}
func (r *Recur) onMonths(date time.Time) bool {
if r.Start.After(date) {
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
}
tDate := r.Start
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
}
// 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
}
tDate := enm.Start
for {
if tDate.Equal(date) {
return true
@ -63,23 +314,11 @@ func (r *Recur) onMonths(date time.Time) bool {
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)
tDate = tDate.AddMonths(enm.N)
}
}
func (r *Recur) NextAfter(old time.Time) time.Time {
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)
func (enm EveryNMonths) String() string {
return fmt.Sprintf("%s, every %d months", enm.Start.String(), enm.N)
}

View File

@ -1,105 +1,417 @@
package item_test
import (
"testing"
"time"
// func TestDaily(t *testing.T) {
// daily := task.Daily{
// Start: task.NewDate(2021, 1, 31), // a sunday
// }
// dailyStr := "2021-01-31 (sunday), daily"
"go-mod.ewintr.nl/planner/item"
)
// t.Run("parse", func(t *testing.T) {
// test.Equals(t, daily, task.NewRecurrer(dailyStr))
// })
func TestRecur(t *testing.T) {
t.Parallel()
// t.Run("string", func(t *testing.T) {
// test.Equals(t, dailyStr, daily.String())
// })
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
// 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))
// })
// }
// })
// }
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)
}
})
}
})
// 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("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,
}
// t.Run("parse", func(t *testing.T) {
// test.Equals(t, every, task.NewRecurrer(everyStr))
// })
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)
}
})
}
})
// 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 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)))
// })
// }

3
plan/test-conf.yaml Normal file
View File

@ -0,0 +1,3 @@
db_path: ./plan.db
sync_url: http://localhost:8092
api_key: testKey

View File

@ -66,6 +66,7 @@ func (p *Postgres) Update(i item.Item, ts time.Time) error {
if err != nil {
return fmt.Errorf("%w: %v", ErrPostgresFailure, err)
}
i.RecurNext = i.Recurrer.Start
} else {
recurrerJSON = nil
}

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"log/slog"
"time"
@ -35,21 +36,26 @@ func (r *Recur) Run(interval time.Duration) {
}
func (r *Recur) Recur() error {
r.logger.Info("start looking for recurring items")
items, err := r.repoRecur.RecursBefore(time.Now())
if err != nil {
return err
}
r.logger.Info("found recurring items", "count", len(items))
for _, i := range items {
r.logger.Info("processing recurring item", "item", fmt.Sprintf("%+v", i))
// spawn instance
ne, err := item.NewEvent(i)
if err != nil {
return err
}
r.logger.Info("processing recurring event", "event", fmt.Sprintf("%+v", ne))
y, m, d := i.RecurNext.Date()
ne.ID = uuid.New().String()
ne.Recurrer = nil
ne.RecurNext = time.Time{}
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()
if err != nil {
@ -58,11 +64,14 @@ func (r *Recur) Recur() error {
if err := r.repoSync.Update(ni, time.Now()); err != nil {
return err
}
r.logger.Info("storen instance of recurring event", "recEventID", ne.ID, "instanceID", ni.ID)
// set next
if err := r.repoRecur.RecursNext(i.ID, i.Recurrer.NextAfter(i.RecurNext), time.Now()); err != nil {
next := i.Recurrer.NextAfter(i.RecurNext)
if err := r.repoRecur.RecursNext(i.ID, next, time.Now()); err != nil {
return err
}
r.logger.Info("updated recur date", "recEventID", ne.ID, "next", next)
}
r.logger.Info("processed recurring items", "count", len(items))

View File

@ -39,7 +39,7 @@ func main() {
"dbUser": *dbUser,
})
recurrer := NewRecur(repo, repo, logger)
go recurrer.Run(12 * time.Hour)
go recurrer.Run(10 * time.Second)
srv := NewServer(repo, *apiKey, logger)
go http.ListenAndServe(fmt.Sprintf(":%s", *apiPort), srv)