diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-08 22:05:11 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-08 22:05:11 +0300 |
| commit | 595075473a51b2709f1554f055df43a69369cddd (patch) | |
| tree | 9a0fb966535af4aa23c177991d92b8f6c36e1eed /internal/task/sort.go | |
| parent | b6e10387a6a22f96e91542d487014b40e42c1dd0 (diff) | |
Task 9: extract SortTasks helpers
Diffstat (limited to 'internal/task/sort.go')
| -rw-r--r-- | internal/task/sort.go | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/internal/task/sort.go b/internal/task/sort.go new file mode 100644 index 0000000..7cd953b --- /dev/null +++ b/internal/task/sort.go @@ -0,0 +1,99 @@ +package task + +import ( + "sort" + "strings" + "time" +) + +// SortTasks orders tasks by start status, priority, due date, tag names and id. +// Started tasks are always placed before non-started ones. Tasks without a due +// date are placed after tasks with a due date. Overdue tasks are placed at the +// very top regardless of other properties. +// +// The sort order is: +// 1. Overdue tasks (oldest due date first) +// 2. Started tasks (not completed) +// 3. High priority tasks +// 4. Tasks with earlier due dates +// 5. Tasks sorted alphabetically by tags +// 6. Tasks sorted by ID (oldest first) +func SortTasks(tasks []Task) { + now := time.Now() + + sort.Slice(tasks, func(i, j int) bool { + ti, tj := tasks[i], tasks[j] + + if oi, oj := isOverdue(ti, now), isOverdue(tj, now); oi != oj { + return oi + } + + startedI := ti.Start != "" && ti.Status != "completed" + startedJ := tj.Start != "" && tj.Status != "completed" + if startedI != startedJ { + return startedI + } + + pi, pj := priorityRank(ti.Priority), priorityRank(tj.Priority) + if pi != pj { + return pi > pj + } + + di, iok := parseDueDate(ti.Due) + dj, jok := parseDueDate(tj.Due) + if iok && !jok { + return true + } + if !iok && jok { + return false + } + if iok && jok && !di.Equal(dj) { + return di.Before(dj) + } + + tgI, tgJ := joinTags(ti.Tags), joinTags(tj.Tags) + if tgI != tgJ { + return tgI < tgJ + } + + return ti.ID < tj.ID + }) +} + +func joinTags(tags []string) string { + if len(tags) == 0 { + return "" + } + cpy := append([]string(nil), tags...) + sort.Strings(cpy) + return strings.Join(cpy, " ") +} + +func priorityRank(priority string) int { + switch priority { + case "H": + return 3 + case "M": + return 2 + case "L": + return 1 + default: + return 0 + } +} + +func parseDueDate(value string) (time.Time, bool) { + if value == "" { + return time.Time{}, false + } + parsed, err := time.Parse(DateFormat, value) + if err != nil { + return time.Time{}, false + } + return parsed, true +} + +func isOverdue(task Task, now time.Time) bool { + due, ok := parseDueDate(task.Due) + return ok && now.After(due) +} |
