break intervals out
This commit is contained in:
parent
e879468203
commit
d5b5cb4893
6 changed files with 125 additions and 73 deletions
84
internal/common/intervals.go
Normal file
84
internal/common/intervals.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DaysInWeek = 7
|
||||||
|
MonthsInQuarter = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeBounder interface {
|
||||||
|
GetDailyBounds(date time.Time) (lowerBound, upperBound time.Time)
|
||||||
|
GetWeeklyBounds(date time.Time) (lowerBound, upperBound time.Time)
|
||||||
|
GetMonthlyBounds(date time.Time) (lowerBound, upperBound time.Time)
|
||||||
|
GetQuarterlyBounds(date time.Time) (lowerBound, upperBound time.Time)
|
||||||
|
GetYearlyBounds(date time.Time) (lowerBound, upperBound time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tbOpt func(*timeBounder)
|
||||||
|
|
||||||
|
func WithLocation(l *time.Location) tbOpt {
|
||||||
|
return func(tb *timeBounder) {
|
||||||
|
tb.loc = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeBounder(opts ...tbOpt) timeBounder {
|
||||||
|
tb := timeBounder{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&tb)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tb.loc == nil {
|
||||||
|
tb.loc = time.UTC
|
||||||
|
}
|
||||||
|
|
||||||
|
return tb
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeBounder struct {
|
||||||
|
loc *time.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb timeBounder) GetDailyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
||||||
|
lowerBound = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, tb.loc)
|
||||||
|
upperBound = lowerBound.AddDate(0, 0, 1)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb timeBounder) GetWeeklyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
||||||
|
lowerBound = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, tb.loc).AddDate(0, 0, -int(date.Weekday()-time.Monday))
|
||||||
|
upperBound = lowerBound.AddDate(0, 0, DaysInWeek)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb timeBounder) GetMonthlyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
||||||
|
lowerBound = time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, tb.loc)
|
||||||
|
upperBound = lowerBound.AddDate(0, 1, 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *timeBounder) GetQuarterlyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
||||||
|
year, _, _ := date.Date()
|
||||||
|
|
||||||
|
quarter := (int(date.Month()) - 1) / MonthsInQuarter
|
||||||
|
firstMonthOfTheQuarter := time.Month(quarter*MonthsInQuarter + 1)
|
||||||
|
|
||||||
|
lowerBound = time.Date(year, firstMonthOfTheQuarter, 1, 0, 0, 0, 0, tb.loc)
|
||||||
|
upperBound = lowerBound.AddDate(0, MonthsInQuarter, 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb timeBounder) GetYearlyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
||||||
|
lowerBound = time.Date(date.Year(), 1, 1, 0, 0, 0, 0, tb.loc)
|
||||||
|
upperBound = lowerBound.AddDate(1, 0, 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -3,66 +3,23 @@ package partman
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
daysInWeek = 7
|
|
||||||
monthsInQuarter = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
func getDailyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
|
||||||
lowerBound = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.UTC)
|
|
||||||
upperBound = lowerBound.AddDate(0, 0, 1)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWeeklyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
|
||||||
lowerBound = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, -int(date.Weekday()-time.Monday))
|
|
||||||
upperBound = lowerBound.AddDate(0, 0, daysInWeek)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMonthlyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
|
||||||
lowerBound = time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
upperBound = lowerBound.AddDate(0, 1, 0)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQuarterlyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
|
||||||
year, _, _ := date.Date()
|
|
||||||
|
|
||||||
quarter := (int(date.Month()) - 1) / monthsInQuarter
|
|
||||||
firstMonthOfTheQuarter := time.Month(quarter*monthsInQuarter + 1)
|
|
||||||
|
|
||||||
lowerBound = time.Date(year, firstMonthOfTheQuarter, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
upperBound = lowerBound.AddDate(0, monthsInQuarter, 0)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getYearlyBounds(date time.Time) (lowerBound, upperBound time.Time) {
|
|
||||||
lowerBound = time.Date(date.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
upperBound = lowerBound.AddDate(1, 0, 0)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Partition) Next(i int) Partition {
|
func (p Partition) Next(i int) Partition {
|
||||||
var t time.Time
|
var t time.Time
|
||||||
switch p.Interval {
|
switch p.Interval {
|
||||||
case Daily:
|
case Daily:
|
||||||
t = p.Time.AddDate(0, 0, i)
|
t = p.Time.AddDate(0, 0, i)
|
||||||
case Weekly:
|
case Weekly:
|
||||||
t = p.Time.AddDate(0, 0, i*daysInWeek)
|
t = p.Time.AddDate(0, 0, i*common.DaysInWeek)
|
||||||
case Monthly:
|
case Monthly:
|
||||||
year, month, _ := p.Time.Date()
|
year, month, _ := p.Time.Date()
|
||||||
|
|
||||||
t = time.Date(year, month+time.Month(i), 1, 0, 0, 0, 0, p.Time.Location())
|
t = time.Date(year, month+time.Month(i), 1, 0, 0, 0, 0, p.Time.Location())
|
||||||
case Quarterly:
|
case Quarterly:
|
||||||
t = p.Time.AddDate(0, i*monthsInQuarter, 0)
|
t = p.Time.AddDate(0, i*common.MonthsInQuarter, 0)
|
||||||
case Yearly:
|
case Yearly:
|
||||||
year, _, _ := p.Time.Date()
|
year, _, _ := p.Time.Date()
|
||||||
|
|
||||||
|
@ -125,13 +82,13 @@ func (p Partition) Prev(i int) Partition {
|
||||||
case Daily:
|
case Daily:
|
||||||
t = p.Time.AddDate(0, 0, -i)
|
t = p.Time.AddDate(0, 0, -i)
|
||||||
case Weekly:
|
case Weekly:
|
||||||
t = p.Time.AddDate(0, 0, -i*daysInWeek)
|
t = p.Time.AddDate(0, 0, -i*common.DaysInWeek)
|
||||||
case Monthly:
|
case Monthly:
|
||||||
year, month, _ := p.Time.Date()
|
year, month, _ := p.Time.Date()
|
||||||
|
|
||||||
t = time.Date(year, month-time.Month(i), 1, 0, 0, 0, 0, p.Time.Location())
|
t = time.Date(year, month-time.Month(i), 1, 0, 0, 0, 0, p.Time.Location())
|
||||||
case Quarterly:
|
case Quarterly:
|
||||||
t = p.Time.AddDate(0, -i*monthsInQuarter, 0)
|
t = p.Time.AddDate(0, -i*common.MonthsInQuarter, 0)
|
||||||
case Yearly:
|
case Yearly:
|
||||||
year, _, _ := p.Time.Date()
|
year, _, _ := p.Time.Date()
|
||||||
|
|
||||||
|
@ -150,3 +107,21 @@ func (p Partition) Prev(i int) Partition {
|
||||||
return pp
|
return pp
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Partition) Range() (time.Time, time.Time) {
|
||||||
|
b := common.NewTimeBounder()
|
||||||
|
switch p.Interval {
|
||||||
|
case Daily:
|
||||||
|
return b.GetDailyBounds(p.Time)
|
||||||
|
case Weekly:
|
||||||
|
return b.GetWeeklyBounds(p.Time)
|
||||||
|
case Monthly:
|
||||||
|
return b.GetMonthlyBounds(p.Time)
|
||||||
|
case Quarterly:
|
||||||
|
return b.GetQuarterlyBounds(p.Time)
|
||||||
|
case Yearly:
|
||||||
|
return b.GetYearlyBounds(p.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("unknown interval!")
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
"dynatron.me/x/stillbox/internal/isoweek"
|
"dynatron.me/x/stillbox/internal/isoweek"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
@ -325,23 +326,6 @@ func (pm *partman) Check(ctx context.Context, now time.Time) error {
|
||||||
}, pgx.TxOptions{})
|
}, pgx.TxOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Partition) Range() (time.Time, time.Time) {
|
|
||||||
switch p.Interval {
|
|
||||||
case Daily:
|
|
||||||
return getDailyBounds(p.Time)
|
|
||||||
case Weekly:
|
|
||||||
return getWeeklyBounds(p.Time)
|
|
||||||
case Monthly:
|
|
||||||
return getMonthlyBounds(p.Time)
|
|
||||||
case Quarterly:
|
|
||||||
return getQuarterlyBounds(p.Time)
|
|
||||||
case Yearly:
|
|
||||||
return getYearlyBounds(p.Time)
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("unknown interval!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Partition) PartitionName() string {
|
func (p Partition) PartitionName() string {
|
||||||
return p.Name
|
return p.Name
|
||||||
}
|
}
|
||||||
|
@ -423,7 +407,7 @@ func (pm *partman) verifyPartName(pr database.PartitionResult) (p Partition, err
|
||||||
if quarterNum > 4 {
|
if quarterNum > 4 {
|
||||||
return p, PartitionError(pn, errors.New("invalid quarter"))
|
return p, PartitionError(pn, errors.New("invalid quarter"))
|
||||||
}
|
}
|
||||||
firstMonthOfTheQuarter := time.Month((quarterNum-1)*monthsInQuarter + 1)
|
firstMonthOfTheQuarter := time.Month((quarterNum-1)*common.MonthsInQuarter + 1)
|
||||||
parsed := time.Date(year, firstMonthOfTheQuarter, 1, 0, 0, 0, 0, time.UTC)
|
parsed := time.Date(year, firstMonthOfTheQuarter, 1, 0, 0, 0, 0, time.UTC)
|
||||||
if parsed != p.Time {
|
if parsed != p.Time {
|
||||||
return p, PartitionError(pn, ParsedIntvlErr{parsed: parsed, start: p.Time})
|
return p, PartitionError(pn, ParsedIntvlErr{parsed: parsed, start: p.Time})
|
||||||
|
|
|
@ -20,7 +20,7 @@ WHERE
|
||||||
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date >= $2 ELSE TRUE END AND
|
c.call_date >= $2 ELSE TRUE END AND
|
||||||
CASE WHEN $3::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN $3::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date <= $3 ELSE TRUE END
|
c.call_date < $3 ELSE TRUE END
|
||||||
GROUP BY date
|
GROUP BY date
|
||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
`
|
`
|
||||||
|
@ -61,7 +61,7 @@ WHERE
|
||||||
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date >= $2 ELSE TRUE END AND
|
c.call_date >= $2 ELSE TRUE END AND
|
||||||
CASE WHEN $3::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN $3::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date <= $3 ELSE TRUE END
|
c.call_date < $3 ELSE TRUE END
|
||||||
GROUP BY 2, 3, 4
|
GROUP BY 2, 3, 4
|
||||||
ORDER BY 4 DESC
|
ORDER BY 4 DESC
|
||||||
`
|
`
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/cache"
|
"dynatron.me/x/stillbox/internal/cache"
|
||||||
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
||||||
"dynatron.me/x/stillbox/pkg/services"
|
"dynatron.me/x/stillbox/pkg/services"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultExpiration = 5 * time.Minute
|
const DefaultExpiration = 5 * time.Minute
|
||||||
|
@ -56,20 +58,27 @@ func (s *stats) GetCallStats(ctx context.Context, interval calls.StatsInterval)
|
||||||
|
|
||||||
var start time.Time
|
var start time.Time
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
end := now
|
||||||
|
bnd := common.NewTimeBounder(common.WithLocation(now.Location()))
|
||||||
|
|
||||||
switch interval {
|
switch interval {
|
||||||
case calls.IntervalHour:
|
case calls.IntervalHour:
|
||||||
start = now.Add(-24 * time.Hour) // one day
|
start = now.Add(-24 * time.Hour) // one day
|
||||||
case calls.IntervalDay:
|
case calls.IntervalDay:
|
||||||
start = now.Add(-7 * 24 * time.Hour) // one week
|
start = now.Add(-7 * 24 * time.Hour) // one week
|
||||||
case calls.IntervalWeek:
|
case calls.IntervalWeek:
|
||||||
start = now.Add(-30 * 24 * time.Hour) // one month
|
start, end = bnd.GetMonthlyBounds(now)
|
||||||
|
start, _ = bnd.GetWeeklyBounds(start)
|
||||||
|
_, end = bnd.GetWeeklyBounds(end)
|
||||||
case calls.IntervalMonth:
|
case calls.IntervalMonth:
|
||||||
start = now.Add(-365 * 24 * time.Hour) // one year
|
start = now.Add(-365 * 24 * time.Hour) // one year
|
||||||
default:
|
default:
|
||||||
return nil, calls.ErrInvalidInterval
|
return nil, calls.ErrInvalidInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := s.cs.CallStats(ctx, interval, jsontypes.Time(start), jsontypes.Time(now))
|
log.Debug().Str("start", start.String()).Str("end", end.String()).Msg("bound")
|
||||||
|
|
||||||
|
st, err := s.cs.CallStats(ctx, interval, jsontypes.Time(start), jsontypes.Time(end))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ WHERE
|
||||||
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date >= @start ELSE TRUE END AND
|
c.call_date >= @start ELSE TRUE END AND
|
||||||
CASE WHEN sqlc.narg('end')::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN sqlc.narg('end')::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date <= sqlc.narg('end') ELSE TRUE END
|
c.call_date < sqlc.narg('end') ELSE TRUE END
|
||||||
GROUP BY 2, 3, 4
|
GROUP BY 2, 3, 4
|
||||||
ORDER BY 4 DESC;
|
ORDER BY 4 DESC;
|
||||||
|
|
||||||
|
@ -22,6 +22,6 @@ WHERE
|
||||||
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date >= @start ELSE TRUE END AND
|
c.call_date >= @start ELSE TRUE END AND
|
||||||
CASE WHEN sqlc.narg('end')::TIMESTAMPTZ IS NOT NULL THEN
|
CASE WHEN sqlc.narg('end')::TIMESTAMPTZ IS NOT NULL THEN
|
||||||
c.call_date <= sqlc.narg('end') ELSE TRUE END
|
c.call_date < sqlc.narg('end') ELSE TRUE END
|
||||||
GROUP BY date
|
GROUP BY date
|
||||||
ORDER BY date DESC;
|
ORDER BY date DESC;
|
||||||
|
|
Loading…
Add table
Reference in a new issue