summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-28 09:59:06 +0200
committerPaul Buetow <paul@buetow.org>2026-02-28 12:50:12 +0200
commitdbf8980f1bc5428d1eb463505968469617dbf3fc (patch)
treec94c057bfe6d2d8ff3763a64a30cefd59d76cd87
parentd2cc2def17b58da762d88090c0c298dc2e87dd1d (diff)
refactor(task): centralize Taskwarrior date format as task.DateFormat
DRY/MEDIUM task (UUID 2c74960f): the format string "20060102T150405Z" was hardcoded in 5+ places across the task and ui packages. - Export task.DateFormat constant from internal/task/task.go - Replace hardcoded string in task.go and stats.go with DateFormat - Update internal/ui/helpers.go to alias the constant (taskDateFormat = task.DateFormat) rather than repeating the string literal - Replace all 6 raw string literals in table.go with task.DateFormat - Remove duplicate dueText() from table.go; its only call site now uses formatDueText() from helpers.go, which also normalises to midnight UTC for more accurate day-difference calculations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/task/stats.go2
-rw-r--r--internal/task/task.go7
-rw-r--r--internal/ui/helpers.go8
-rw-r--r--internal/ui/table.go32
4 files changed, 19 insertions, 30 deletions
diff --git a/internal/task/stats.go b/internal/task/stats.go
index c516249..b306687 100644
--- a/internal/task/stats.go
+++ b/internal/task/stats.go
@@ -28,7 +28,7 @@ func DueTasks(tasks []Task, now time.Time) int {
if t.Status == "completed" || t.Due == "" {
continue
}
- ts, err := time.Parse("20060102T150405Z", t.Due)
+ ts, err := time.Parse(DateFormat, t.Due)
if err != nil {
continue
}
diff --git a/internal/task/task.go b/internal/task/task.go
index 2c7f8e8..8eadd18 100644
--- a/internal/task/task.go
+++ b/internal/task/task.go
@@ -16,6 +16,11 @@ import (
"github.com/google/shlex"
)
+// DateFormat is the date format used by Taskwarrior in all date fields
+// (e.g. Entry, Due, Start). All date parsing and formatting in this
+// package uses this constant.
+const DateFormat = "20060102T150405Z"
+
// Task represents a taskwarrior task as returned by `task export`.
type Annotation struct {
Entry string `json:"entry"`
@@ -421,7 +426,7 @@ func SortTasks(tasks []Task) {
if s == "" {
return time.Time{}, false
}
- t, err := time.Parse("20060102T150405Z", s)
+ t, err := time.Parse(DateFormat, s)
if err != nil {
return time.Time{}, false
}
diff --git a/internal/ui/helpers.go b/internal/ui/helpers.go
index 71c21db..9846811 100644
--- a/internal/ui/helpers.go
+++ b/internal/ui/helpers.go
@@ -5,10 +5,14 @@ import (
"regexp"
"strings"
"time"
+
+ "codeberg.org/snonux/tasksamurai/internal/task"
)
-// Date format used by Taskwarrior
-const taskDateFormat = "20060102T150405Z"
+// taskDateFormat aliases task.DateFormat for use within this package.
+// It is kept as a package-level constant so internal helpers don't need
+// to qualify every parse/format call with the package name.
+const taskDateFormat = task.DateFormat
// parseTaskDate parses a date string in Taskwarrior format
func parseTaskDate(dateStr string) (time.Time, error) {
diff --git a/internal/ui/table.go b/internal/ui/table.go
index 2cc2b3d..97011ff 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -927,7 +927,7 @@ func (m Model) taskToRow(t task.Task) atable.Row {
}
age := ""
- if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil {
+ if ts, err := time.Parse(task.DateFormat, t.Entry); err == nil {
days := int(time.Since(ts).Hours() / 24)
age = fmt.Sprintf("%dd", days)
}
@@ -967,7 +967,7 @@ func (m Model) formatDue(s string, width int) string {
if s == "" {
return ""
}
- ts, err := time.Parse("20060102T150405Z", s)
+ ts, err := time.Parse(task.DateFormat, s)
if err != nil {
return s
}
@@ -1078,7 +1078,7 @@ func (m Model) taskToRowSearch(t task.Task, re *regexp.Regexp, styles atable.Sty
}
age := ""
- if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil {
+ if ts, err := time.Parse(task.DateFormat, t.Entry); err == nil {
days := int(time.Since(ts).Hours() / 24)
age = fmt.Sprintf("%dd", days)
}
@@ -1146,7 +1146,7 @@ func (m Model) expandedCellView() string {
case 1:
val = strconv.Itoa(t.ID)
case 2:
- if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil {
+ if ts, err := time.Parse(task.DateFormat, t.Entry); err == nil {
days := int(time.Since(ts).Hours() / 24)
val = fmt.Sprintf("%dd", days)
}
@@ -1223,26 +1223,6 @@ func (m *Model) updateTableHeight() {
m.tbl.SetHeight(h)
}
-func dueText(s string) string {
- if s == "" {
- return ""
- }
- ts, err := time.Parse("20060102T150405Z", s)
- if err != nil {
- return s
- }
- days := int(time.Until(ts).Hours() / 24)
- switch days {
- case 0:
- return "today"
- case 1:
- return "tomorrow"
- case -1:
- return "yesterday"
- default:
- return fmt.Sprintf("%dd", days)
- }
-}
func (m *Model) computeColumnWidths() {
maxID := 1
@@ -1258,7 +1238,7 @@ func (m *Model) computeColumnWidths() {
maxID = l
}
age := ""
- if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil {
+ if ts, err := time.Parse(task.DateFormat, t.Entry); err == nil {
age = fmt.Sprintf("%dd", int(time.Since(ts).Hours()/24))
}
if l := len(age); l > maxAge {
@@ -1268,7 +1248,7 @@ func (m *Model) computeColumnWidths() {
if l := len(urg); l > maxUrg {
maxUrg = l
}
- due := dueText(t.Due)
+ due := formatDueText(t.Due)
if l := len(due); l > maxDue {
maxDue = l
}