Compare commits
2 Commits
7360744e34
...
7de4408926
Author | SHA1 | Date |
---|---|---|
Erik Winter | 7de4408926 | |
Erik Winter | 6972f08123 |
|
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
173
item/event.go
173
item/event.go
|
@ -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
|
||||||
}
|
// }
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
// }
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue