This repository has been archived by the owner on May 16, 2020. It is now read-only.
forked from howeyc/ledger
-
Notifications
You must be signed in to change notification settings - Fork 0
/
date.go
162 lines (132 loc) · 4.91 KB
/
date.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package ledger
import "time"
// TransactionsInDateRange returns a new array of transactions that are in the date range
// specified by start and end. The returned list contains transactions on the same day as start
// but does not include any transactions on the day of end.
func TransactionsInDateRange(trans []*Transaction, start, end time.Time) []*Transaction {
var newlist []*Transaction
start = start.Add(-1 * time.Second)
for _, tran := range trans {
if tran.Date.After(start) && tran.Date.Before((end)) {
newlist = append(newlist, tran)
}
}
return newlist
}
// Period is used to specify the length of a date range or frequency
type Period string
// Periods suppored by ledger
const (
PeriodWeek Period = "Weekly"
Period2Week Period = "BiWeekly"
PeriodMonth Period = "Monthly"
PeriodQuarter Period = "Quarterly"
PeriodYear Period = "Yearly"
PeriodSemiYear Period = "SemiYearly"
)
func getDateBoundaries(per Period, start, end time.Time) []time.Time {
var incDays, incMonth, incYear int
var periodStart time.Time
switch per {
case PeriodWeek:
incDays = 7
for periodStart = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, time.UTC); periodStart.Weekday() != time.Sunday; {
periodStart = periodStart.AddDate(0, 0, -1)
}
case Period2Week:
incDays = 14
for periodStart = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, time.UTC); periodStart.Weekday() != time.Sunday; {
periodStart = periodStart.AddDate(0, 0, -1)
}
case PeriodMonth:
incMonth = 1
periodStart = time.Date(start.Year(), start.Month(), 1, 0, 0, 0, 0, time.UTC)
case PeriodQuarter:
incMonth = 3
switch start.Month() {
case time.January, time.February, time.March:
periodStart = time.Date(start.Year(), time.January, 1, 0, 0, 0, 0, time.UTC)
case time.April, time.May, time.June:
periodStart = time.Date(start.Year(), time.April, 1, 0, 0, 0, 0, time.UTC)
case time.July, time.August, time.September:
periodStart = time.Date(start.Year(), time.July, 1, 0, 0, 0, 0, time.UTC)
default:
periodStart = time.Date(start.Year(), time.October, 1, 0, 0, 0, 0, time.UTC)
}
case PeriodSemiYear:
incMonth = 6
switch start.Month() {
case time.January, time.February, time.March, time.April, time.May, time.June:
periodStart = time.Date(start.Year(), time.January, 1, 0, 0, 0, 0, time.UTC)
default:
periodStart = time.Date(start.Year(), time.July, 1, 0, 0, 0, 0, time.UTC)
}
case PeriodYear:
incYear = 1
periodStart = time.Date(start.Year(), time.January, 1, 0, 0, 0, 0, time.UTC)
}
boundaries := []time.Time{periodStart}
for periodStart.Before(end) || periodStart.Equal(end) {
periodStart = periodStart.AddDate(incYear, incMonth, incDays)
boundaries = append(boundaries, periodStart)
}
return boundaries
}
// RangeType is used to specify how the data is "split" into sections
type RangeType string
const (
// RangeSnapshot will have each section be the running total at the time of the snapshot
RangeSnapshot RangeType = "Snapshot"
// RangePartition will have each section be the accumulated value of the transactions within that partition's date range
RangePartition RangeType = "Partition"
)
// RangeTransactions contains the transactions and the start and end time of the date range
type RangeTransactions struct {
Start, End time.Time
Transactions []*Transaction
}
// TransactionsByPeriod will return the transactions for each period.
func TransactionsByPeriod(trans []*Transaction, per Period) []*RangeTransactions {
var results []*RangeTransactions
if len(trans) < 1 {
return results
}
tStart := trans[0].Date
tEnd := trans[len(trans)-1].Date
boundaries := getDateBoundaries(per, tStart, tEnd)
bStart := boundaries[0]
for _, boundary := range boundaries[1:] {
bEnd := boundary
bTrans := TransactionsInDateRange(trans, bStart, bEnd)
// End date should be the last day (inclusive, so subtract 1 day)
results = append(results, &RangeTransactions{Start: bStart, End: bEnd.AddDate(0, 0, -1), Transactions: bTrans})
bStart = bEnd
}
return results
}
// RangeBalance contains the account balances and the start and end time of the date range
type RangeBalance struct {
Start, End time.Time
Balances []*Account
}
// BalancesByPeriod will return the account balances for each period.
func BalancesByPeriod(trans []*Transaction, per Period, rType RangeType) []*RangeBalance {
var results []*RangeBalance
if len(trans) < 1 {
return results
}
tStart := trans[0].Date
tEnd := trans[len(trans)-1].Date
boundaries := getDateBoundaries(per, tStart, tEnd)
bStart := boundaries[0]
for _, boundary := range boundaries[1:] {
bEnd := boundary
bTrans := TransactionsInDateRange(trans, bStart, bEnd)
// End date should be the last day (inclusive, so subtract 1 day)
results = append(results, &RangeBalance{Start: bStart, End: bEnd.AddDate(0, 0, -1), Balances: GetBalances(bTrans, []string{})})
if rType == RangePartition {
bStart = bEnd
}
}
return results
}