Skip to content

Commit 08cc19a

Browse files
IAMSamuelRoddaclaude
authored andcommitted
fix(repeat): correct RRULE next occurrence calculation
- Search from now (not baseDate) when due date is in the past to avoid returning past dates - Search from due date when it's in the future to get proper interval - Calculate timeDiff from baseDate when no due date exists - Check RepeatsFromCurrentDate before timeDiff to ensure it takes priority - Use nextOccurrence instead of now for RepeatsFromCurrentDate dates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 74deb01 commit 08cc19a

1 file changed

Lines changed: 24 additions & 11 deletions

File tree

pkg/models/tasks.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,8 +1504,17 @@ func setTaskDatesRRule(oldTask, newTask *Task) {
15041504
// Set the DTSTART on the rule so After() works correctly
15051505
rule.DTStart(baseDate)
15061506

1507-
// Get the next occurrence after the base date
1508-
nextOccurrence := rule.After(baseDate, false)
1507+
// Determine where to search for the next occurrence:
1508+
// - If due date is in the future, advance from there (to get the next interval)
1509+
// - Otherwise, get the next occurrence after now (to skip past dates)
1510+
var searchFrom time.Time
1511+
if !oldTask.DueDate.IsZero() && oldTask.DueDate.After(now) {
1512+
searchFrom = oldTask.DueDate
1513+
} else {
1514+
searchFrom = now
1515+
}
1516+
1517+
nextOccurrence := rule.After(searchFrom, false)
15091518
if nextOccurrence.IsZero() {
15101519
// No more occurrences according to the rule (e.g., COUNT limit reached)
15111520
// Don't reschedule, just mark as done
@@ -1516,6 +1525,9 @@ func setTaskDatesRRule(oldTask, newTask *Task) {
15161525
var timeDiff time.Duration
15171526
if !oldTask.DueDate.IsZero() {
15181527
timeDiff = nextOccurrence.Sub(oldTask.DueDate)
1528+
} else {
1529+
// No due date, calculate diff from the base date used for rule generation
1530+
timeDiff = nextOccurrence.Sub(baseDate)
15191531
}
15201532
// Always set the due date for repeating tasks - if there was no due date,
15211533
// the next occurrence becomes the new due date
@@ -1530,27 +1542,28 @@ func setTaskDatesRRule(oldTask, newTask *Task) {
15301542
}
15311543

15321544
// Update start/end dates preserving their relationship to due date
1545+
// When RepeatsFromCurrentDate is true, dates are set to the next occurrence (future date)
15331546
if !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
15341547
diff := oldTask.EndDate.Sub(oldTask.StartDate)
1535-
if timeDiff != 0 {
1548+
if oldTask.RepeatsFromCurrentDate {
1549+
newTask.StartDate = nextOccurrence
1550+
} else if timeDiff != 0 {
15361551
newTask.StartDate = oldTask.StartDate.Add(timeDiff)
1537-
} else if oldTask.RepeatsFromCurrentDate {
1538-
newTask.StartDate = now
15391552
}
15401553
newTask.EndDate = newTask.StartDate.Add(diff)
15411554
} else {
15421555
if !oldTask.StartDate.IsZero() {
1543-
if timeDiff != 0 {
1556+
if oldTask.RepeatsFromCurrentDate {
1557+
newTask.StartDate = nextOccurrence
1558+
} else if timeDiff != 0 {
15441559
newTask.StartDate = oldTask.StartDate.Add(timeDiff)
1545-
} else if oldTask.RepeatsFromCurrentDate {
1546-
newTask.StartDate = now
15471560
}
15481561
}
15491562
if !oldTask.EndDate.IsZero() {
1550-
if timeDiff != 0 {
1563+
if oldTask.RepeatsFromCurrentDate {
1564+
newTask.EndDate = nextOccurrence
1565+
} else if timeDiff != 0 {
15511566
newTask.EndDate = oldTask.EndDate.Add(timeDiff)
1552-
} else if oldTask.RepeatsFromCurrentDate {
1553-
newTask.EndDate = now
15541567
}
15551568
}
15561569
}

0 commit comments

Comments
 (0)