Compare commits

...

2 Commits

Author SHA1 Message Date
Erik Winter 7de4408926 time 2024-12-22 11:43:37 +01:00
Erik Winter 6972f08123 item 2024-12-22 10:12:47 +01:00
7 changed files with 296 additions and 165 deletions

View File

@ -66,7 +66,6 @@ func (d *Date) UnmarshalJSON(data []byte) error {
} }
func NewDate(year, month, day int) Date { func NewDate(year, month, day int) Date {
if year == 0 || month == 0 || month > 12 || day == 0 { if year == 0 || month == 0 || month > 12 || day == 0 {
return Date{} return Date{}
} }

View File

@ -1,111 +1,110 @@
package item package item
import ( import (
"encoding/json"
"fmt"
"time" "time"
) )
type EventBody struct { type EventBody struct {
Title string `json:"title"` Title string `json:"title"`
Start time.Time `json:"start"` Date Date `json:"date"`
Time Time `json:"time"`
Duration time.Duration `json:"duration"` Duration time.Duration `json:"duration"`
} }
func (e EventBody) MarshalJSON() ([]byte, error) { // func (e EventBody) MarshalJSON() ([]byte, error) {
type Alias EventBody // type Alias EventBody
return json.Marshal(&struct { // return json.Marshal(&struct {
Start string `json:"start"` // Start string `json:"start"`
Duration string `json:"duration"` // Duration string `json:"duration"`
*Alias // *Alias
}{ // }{
Start: e.Start.UTC().Format(time.RFC3339), // Start: e.Start.UTC().Format(time.RFC3339),
Duration: e.Duration.String(), // Duration: e.Duration.String(),
Alias: (*Alias)(&e), // Alias: (*Alias)(&e),
}) // })
} // }
func (e *EventBody) UnmarshalJSON(data []byte) error { // func (e *EventBody) UnmarshalJSON(data []byte) error {
type Alias EventBody // type Alias EventBody
aux := &struct { // aux := &struct {
Start string `json:"start"` // Start string `json:"start"`
Duration string `json:"duration"` // Duration string `json:"duration"`
*Alias // *Alias
}{ // }{
Alias: (*Alias)(e), // Alias: (*Alias)(e),
} // }
if err := json.Unmarshal(data, &aux); err != nil { // if err := json.Unmarshal(data, &aux); err != nil {
return err // return err
} // }
var err error // var err error
if e.Start, err = time.Parse(time.RFC3339, aux.Start); err != nil { // if e.Start, err = time.Parse(time.RFC3339, aux.Start); err != nil {
return err // return err
} // }
if e.Duration, err = time.ParseDuration(aux.Duration); err != nil { // if e.Duration, err = time.ParseDuration(aux.Duration); err != nil {
return err // return err
} // }
return nil // return nil
} // }
type Event struct { // type Event struct {
ID string `json:"id"` // ID string `json:"id"`
Recurrer Recurrer `json:"recurrer"` // Recurrer Recurrer `json:"recurrer"`
RecurNext time.Time `json:"recurNext"` // RecurNext time.Time `json:"recurNext"`
EventBody // EventBody
} // }
func NewEvent(i Item) (Event, error) { // func NewEvent(i Item) (Event, error) {
if i.Kind != KindEvent { // if i.Kind != KindEvent {
return Event{}, fmt.Errorf("item is not an event") // return Event{}, fmt.Errorf("item is not an event")
} // }
var e Event // var e Event
if err := json.Unmarshal([]byte(i.Body), &e); err != nil { // if err := json.Unmarshal([]byte(i.Body), &e); err != nil {
return Event{}, fmt.Errorf("could not unmarshal item body: %v", err) // return Event{}, fmt.Errorf("could not unmarshal item body: %v", err)
} // }
e.ID = i.ID // e.ID = i.ID
e.Recurrer = i.Recurrer // e.Recurrer = i.Recurrer
e.RecurNext = i.RecurNext // e.RecurNext = i.RecurNext
return e, nil // return e, nil
} // }
func (e Event) Item() (Item, error) { // func (e Event) Item() (Item, error) {
body, err := json.Marshal(EventBody{ // body, err := json.Marshal(EventBody{
Title: e.Title, // Title: e.Title,
Start: e.Start, // Start: e.Start,
Duration: e.Duration, // Duration: e.Duration,
}) // })
if err != nil { // if err != nil {
return Item{}, fmt.Errorf("could not marshal event to json") // return Item{}, fmt.Errorf("could not marshal event to json")
} // }
return Item{ // return Item{
ID: e.ID, // ID: e.ID,
Kind: KindEvent, // Kind: KindEvent,
Recurrer: e.Recurrer, // Recurrer: e.Recurrer,
RecurNext: e.RecurNext, // RecurNext: e.RecurNext,
Body: string(body), // Body: string(body),
}, nil // }, nil
} // }
func (e Event) Valid() bool { // func (e Event) Valid() bool {
if e.Title == "" { // if e.Title == "" {
return false // return false
} // }
if e.Start.IsZero() || e.Start.Year() < 2024 { // if e.Start.IsZero() || e.Start.Year() < 2024 {
return false // return false
} // }
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
} // }

View File

@ -23,7 +23,7 @@ type Item struct {
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
Deleted bool `json:"deleted"` Deleted bool `json:"deleted"`
Recurrer Recurrer `json:"recurrer"` Recurrer Recurrer `json:"recurrer"`
RecurNext time.Time `json:"recurNext"` RecurNext Date `json:"recurNext"`
Body string `json:"body"` Body string `json:"body"`
} }

70
item/time.go Normal file
View File

@ -0,0 +1,70 @@
package item
import (
"encoding/json"
"time"
)
const (
TimeFormat = "15:04"
)
type Time struct {
t time.Time
}
func (t Time) MarshalJSON() ([]byte, error) {
return json.Marshal(t.String())
}
func (t *Time) UnmarshalJSON(data []byte) error {
timeString := ""
if err := json.Unmarshal(data, &timeString); err != nil {
return err
}
nt := NewTimeFromString(timeString)
t.t = nt.Time()
return nil
}
func NewTime(hour, minute int) Time {
return Time{
t: time.Date(0, 0, 0, hour, minute, 0, 0, time.UTC),
}
}
func NewTimeFromString(timeStr string) Time {
tm, err := time.Parse(TimeFormat, timeStr)
if err != nil {
return Time{t: time.Time{}}
}
return Time{t: tm}
}
func (t *Time) String() string {
return t.t.Format(TimeFormat)
}
func (t *Time) Time() time.Time {
return t.t
}
func (t *Time) IsZero() bool {
return t.t.IsZero()
}
func (t *Time) Hour() int {
return t.t.Hour()
}
func (t *Time) Minute() int {
return t.t.Minute()
}
func (t *Time) Add(d time.Duration) Time {
return Time{
t: t.t.Add(d),
}
}

67
item/time_test.go Normal file
View File

@ -0,0 +1,67 @@
package item_test
import (
"encoding/json"
"fmt"
"testing"
"go-mod.ewintr.nl/planner/item"
)
func TestTime(t *testing.T) {
t.Parallel()
h, m := 11, 18
tm := item.NewTime(h, m)
expStr := "11:18"
if expStr != tm.String() {
t.Errorf("exp %v, got %v", expStr, tm.String())
}
actJSON, err := json.Marshal(tm)
if err != nil {
t.Errorf("exp nil, got %v", err)
}
expJSON := fmt.Sprintf("%q", expStr)
if expJSON != string(actJSON) {
t.Errorf("exp %v, got %v", expJSON, string(actJSON))
}
var actTM item.Time
if err := json.Unmarshal(actJSON, &actTM); err != nil {
t.Errorf("exp nil, got %v", err)
}
if expStr != actTM.String() {
t.Errorf("ecp %v, got %v", expStr, actTM.String())
}
}
func TestTimeFromString(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
str string
exp string
}{
{
name: "empty",
exp: "00:00",
},
{
name: "invalid",
str: "invalid",
exp: "00:00",
},
{
name: "valid",
str: "11:42",
exp: "11:42",
},
} {
t.Run(tc.name, func(t *testing.T) {
act := item.NewTimeFromString(tc.str)
if tc.exp != act.String() {
t.Errorf("exp %v, got %v", tc.exp, act.String())
}
})
}
}

