diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-14 20:17:10 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-16 02:44:02 +0000 |
| commit | c99d52ccf5c52e9c75b6fcb661c8e74816b34924 (patch) | |
| tree | c14a8f89cc7c7d76147d12a998479432cde43782 /internal/models/recurrence_test.go | |
| parent | 0baf1134ea2d415aa55079ff98476afd0acf5811 (diff) | |
feat(tasks): add ComputeNextOccurrence for recurring task scheduling
Implement pure function to compute next occurrence of recurring tasks given
frequency, interval, and optional weekday constraints. Supports daily, weekly,
monthly, and yearly recurrence patterns. Weekly recurrence with weekday
constraints wraps to next applicable week when needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
Diffstat (limited to 'internal/models/recurrence_test.go')
| -rw-r--r-- | internal/models/recurrence_test.go | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/internal/models/recurrence_test.go b/internal/models/recurrence_test.go new file mode 100644 index 0000000..8d18996 --- /dev/null +++ b/internal/models/recurrence_test.go @@ -0,0 +1,51 @@ +package models + +import ( + "testing" + "time" +) + +func TestComputeNextOccurrence(t *testing.T) { + mustParse := func(t *testing.T, s string) time.Time { + t.Helper() + d, err := time.Parse("2006-01-02", s) + if err != nil { + t.Fatalf("bad fixture date %q: %v", s, err) + } + return d + } + + tests := []struct { + name string + due string + freq string + interval int + weekdays []int + want string + }{ + {"daily interval 1", "2026-07-13", "daily", 1, nil, "2026-07-14"}, + {"daily interval 3", "2026-07-13", "daily", 3, nil, "2026-07-16"}, + {"weekly no weekdays interval 1", "2026-07-13", "weekly", 1, nil, "2026-07-20"}, + {"weekly no weekdays interval 2", "2026-07-13", "weekly", 2, nil, "2026-07-27"}, + // 2026-07-13 is a Monday (weekday=1). + {"weekly with weekdays same week", "2026-07-13", "weekly", 1, []int{1, 3, 5}, "2026-07-15"}, + {"weekly with weekdays wraps to next week", "2026-07-17", "weekly", 1, []int{1, 3, 5}, "2026-07-20"}, // due=Fri(5), wraps to Mon + {"weekly with weekdays interval 2 wraps", "2026-07-13", "weekly", 2, []int{1}, "2026-07-27"}, // due=Mon, only Mon active, skip a week + {"monthly interval 1", "2026-06-13", "monthly", 1, nil, "2026-07-13"}, + {"monthly rollover", "2026-01-31", "monthly", 1, nil, "2026-03-03"}, + {"monthly rollover locks in on the drifted day", "2026-03-03", "monthly", 1, nil, "2026-04-03"}, + {"yearly interval 1", "2026-07-13", "yearly", 1, nil, "2027-07-13"}, + {"unknown freq returns due unchanged", "2026-07-13", "bogus", 1, nil, "2026-07-13"}, + {"interval below 1 is treated as 1", "2026-07-13", "daily", 0, nil, "2026-07-14"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := ComputeNextOccurrence(mustParse(t, tc.due), tc.freq, tc.interval, tc.weekdays) + want := mustParse(t, tc.want) + if !got.Equal(want) { + t.Errorf("ComputeNextOccurrence(%s, %s, %d, %v) = %s, want %s", tc.due, tc.freq, tc.interval, tc.weekdays, got.Format("2006-01-02"), tc.want) + } + }) + } +} |
