-
Notifications
You must be signed in to change notification settings - Fork 1
/
aggregators.go
147 lines (126 loc) · 3.93 KB
/
aggregators.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Package aggregators contains aggregators of fin.Entry and fin.CatPayment
// values. Each aggregator has an Include method and can be easily converted
// to a consumer via the appropriate method in the consumers package.
package aggregators
import (
"github.com/keep94/finances/fin"
"github.com/keep94/toolbox/date_util"
"github.com/keep94/toolbox/str_util"
"time"
)
// Totaler sums up the total in each fin.CatPayment instance.
type Totaler struct {
// Total is the total so far
Total int64
}
func (t *Totaler) Include(cp fin.CatPayment) {
t.Total += cp.Total()
}
// AutoCompleteAggregator makes list of auto complete candidates.
type AutoCompleteAggregator struct {
str_util.AutoComplete
// The field on which to find auto complete candidates
Field func(e fin.Entry) string
}
func (a *AutoCompleteAggregator) Include(e fin.Entry) {
a.Add(a.Field(e))
}
// Recurring is the interface for recurring time periods. e.g monthly,
// yearly.
type Recurring interface {
// Normalize returns the beginning of a time period for a given time
Normalize(t time.Time) time.Time
// Add returns the result of adding numPeriods time periods to a start time
Add(t time.Time, numPeriods int) time.Time
}
func Monthly() Recurring {
return monthly{}
}
func Yearly() Recurring {
return yearly{}
}
// PeriodTotal contains the total of all transactions for a given period.
type PeriodTotal struct {
// The start of the period
PeriodStart time.Time
// The actual start inclusive. May differ from PeriodStart if this record
// covers a partial period
Start time.Time
// The end exclusive. May differ from start of next period if this record
// covers a partial period.
End time.Time
// The total for the period.
Total int64
}
// ByPeriodTotaler sums totals by period
type ByPeriodTotaler struct {
start time.Time
end time.Time
recurring Recurring
totals map[time.Time]int64
}
// NewByPeriodTotaler creates a new ByPeriodTotaler that collects statistics
// for transactions happening between start inclusive and end exclusive.
// The recurring perameter indicates the recurring period such as monthly or
// yearly.
func NewByPeriodTotaler(start, end time.Time, recurring Recurring) *ByPeriodTotaler {
return &ByPeriodTotaler{
date_util.TimeToDate(start),
date_util.TimeToDate(end),
recurring,
make(map[time.Time]int64)}
}
func (b *ByPeriodTotaler) Include(entry fin.Entry) {
if entry.Date.Before(b.start) || !b.end.After(entry.Date) {
return
}
b.totals[b.recurring.Normalize(entry.Date)] += entry.Total()
}
// Iterator is used to get the totals by period.
func (b *ByPeriodTotaler) Iterator() *PeriodTotalIterator {
return &PeriodTotalIterator{b, b.recurring.Normalize(b.start), 0}
}
// PeriodTotalIterator iterates over period totals.
type PeriodTotalIterator struct {
totaler *ByPeriodTotaler
firstPeriod time.Time
idx int
}
// Next stores the next period total at p and returns true. If there
// is no next period total, Next returns false.
func (pti *PeriodTotalIterator) Next(p *PeriodTotal) bool {
periodStart := pti.totaler.recurring.Add(pti.firstPeriod, pti.idx)
total := pti.totaler.totals[periodStart]
start := periodStart
if start.Before(pti.totaler.start) {
start = pti.totaler.start
}
end := pti.totaler.recurring.Add(pti.firstPeriod, pti.idx+1)
if end.After(pti.totaler.end) {
end = pti.totaler.end
}
if end.After(start) {
pti.idx++
*p = PeriodTotal{
PeriodStart: periodStart,
Start: start,
End: end,
Total: total}
return true
}
return false
}
type monthly struct{}
func (m monthly) Normalize(t time.Time) time.Time {
return date_util.YMD(t.Year(), int(t.Month()), 1)
}
func (m monthly) Add(t time.Time, numPeriods int) time.Time {
return t.AddDate(0, numPeriods, 0)
}
type yearly struct{}
func (y yearly) Normalize(t time.Time) time.Time {
return date_util.YMD(t.Year(), 1, 1)
}
func (y yearly) Add(t time.Time, numPeriods int) time.Time {
return t.AddDate(numPeriods, 0, 0)
}