snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-16 05:24:45 +05:00
commit cda7c1a474
272 changed files with 31973 additions and 0 deletions

46
time/interval.go Normal file
View 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
View 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
View 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
View 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
View 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
}