View File

@ -2,70 +2,66 @@ package storage
import ( import (
"errors" "errors"
"sort"
"time"
"go-mod.ewintr.nl/planner/item"
) )
var ( var (
ErrNotFound = errors.New("not found") ErrNotFound = errors.New("not found")
) )
type LocalID interface { // type LocalID interface {
FindAll() (map[string]int, error) // FindAll() (map[string]int, error)
FindOrNext(id string) (int, error) // FindOrNext(id string) (int, error)
Next() (int, error) // Next() (int, error)
Store(id string, localID int) error // Store(id string, localID int) error
Delete(id string) error // Delete(id string) error
} // }
type Sync interface { // type Sync interface {
FindAll() ([]item.Item, error) // FindAll() ([]item.Item, error)
Store(i item.Item) error // Store(i item.Item) error
DeleteAll() error // DeleteAll() error
LastUpdate() (time.Time, error) // LastUpdate() (time.Time, error)
} // }
type Event interface { // type Event interface {
Store(event item.Event) error // Store(event item.Event) error
Find(id string) (item.Event, error) // Find(id string) (item.Event, error)
FindAll() ([]item.Event, error) // FindAll() ([]item.Event, error)
Delete(id string) error // Delete(id string) error
} // }
func NextLocalID(used []int) int { // func NextLocalID(used []int) int {
if len(used) == 0 { // if len(used) == 0 {
return 1 // return 1
} // }
sort.Ints(used) // sort.Ints(used)
usedMax := 1 // usedMax := 1
for _, u := range used { // for _, u := range used {
if u > usedMax { // if u > usedMax {
usedMax = u // usedMax = u
} // }
} // }
var limit int // var limit int
for limit = 1; limit <= len(used) || limit < usedMax; limit *= 10 { // for limit = 1; limit <= len(used) || limit < usedMax; limit *= 10 {
} // }
newId := used[len(used)-1] + 1 // newId := used[len(used)-1] + 1
if newId < limit { // if newId < limit {
return newId // return newId
} // }
usedMap := map[int]bool{} // usedMap := map[int]bool{}
for _, u := range used { // for _, u := range used {
usedMap[u] = true // usedMap[u] = true
} // }
for i := 1; i < limit; i++ { // for i := 1; i < limit; i++ {
if _, ok := usedMap[i]; !ok { // if _, ok := usedMap[i]; !ok {
return i // return i
} // }
} // }
return limit // return limit
} // }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"slices" "slices"
"sync" "sync"
"time" "time"
@ -48,32 +47,33 @@ func (m *Memory) Updated(kinds []item.Kind, timestamp time.Time) ([]item.Item, e
} }
func (m *Memory) RecursBefore(date time.Time) ([]item.Item, error) { func (m *Memory) RecursBefore(date time.Time) ([]item.Item, error) {
res := make([]item.Item, 0) // res := make([]item.Item, 0)
for _, i := range m.items { // for _, i := range m.items {
if i.Recurrer == nil { // if i.Recurrer == nil {
continue // continue
} // }
if i.RecurNext.Before(date) { // if i.RecurNext.Before(date) {
res = append(res, i) // res = append(res, i)
} // }
} // }
return res, nil // return res, nil
return nil, nil
} }
func (m *Memory) RecursNext(id string, date time.Time, ts time.Time) error { func (m *Memory) RecursNext(id string, date time.Time, ts time.Time) error {
i, ok := m.items[id] // i, ok := m.items[id]
if !ok { // if !ok {
return ErrNotFound // return ErrNotFound
} // }
if i.Recurrer == nil { // if i.Recurrer == nil {
return ErrNotARecurrer // return ErrNotARecurrer
} // }
if !i.Recurrer.On(date) { // if !i.Recurrer.On(date) {
return fmt.Errorf("item does not recur on %v", date) // return fmt.Errorf("item does not recur on %v", date)
} // }
i.RecurNext = date // i.RecurNext = date
i.Updated = ts // i.Updated = ts
m.items[id] = i // m.items[id] = i
return nil return nil
} }