snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s
Some checks failed
build / build (push) Failing after 3s
This commit is contained in:
46
time/interval.go
Normal file
46
time/interval.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package time
|
||||
|
||||
import (
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
// InternalInterval converts gqlmodel to internal
|
||||
func InternalInterval(interval gqlmodel.StatsInterval) model.Interval {
|
||||
switch interval {
|
||||
case gqlmodel.StatsIntervalHourly:
|
||||
return model.IntervalHourly
|
||||
case gqlmodel.StatsIntervalDaily:
|
||||
return model.IntervalDaily
|
||||
case gqlmodel.StatsIntervalWeekly:
|
||||
return model.IntervalWeekly
|
||||
case gqlmodel.StatsIntervalMonthly:
|
||||
return model.IntervalMonthly
|
||||
case gqlmodel.StatsIntervalYearly:
|
||||
return model.IntervalYearly
|
||||
case gqlmodel.StatsIntervalSingle:
|
||||
return model.IntervalSingle
|
||||
default:
|
||||
panic("unknown interval type " + interval)
|
||||
}
|
||||
}
|
||||
|
||||
// ExternalInterval converts internal to gqlmodel
|
||||
func ExternalInterval(interval model.Interval) gqlmodel.StatsInterval {
|
||||
switch interval {
|
||||
case model.IntervalHourly:
|
||||
return gqlmodel.StatsIntervalHourly
|
||||
case model.IntervalDaily:
|
||||
return gqlmodel.StatsIntervalDaily
|
||||
case model.IntervalWeekly:
|
||||
return gqlmodel.StatsIntervalWeekly
|
||||
case model.IntervalMonthly:
|
||||
return gqlmodel.StatsIntervalMonthly
|
||||
case model.IntervalYearly:
|
||||
return gqlmodel.StatsIntervalYearly
|
||||
case model.IntervalSingle:
|
||||
return gqlmodel.StatsIntervalSingle
|
||||
default:
|
||||
panic("unknown interval type " + interval)
|
||||
}
|
||||
}
|
||||
23
time/interval_test.go
Normal file
23
time/interval_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package time
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
func TestInterval(t *testing.T) {
|
||||
for _, inter := range []model.Interval{
|
||||
model.IntervalSingle, model.IntervalHourly, model.IntervalDaily, model.IntervalWeekly, model.IntervalMonthly, model.IntervalYearly,
|
||||
} {
|
||||
assert.Equal(t, inter, InternalInterval(ExternalInterval(inter)))
|
||||
}
|
||||
assert.Panics(t, func() {
|
||||
InternalInterval(gqlmodel.StatsInterval("meh"))
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
ExternalInterval(model.Interval("meh"))
|
||||
})
|
||||
}
|
||||
53
time/parse.go
Normal file
53
time/parse.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package time
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmattheis/go-timemath"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
const nowKey = "now"
|
||||
|
||||
// RelativeRange represents a relative range.
|
||||
type RelativeRange struct {
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
// StaticRange represents a concrete range.
|
||||
type StaticRange struct {
|
||||
From time.Time
|
||||
To time.Time
|
||||
}
|
||||
|
||||
// ParseRange parses a range and converts it to static ranges.
|
||||
func ParseRange(now time.Time, r RelativeRange, interval model.Interval, startOf, endOf time.Weekday) ([]StaticRange, error) {
|
||||
from, err := ParseTime(now, r.From, true, startOf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range from: %s", err)
|
||||
}
|
||||
to, err := ParseTime(now, r.To, false, endOf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range to: %s", err)
|
||||
}
|
||||
|
||||
return ranges(from, to, interval), nil
|
||||
}
|
||||
|
||||
// Validate tries to parse the input and only returns the error.
|
||||
func Validate(value string) error {
|
||||
_, err := ParseTime(time.Now(), value, true, time.Monday)
|
||||
return err
|
||||
}
|
||||
|
||||
// ParseTime parses time.
|
||||
func ParseTime(now time.Time, value string, startOf bool, weekday time.Weekday) (time.Time, error) {
|
||||
parse, err := time.Parse(time.RFC3339, value)
|
||||
if err == nil {
|
||||
return parse, nil
|
||||
}
|
||||
|
||||
return timemath.Parse(now, value, startOf, weekday)
|
||||
}
|
||||
224
time/parse_test.go
Normal file
224
time/parse_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package time
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traggo/server/model"
|
||||
"github.com/traggo/server/test"
|
||||
)
|
||||
|
||||
func TestParseRange(t *testing.T) {
|
||||
|
||||
entries := []testEntry{
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1d", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-12T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1d", "2019-05-13T18:52:23Z").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-12T15:55:23Z", "2019-05-13T18:52:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1w", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-06T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-10d", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-03T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1M", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-04-13T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1y", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2018-05-13T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-5s-3m-5h-2d-2w-3M-1y", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2018-01-27T10:52:18Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now/s", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now/m", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:00Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now/h", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:00:00Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now/d", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T00:00:00Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now/w", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T00:00:00Z", "2019-05-15T15:55:23Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now/M", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-01T00:00:00Z", "2019-05-15T15:55:23Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now/y", "now").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-01-01T00:00:00Z", "2019-05-15T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now", "now/s").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:23Z", "2019-05-13T15:55:23.999999999Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now", "now/m").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:23Z", "2019-05-13T15:55:59.999999999Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now", "now/h").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:23Z", "2019-05-13T15:59:59.999999999Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now", "now/d").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:23Z", "2019-05-13T23:59:59.999999999Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now", "now/d").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-13T15:55:23Z", "2019-05-13T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now", "now/w").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-15T15:55:23Z", "2019-05-19T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now", "now/M").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-15T15:55:23Z", "2019-05-31T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now", "now/y").
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-15T15:55:23Z", "2019-12-31T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now-1y/y", "now-1y/y"). // last year
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2018-01-01T00:00:00Z", "2018-12-31T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now-1w/w", "now-1w/w"). // last week
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-06T00:00:00Z", "2019-05-12T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now-1w/w", "now-1w/w-2d"). // last work monday to friday
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-06T00:00:00Z", "2019-05-10T23:59:59.999999999Z")),
|
||||
now("2019-05-15T15:55:23Z").
|
||||
Range("now-1w/w+5d", "now-1w/w"). // last weekend
|
||||
Interval(model.IntervalSingle).
|
||||
Expect(r("2019-05-11T00:00:00Z", "2019-05-12T23:59:59.999999999Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1d", "now").
|
||||
Interval(model.IntervalYearly).
|
||||
Expect(r("2019-05-12T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1d", "now").
|
||||
Interval(model.IntervalMonthly).
|
||||
Expect(r("2019-05-12T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-1d", "now").
|
||||
Interval(model.IntervalWeekly).
|
||||
Expect(r("2019-05-12T15:55:23Z", "2019-05-13T15:55:23Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-5h/h", "now/h").
|
||||
Interval(model.IntervalHourly).
|
||||
Expect(
|
||||
r("2019-05-13T10:00:00Z", "2019-05-13T10:59:59Z"),
|
||||
r("2019-05-13T11:00:00Z", "2019-05-13T11:59:59Z"),
|
||||
r("2019-05-13T12:00:00Z", "2019-05-13T12:59:59Z"),
|
||||
r("2019-05-13T13:00:00Z", "2019-05-13T13:59:59Z"),
|
||||
r("2019-05-13T14:00:00Z", "2019-05-13T14:59:59Z"),
|
||||
r("2019-05-13T15:00:00Z", "2019-05-13T15:59:59Z")),
|
||||
now("2019-05-13T15:55:23Z").
|
||||
Range("now-5d/d", "now/d").
|
||||
Interval(model.IntervalDaily).
|
||||
Expect(
|
||||
r("2019-05-08T00:00:00Z", "2019-05-08T23:59:59Z"),
|
||||
r("2019-05-09T00:00:00Z", "2019-05-09T23:59:59Z"),
|
||||
r("2019-05-10T00:00:00Z", "2019-05-10T23:59:59Z"),
|
||||
r("2019-05-11T00:00:00Z", "2019-05-11T23:59:59Z"),
|
||||
r("2019-05-12T00:00:00Z", "2019-05-12T23:59:59Z"),
|
||||
r("2019-05-13T00:00:00Z", "2019-05-13T23:59:59Z")),
|
||||
now("2019-02-13T15:55:23Z").
|
||||
Range("now-5M/M", "now/M").
|
||||
Interval(model.IntervalMonthly).
|
||||
Expect(
|
||||
r("2018-09-01T00:00:00Z", "2018-09-30T23:59:59Z"),
|
||||
r("2018-10-01T00:00:00Z", "2018-10-31T23:59:59Z"),
|
||||
r("2018-11-01T00:00:00Z", "2018-11-30T23:59:59Z"),
|
||||
r("2018-12-01T00:00:00Z", "2018-12-31T23:59:59Z"),
|
||||
r("2019-01-01T00:00:00Z", "2019-01-31T23:59:59Z"),
|
||||
r("2019-02-01T00:00:00Z", "2019-02-28T23:59:59Z")),
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
t.Run(fmt.Sprintf("now=%s;%s_to_%s;%s", entry.now, entry.from, entry.to, entry.interval), func(t *testing.T) {
|
||||
result, err := ParseRange(entry.now, RelativeRange{From: entry.from, To: entry.to}, entry.interval, time.Monday, time.Sunday)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, entry.expectRanges, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStuff(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
ranges(time.Time{}, time.Time{}, "meh")
|
||||
})
|
||||
assert.NotNil(t, Validate("invalid"))
|
||||
assert.Nil(t, Validate("now-1d"))
|
||||
_, err := ParseRange(time.Now(), RelativeRange{
|
||||
From: "invalid",
|
||||
To: "invalid",
|
||||
}, model.IntervalDaily, time.Monday, time.Sunday)
|
||||
assert.NotNil(t, err)
|
||||
_, err = ParseRange(time.Now(), RelativeRange{
|
||||
From: "now-1d",
|
||||
To: "invalid",
|
||||
}, model.IntervalDaily, time.Monday, time.Sunday)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func now(now string) testEntry {
|
||||
return testEntry{now: test.Time(now)}
|
||||
}
|
||||
|
||||
type testEntry struct {
|
||||
now time.Time
|
||||
from string
|
||||
to string
|
||||
interval model.Interval
|
||||
expectRanges []StaticRange
|
||||
}
|
||||
|
||||
func r(from string, to string) StaticRange {
|
||||
return StaticRange{
|
||||
From: test.Time(from),
|
||||
To: test.Time(to),
|
||||
}
|
||||
}
|
||||
|
||||
func (t testEntry) Expect(ranges ...StaticRange) testEntry {
|
||||
t.expectRanges = ranges
|
||||
return t
|
||||
}
|
||||
|
||||
func (t testEntry) Range(from string, to string) testEntry {
|
||||
t.from = from
|
||||
t.to = to
|
||||
return t
|
||||
}
|
||||
|
||||
func (t testEntry) Interval(interval model.Interval) testEntry {
|
||||
t.interval = interval
|
||||
return t
|
||||
}
|
||||
41
time/range.go
Normal file
41
time/range.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package time
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jmattheis/go-timemath"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
func ranges(from time.Time, to time.Time, interval model.Interval) []StaticRange {
|
||||
switch interval {
|
||||
case model.IntervalSingle:
|
||||
return []StaticRange{{From: from, To: to}}
|
||||
case model.IntervalHourly:
|
||||
return rangeForUnit(from, to, timemath.Hour)
|
||||
case model.IntervalDaily:
|
||||
return rangeForUnit(from, to, timemath.Day)
|
||||
case model.IntervalWeekly:
|
||||
return rangeForUnit(from, to, timemath.Week)
|
||||
case model.IntervalMonthly:
|
||||
return rangeForUnit(from, to, timemath.Month)
|
||||
case model.IntervalYearly:
|
||||
return rangeForUnit(from, to, timemath.Year)
|
||||
default:
|
||||
panic("unknown interval type")
|
||||
}
|
||||
}
|
||||
|
||||
func rangeForUnit(from time.Time, to time.Time, u timemath.Unit) []StaticRange {
|
||||
var result []StaticRange
|
||||
newFrom := from
|
||||
for newFrom.Before(to) {
|
||||
newTo := timemath.Second.Subtract(u.Add(newFrom, 1), 1)
|
||||
if newTo.After(to) {
|
||||
newTo = to
|
||||
}
|
||||
result = append(result, StaticRange{From: newFrom, To: newTo})
|
||||
newFrom = timemath.Second.Add(newTo, 1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user