break intervals out

This commit is contained in:
Daniel Ponte 2025-02-18 08:58:12 -05:00
parent 4bec0b5a78
commit 6c3dd5e636
6 changed files with 125 additions and 73 deletions

View 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
}

View file

@ -3,66 +3,23 @@ package partman
import (
"fmt"
"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 {
var t time.Time
switch p.Interval {
case Daily:
t = p.Time.AddDate(0, 0, i)
case Weekly:
t = p.Time.AddDate(0, 0, i*daysInWeek)
t = p.Time.AddDate(0, 0, i*common.DaysInWeek)
case Monthly:
year, month, _ := p.Time.Date()
t = time.Date(year, month+time.Month(i), 1, 0, 0, 0, 0, p.Time.Location())
case Quarterly:
t = p.Time.AddDate(0, i*monthsInQuarter, 0)
t = p.Time.AddDate(0, i*common.MonthsInQuarter, 0)
case Yearly:
year, _, _ := p.Time.Date()
@ -125,13 +82,13 @@ func (p Partition) Prev(i int) Partition {
case Daily:
t = p.Time.AddDate(0, 0, -i)
case Weekly:
t = p.Time.AddDate(0, 0, -i*daysInWeek)
t = p.Time.AddDate(0, 0, -i*common.DaysInWeek)
case Monthly:
year, month, _ := p.Time.Date()
t = time.Date(year, month-time.Month(i), 1, 0, 0, 0, 0, p.Time.Location())
case Quarterly:
t = p.Time.AddDate(0, -i*monthsInQuarter, 0)
t = p.Time.AddDate(0, -i*common.MonthsInQuarter, 0)
case Yearly:
year, _, _ := p.Time.Date()
@ -150,3 +107,21 @@ func (p Partition) Prev(i int) Partition {
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!")
}

View file

@ -10,6 +10,7 @@ import (
"strings"
"time"
"dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/internal/isoweek"
"dynatron.me/x/stillbox/pkg/config"
"dynatron.me/x/stillbox/pkg/database"
@ -325,23 +326,6 @@ func (pm *partman) Check(ctx context.Context, now time.Time) error {
}, 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 {
return p.Name
}
@ -423,7 +407,7 @@ func (pm *partman) verifyPartName(pr database.PartitionResult) (p Partition, err
if quarterNum > 4 {
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)
if parsed != p.Time {
return p, PartitionError(pn, ParsedIntvlErr{parsed: parsed, start: p.Time})

View file

@ -20,7 +20,7 @@ WHERE
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
c.call_date >= $2 ELSE TRUE END AND
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
ORDER BY date DESC
`
@ -61,7 +61,7 @@ WHERE
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
c.call_date >= $2 ELSE TRUE END AND
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
ORDER BY 4 DESC
`

View file

@ -5,10 +5,12 @@ import (
"time"
"dynatron.me/x/stillbox/internal/cache"
"dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/internal/jsontypes"
"dynatron.me/x/stillbox/pkg/calls"
"dynatron.me/x/stillbox/pkg/calls/callstore"
"dynatron.me/x/stillbox/pkg/services"
"github.com/rs/zerolog/log"
)
const DefaultExpiration = 5 * time.Minute
@ -56,20 +58,27 @@ func (s *stats) GetCallStats(ctx context.Context, interval calls.StatsInterval)
var start time.Time
now := time.Now()
end := now
bnd := common.NewTimeBounder(common.WithLocation(now.Location()))
switch interval {
case calls.IntervalHour:
start = now.Add(-24 * time.Hour) // one day
case calls.IntervalDay:
start = now.Add(-7 * 24 * time.Hour) // one week
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:
start = now.Add(-365 * 24 * time.Hour) // one year
default:
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 {
return nil, err
}

View file

@ -9,7 +9,7 @@ WHERE
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
c.call_date >= @start ELSE TRUE END AND
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
ORDER BY 4 DESC;
@ -22,6 +22,6 @@ WHERE
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
c.call_date >= @start ELSE TRUE END AND
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
ORDER BY date DESC;