Skip to content

Commit e7c98d3

Browse files
committed
force until to be date only, use simpler errors.New()
1 parent 70ad610 commit e7c98d3

4 files changed

Lines changed: 59 additions & 33 deletions

File tree

e2e/events_repeating_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ func TestRepeatingEvent_Update_Following_SplitsSeriesFromTargetChild(t *testing.
325325
if oldParent.Repeat == nil {
326326
t.Fatalf("old parent should still repeat before the split")
327327
}
328-
if !oldParent.Repeat.Until.Equal(previous.From) {
329-
t.Fatalf("old parent Until mismatch: expected %s, got %s", previous.From, oldParent.Repeat.Until)
328+
if !oldParent.Repeat.Until.Equal(dateOnly(previous.From)) {
329+
t.Fatalf("old parent Until mismatch: expected %s, got %s", dateOnly(previous.From), oldParent.Repeat.Until)
330330
}
331331
if oldParent.Repeat.Count != 0 {
332332
t.Fatalf("old parent Count should be reset to 0, got %d", oldParent.Repeat.Count)
@@ -938,3 +938,11 @@ func eventStarts(events []core.Event) []time.Time {
938938

939939
return starts
940940
}
941+
942+
func dateOnly(t time.Time) time.Time {
943+
return time.Date(
944+
t.Year(), t.Month(), t.Day(),
945+
0, 0, 0, 0,
946+
t.Location(),
947+
)
948+
}

pkg/core/core_events.go

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ import (
1515
// Creates a new event and save it into git.
1616
func (c *Core) CreateEvent(event Event) (*Event, error) {
1717
if _, ok := c.events[event.Id]; ok && event.Id != uuid.Nil {
18-
return nil, fmt.Errorf("an event with this id already exists")
18+
return nil, errors.New("an event with this id already exists")
1919
}
2020

2121
cal, ok := c.calendars[event.Calendar]
2222
if !ok {
23-
return nil, fmt.Errorf("the specified calendar is missing")
23+
return nil, errors.New("the specified calendar is missing")
2424
}
2525
if cal.Readonly {
26-
return nil, fmt.Errorf("the specified calendar is read-only")
26+
return nil, errors.New("the specified calendar is read-only")
2727
}
2828

2929
if err := event.Validate(); err != nil {
@@ -57,10 +57,10 @@ func (c *Core) UpdateEvent(event Event) (*Event, error) {
5757

5858
cal, ok := c.calendars[event.Calendar]
5959
if !ok {
60-
return nil, fmt.Errorf("the specified calendar is missing")
60+
return nil, errors.New("the specified calendar is missing")
6161
}
6262
if cal.Readonly {
63-
return nil, fmt.Errorf("the specified calendar is read-only")
63+
return nil, errors.New("the specified calendar is read-only")
6464
}
6565

6666
oldEnd := originalEvent.getTreeEndTime()
@@ -110,24 +110,24 @@ func (c *Core) UpdateRepeatingEvent(old, new Event, strat UpdateStrategy) (*Even
110110
return nil, fmt.Errorf("invalid new event: %w", err)
111111
}
112112
if !strat.IsValid() {
113-
return nil, fmt.Errorf("incorrect strategy provided")
113+
return nil, errors.New("incorrect strategy provided")
114114
}
115115
if old.Id != new.Id { // check if the event we are changing is the original Parent
116116
return nil, fmt.Errorf("invalid update event: id %q does not match parent id %q", old.Id, new.Id)
117117
}
118118
if !old.IsChild() || !new.IsChild() {
119-
return nil, fmt.Errorf("repeating update requires child events")
119+
return nil, errors.New("repeating update requires child events")
120120
}
121-
if old.ParentId != new.ParentId {
122-
return nil, fmt.Errorf("old and new child events have different parent ids")
121+
if *old.ParentId != *new.ParentId {
122+
return nil, errors.New("old and new child events have different parent ids")
123123
}
124124

125125
cal, ok := c.calendars[new.Calendar]
126126
if !ok {
127-
return nil, fmt.Errorf("the specified calendar is missing")
127+
return nil, errors.New("the specified calendar is missing")
128128
}
129129
if cal.Readonly {
130-
return nil, fmt.Errorf("the specified calendar is read-only")
130+
return nil, errors.New("the specified calendar is read-only")
131131
}
132132

133133
switch strat {
@@ -149,12 +149,7 @@ func (c *Core) RemoveEvent(event Event) error {
149149
}
150150

151151
if cal, ok := c.calendars[event.Calendar]; ok && cal.Readonly {
152-
return fmt.Errorf("the events calendar is read-only")
153-
}
154-
155-
err := c.intervalTree.RemoveEvent(event)
156-
if err != nil {
157-
return fmt.Errorf("failed to delete event from interval tree: %w", err)
152+
return errors.New("the events calendar is read-only")
158153
}
159154

160155
// delete file from disk + git
@@ -174,7 +169,7 @@ func (c *Core) RemoveRepeatingEvent(event Event, strat UpdateStrategy) error {
174169
}
175170

176171
if cal, ok := c.calendars[event.Calendar]; ok && cal.Readonly {
177-
return fmt.Errorf("the events calendar is read-only")
172+
return errors.New("the events calendar is read-only")
178173
}
179174

180175
if !event.IsChild() {
@@ -240,7 +235,7 @@ func (c *Core) GetEvents(from, to time.Time, filter GetEventsFilter) []Event {
240235

241236
for firstStart.Before(to) { // while child event fits in the wanted interval
242237
// logic when repeating until
243-
if curEvent.Repeat.Count == 0 && firstStart.After(curEvent.Repeat.Until) {
238+
if curEvent.Repeat.Count == 0 && dateAfter(firstStart, curEvent.Repeat.Until) {
244239
break // new event exceeded the repetition end (Until)
245240
}
246241
// logic for repeating only N times (count)
@@ -280,10 +275,10 @@ func (c *Core) GetEvents(from, to time.Time, filter GetEventsFilter) []Event {
280275
func (c *Core) updateCurrentChild(updated *Event) (*Event, error) {
281276
parent, ok := c.events[*updated.ParentId] // we check nil pointer in UpdateRepeatingEvent
282277
if !ok || parent == nil || !parent.IsParent() {
283-
return nil, fmt.Errorf("no valid parent found")
278+
return nil, errors.New("no valid parent found")
284279
}
285280
if parent.Repeat == nil {
286-
return nil, fmt.Errorf("parent is not a repeating event, WTF")
281+
return nil, errors.New("parent is not a repeating event, WTF")
287282
}
288283

289284
// update parent event with the new exception
@@ -305,10 +300,10 @@ func (c *Core) updateCurrentChild(updated *Event) (*Event, error) {
305300
func (c *Core) updateFollowingChildren(old, new *Event) (*Event, error) {
306301
parent, ok := c.events[*old.ParentId] // we check nil pointer in UpdateRepeatingEvent
307302
if !ok || parent == nil || !parent.IsParent() {
308-
return nil, fmt.Errorf("no valid parent found")
303+
return nil, errors.New("no valid parent found")
309304
}
310305
if parent.Repeat == nil {
311-
return nil, fmt.Errorf("parent is not a repeating event")
306+
return nil, errors.New("parent is not a repeating event")
312307
}
313308

314309
// keep originals for rollback
@@ -344,7 +339,7 @@ func (c *Core) updateFollowingChildren(old, new *Event) (*Event, error) {
344339
} else {
345340
// keep repeating but stop before the split point
346341
capped := originalRepeat
347-
capped.Until = previousStart
342+
capped.Until = dateOnly(previousStart)
348343
capped.Count = 0
349344
capped.Exceptions = exBefore
350345
parent.Repeat = &capped
@@ -412,7 +407,7 @@ func (c *Core) updateFollowingChildren(old, new *Event) (*Event, error) {
412407
func (c *Core) updateAllChildren(old, new *Event) (*Event, error) {
413408
parent, ok := c.events[*old.ParentId] // we check nil pointer in UpdateRepeatingEvent
414409
if !ok || parent == nil || !parent.IsParent() {
415-
return nil, fmt.Errorf("no valid parent found")
410+
return nil, errors.New("no valid parent found")
416411
}
417412

418413
fromDiff := new.From.Sub(old.From)
@@ -476,7 +471,7 @@ func (c *Core) updateAllChildren(old, new *Event) (*Event, error) {
476471
func (c *Core) removeCurrentChild(event *Event) error {
477472
parent, ok := c.events[*event.ParentId] // we check nil pointer in RemoveRepeatingEvent
478473
if !ok || parent == nil || !parent.IsParent() {
479-
return fmt.Errorf("no valid parent found")
474+
return errors.New("no valid parent found")
480475
}
481476

482477
// if exception doesn't exist yet
@@ -564,7 +559,7 @@ func (c *Core) saveAndCommitEvent(event *Event, commitMsg string) error {
564559
return fmt.Errorf("calendar not found: %s", event.Calendar)
565560
}
566561
if cal.repository == nil {
567-
return fmt.Errorf("calendar repo not initialized")
562+
return errors.New("calendar repo not initialized")
568563
}
569564

570565
wt, err := cal.repository.Worktree()
@@ -627,7 +622,7 @@ func (c *Core) deleteAndCommitEvent(eventId uuid.UUID, commitMsg string) error {
627622
return fmt.Errorf("calendar not found: %s", event.Calendar)
628623
}
629624
if cal.repository == nil {
630-
return fmt.Errorf("calendar repo not initialized")
625+
return errors.New("calendar repo not initialized")
631626
}
632627

633628
wt, err := cal.repository.Worktree()

pkg/core/event.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Event struct {
3535
type Repetition struct {
3636
Frequency Freq `json:"frequency"` // The unit of time for recurrence (Day, Week, Month, etc.).
3737
Interval int `json:"interval"` // The multiplier for Frequency (e.g., Interval:2 * Frequency:Week = every other week).
38-
Until time.Time `json:"until"` // Hard stop date for the series. (Inclusive: occurrences starting BEFORE or even ON this time are included.)
38+
Until time.Time `json:"until"` // Hard stop date for the series. It should just be a date, with time zeroed out (2026-01-01T00:00:00Z). If not, time should be ignored. It is inclusive.
3939
Count int `json:"count"` // Total number of occurrences to generate.
4040
Exceptions []uuid.UUID `json:"exceptions"` // List of Child IDs that deviate from the base rule (edited or cancelled).
4141
}
@@ -83,6 +83,9 @@ func (r *Repetition) Validate() error {
8383
if !r.Until.IsZero() && r.Count > 0 {
8484
return errors.New("Count must be 0 when Until date is set")
8585
}
86+
if !r.Until.IsZero() {
87+
r.Until = dateOnly(r.Until) // normalize to zeroed time
88+
}
8689

8790
return nil
8891
}
@@ -107,8 +110,12 @@ func (e Event) getTreeEndTime() time.Time {
107110

108111
eventEnd := e.To
109112
if e.Repeat != nil {
110-
eventEnd = e.Repeat.Until // if repeating, use interval [From, Repetition.Until]
111-
if e.Repeat.Count >= 1 { // if repeating on count basis
113+
eventEnd = e.Repeat.Until // if repeating, use interval [From, Repetition.Until T 23:59:59]
114+
if !eventEnd.IsZero() {
115+
// bump to end of that calendar day so the tree key covers the full last occurrence, not just midnight
116+
eventEnd = endOfDay(eventEnd)
117+
}
118+
if e.Repeat.Count >= 1 { // if repeating on count basis
112119
eventEnd = addUnit(e.To, e.Repeat.Interval*e.Repeat.Count, e.Repeat.Frequency)
113120
}
114121
}

pkg/core/utils.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,19 @@ func checkFilter(e *Event, f GetEventsFilter) bool {
259259
return u == *e.TagId
260260
})
261261
}
262+
263+
func dateOnly(t time.Time) time.Time {
264+
return time.Date(
265+
t.Year(), t.Month(), t.Day(),
266+
0, 0, 0, 0,
267+
t.Location(),
268+
)
269+
}
270+
271+
func dateAfter(a, b time.Time) bool {
272+
return dateOnly(a).After(dateOnly(b))
273+
}
274+
275+
func endOfDay(t time.Time) time.Time {
276+
return dateOnly(t).Add(24*time.Hour - time.Nanosecond)
277+
}

0 commit comments

Comments
 (0)