tasks: count overdue by calendar day like the ranker does (V-581)
Stalls compared the due instant to now while Rank compares whole calendar days, so a task due at 09:00 was counted overdue from 09:01 while its own row on the same page still read the reason as today. Stalls now reads dayDelta. A row with no capture time also counted as sitting, because the zero time is January of year 1 and every span from it clears ten days. Rank already guarded that and Stalls did not. Folded the open-versus-candidate partition FormatRU and Spoken each carried into one split helper. The two have to agree on where that line falls.
This commit is contained in:
+16
-16
@@ -208,14 +208,7 @@ const SpokenLimit = 5
|
|||||||
// DayPlan.Spoken is built core-side: two formatters drift, and then she says
|
// DayPlan.Spoken is built core-side: two formatters drift, and then she says
|
||||||
// one order and shows another.
|
// one order and shows another.
|
||||||
func FormatRU(ranked []Ranked) string {
|
func FormatRU(ranked []Ranked) string {
|
||||||
var open, cands []Ranked
|
open, cands := split(ranked)
|
||||||
for _, r := range ranked {
|
|
||||||
if r.Status == StatusCandidate {
|
|
||||||
cands = append(cands, r)
|
|
||||||
} else {
|
|
||||||
open = append(open, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(open) == 0 && len(cands) == 0 {
|
if len(open) == 0 && len(cands) == 0 {
|
||||||
return say.S(say.TasksNone, nil)
|
return say.S(say.TasksNone, nil)
|
||||||
}
|
}
|
||||||
@@ -243,6 +236,20 @@ func FormatRU(ranked []Ranked) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// split separates confirmed work from candidates, preserving Rank's order
|
||||||
|
// within each half. FormatRU and Spoken have to agree on where the line falls,
|
||||||
|
// so they read it from one place.
|
||||||
|
func split(ranked []Ranked) (open, cands []Ranked) {
|
||||||
|
for _, r := range ranked {
|
||||||
|
if r.Status == StatusCandidate {
|
||||||
|
cands = append(cands, r)
|
||||||
|
} else {
|
||||||
|
open = append(open, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return open, cands
|
||||||
|
}
|
||||||
|
|
||||||
// joinRU lists up to limit tasks, then says how many are left. withReasons
|
// joinRU lists up to limit tasks, then says how many are left. withReasons
|
||||||
// attaches the parenthesised reason — candidates are listed bare, since their
|
// attaches the parenthesised reason — candidates are listed bare, since their
|
||||||
// due dates are Maven's reading of a mail and not something he stated.
|
// due dates are Maven's reading of a mail and not something he stated.
|
||||||
@@ -273,14 +280,7 @@ func joinRU(rs []Ranked, limit int, withReasons bool) string {
|
|||||||
// an ordinal resolves against is built here and not by a caller guessing how
|
// an ordinal resolves against is built here and not by a caller guessing how
|
||||||
// the renderer split and truncated it.
|
// the renderer split and truncated it.
|
||||||
func Spoken(ranked []Ranked) []Ranked {
|
func Spoken(ranked []Ranked) []Ranked {
|
||||||
var open, cands []Ranked
|
open, cands := split(ranked)
|
||||||
for _, r := range ranked {
|
|
||||||
if r.Status == StatusCandidate {
|
|
||||||
cands = append(cands, r)
|
|
||||||
} else {
|
|
||||||
open = append(open, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out := make([]Ranked, 0, 2*SpokenLimit)
|
out := make([]Ranked, 0, 2*SpokenLimit)
|
||||||
for _, group := range [][]Ranked{open, cands} {
|
for _, group := range [][]Ranked{open, cands} {
|
||||||
if len(group) > SpokenLimit {
|
if len(group) > SpokenLimit {
|
||||||
|
|||||||
@@ -56,10 +56,16 @@ func Stalls(items []Item, now time.Time) []Stall {
|
|||||||
// meant to act on.
|
// meant to act on.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if it.Due != nil && it.Due.Before(now) {
|
// Whole calendar days, the reading Rank already uses. An instant
|
||||||
|
// comparison calls a task due at 18:00 overdue from 18:01, so the count
|
||||||
|
// above the table would say "просрочено" beside a row whose own reason
|
||||||
|
// still said "сегодня".
|
||||||
|
if it.Due != nil && dayDelta(*it.Due, now) < 0 {
|
||||||
overdue++
|
overdue++
|
||||||
}
|
}
|
||||||
if now.Sub(it.Created) >= StallDays*24*time.Hour {
|
// A row with no capture time has not sat for anything. Without the
|
||||||
|
// guard its zero time is January of year 1 and it always counts.
|
||||||
|
if !it.Created.IsZero() && now.Sub(it.Created) >= StallDays*24*time.Hour {
|
||||||
sitting++
|
sitting++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,27 @@ func TestStallsSaysNothingWhenThereIsNothing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStallsCountsOverdueByCalendarDay(t *testing.T) {
|
||||||
|
// Same reading as Rank, or the count above the table contradicts the reason
|
||||||
|
// in the row: due at 09:00, asked at 12:00, and it is still due today.
|
||||||
|
now := stallNow()
|
||||||
|
earlier := now.Add(-3 * time.Hour)
|
||||||
|
items := []Item{{ID: 1, Text: "оплатить интернет", Status: StatusOpen, Created: now.Add(-time.Hour), Due: &earlier}}
|
||||||
|
if got := Stalls(items, now); len(got) != 0 {
|
||||||
|
t.Fatalf("shapes = %+v, want none — a task due today is not overdue", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStallsIgnoresATaskWithNoCaptureTime(t *testing.T) {
|
||||||
|
// The zero time is January of year 1, so an unstamped row would count as
|
||||||
|
// sitting forever.
|
||||||
|
now := stallNow()
|
||||||
|
items := []Item{{ID: 1, Text: "купить молоко", Status: StatusOpen}}
|
||||||
|
if got := Stalls(items, now); len(got) != 0 {
|
||||||
|
t.Fatalf("shapes = %+v, want none", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStallsCountsNoJudgement(t *testing.T) {
|
func TestStallsCountsNoJudgement(t *testing.T) {
|
||||||
// The line this shape may not cross. Every sentence states a count; none of
|
// The line this shape may not cross. Every sentence states a count; none of
|
||||||
// them says whether the work matters or should be dropped.
|
// them says whether the work matters or should be dropped.
|
||||||
|
|||||||
Reference in New Issue
Block a user