Add date and ticket filters plus entry list to the project time report.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 23:23:22 +01:00
co-authored by Cursor
parent 2061df63d4
commit 34ab3b9c5b
6 changed files with 368 additions and 8 deletions
+60
View File
@@ -3,6 +3,7 @@ package ticket
import (
"fmt"
"sort"
"strings"
"time"
)
@@ -155,3 +156,62 @@ func TotalDuration(entries []TimeEntry) time.Duration {
}
return sum
}
// TimeFilter selects which time entries belong in a report.
// From/To are inclusive bounds on the entry's work instant (Start, else End).
// Zero times mean no bound on that side.
type TimeFilter struct {
From time.Time
To time.Time
TicketID string
}
// FilterEntries returns entries matching f. The input slice is not modified.
func FilterEntries(entries []TimeEntry, f TimeFilter) []TimeEntry {
if f.From.IsZero() && f.To.IsZero() && f.TicketID == "" {
out := make([]TimeEntry, len(entries))
copy(out, entries)
return out
}
out := make([]TimeEntry, 0, len(entries))
for _, e := range entries {
if f.TicketID != "" && e.TicketID != f.TicketID {
continue
}
when := e.Start
if when.IsZero() {
when = e.End
}
if !f.From.IsZero() && (when.IsZero() || when.Before(f.From)) {
continue
}
if !f.To.IsZero() && (when.IsZero() || when.After(f.To)) {
continue
}
out = append(out, e)
}
return out
}
// ParseReportDay parses YYYY-MM-DD as local midnight. Empty returns zero time.
func ParseReportDay(s string) (time.Time, error) {
s = strings.TrimSpace(s)
if s == "" {
return time.Time{}, nil
}
t, err := time.ParseInLocation("2006-01-02", s, time.Local)
if err != nil {
return time.Time{}, fmt.Errorf("invalid date %q", s)
}
return t, nil
}
// EndOfReportDay returns the last instant of the calendar day of t in local time.
func EndOfReportDay(t time.Time) time.Time {
if t.IsZero() {
return t
}
local := t.In(time.Local)
y, m, d := local.Date()
return time.Date(y, m, d, 23, 59, 59, 999999999, time.Local)
}